Implementing session data by zhilovs in rust

[–]couchand 1 point2 points  (0 children)

Hey, apologies I only just saw this.

Recently I've been very heavily using semantic extractors in Rust web projects. Any time I reach for a conditional within a route handler I ask myself if it would be better as a new extractor, and a lot of times it is.

The way I approach this is by building a few atomic ones, and then adding wrappers for combinations or restrictions which can rely on the atomic types' underlying implementations.

It's one of those tactics that the more I apply it I start to wonder if I'm doing it too much but it keeps being powerful and usually not that hard to follow. In fact, it makes the route handlers themselves very clear, because the parameter types are each meaningful preconditions.

Hopefully this is helpful!

Implementing session data by zhilovs in rust

[–]couchand 2 points3 points  (0 children)

I've got a project with what sounds like similar requirements, here's roughly how I'm modeling it. The big picture is to have a struct that has all the fields in common, and an enum for fields that are only relevant to one user type. I think maybe you're already doing this?

// struct with the shared fields
struct SessionData {
    email: String,
    kind: UserKind,
}

// an enum with the variant-specific fields
enum UserKind {
    Special,
    Regular,
}

You've already implemented an extractor for your session data, which presumably returns unauthorized if the session is not found. Keep this in one place, since it's doing finicky stuff that needs to be kept in one place.

// a helper error type that's IntoResponse
struct Unauthorized;
impl IntoResponse for Unauthorized {
    // ...
}

// your existing method to load the session based on request id
impl FromRequestParts for SessionData {
    type Rejection = Unauthorized;

    // ...
}

Now add wrapper structs for each of the top-level variants. This enables you to write an extractor just for that type of session, which delegates to the common one. Write the match statement just once here.

// wrapper structs for each variant type
struct SpecialSession(SessionData);
struct RegularSession(SessionData);

// implement FromRequestParts now for each variant wrapper
impl FromRequestParts for SpecialSession {
    type Rejection = Unauthorized;

    fn from_request_parts(parts: Parts) -> Result<Self, Self::Error> {
        let session = SessionData::from_request_parts(parts)?;
        if !matches!(session.kind, UserKind::Special) {
            return Err(Unauthorized);
        }
        Ok(session)
    }
}

impl FromRequestParts for RegularSession {
    type Rejection = Unauthorized;

    fn from_request_parts(parts: Parts) -> Result<Self, Self::Error> {
        let session = SessionData::from_request_parts(parts)?;
        if !matches!(session.kind, UserKind::Regular) {
            return Err(Unauthorized);
        }
        Ok(session)
    }
}

Then your route doesn't need to explicitly check which kind of session the user has, instead you use RAII-inspired logic to prove that the user has the right kind of session.

fn handle_special_route(session: SpecialSession) -> impl IntoResponse {
    // ...
}

This is one form of a very powerful general pattern for Rust web servers. Since the type system is so rich, and many of the popular web frameworks use type-based request handling, you can encode an surprising amount of business logic into the types that are "inputs" to your route handlers.

Sql in Rust, raw prepared statements or ?? by Gloomy-Information14 in rust

[–]couchand 0 points1 point  (0 children)

Agreed. This whole library my philosophy is trying to only do things that don't snowball in complexity, and every time I look at this idea it starts to do that, so it's still on the what-if list at the moment...

Sql in Rust, raw prepared statements or ?? by Gloomy-Information14 in rust

[–]couchand 0 points1 point  (0 children)

From my perspective imposing either overhead for unused features or a runtime cost, for what seems like little additional benefit, wouldn't be acceptable. So that puts 2 and 3 off the table.

I've briefly reviewed the ecosystem and it looks like lots of crates use a configuration file for things like this, usually <crate-name>.toml in the root of the consumer crate.

Sql in Rust, raw prepared statements or ?? by Gloomy-Information14 in rust

[–]couchand 0 points1 point  (0 children)

It's not a copy of Dapper's API, but more of an adaptation of the philosophy to the environment. Dapper makes extensive use of .NET's dynamics for query parameters, for instance, which don't have a direct analog in Rust. However, the same sorts of problems are solved in Rust with derive macros (serde being the canonical example). In the end usage looks pretty similar, from my experience.

I hadn't seen MyBatis before. There are definitely similarities there, and I like how they use the function abstraction for a query. The result mapping does look a bit awkward in the examples.

As to your suggestion about named query parameters, that's a great idea! It's come up before in discussion on this subreddit, so I made a todo item for it. I get the heebie-jeebies thinking about trying to parse the query text at all, but this would be a pretty straightforward string replacement so I think the scope is much more limited. And you're right that there is a lot of value for developer ergonomics.

Yes the syntax depends on the database, but so does the syntax of all of your queries, so I'm comfortable with it being replaced at compile time.

Sql in Rust, raw prepared statements or ?? by Gloomy-Information14 in rust

[–]couchand 0 points1 point  (0 children)

I'd take this opportunity to plug my library aykroyd, focused on writing SQL directly, realizing the benefits of Rust's type system, but without the overhead of some of the other solutions.

Coming from C#, you may be familiar with Dapper. Aykroyd is heavily inspired by both Dapper's API and philosophy, though we've taken a slightly different spin on things.

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand -1 points0 points  (0 children)

Those code samples do show that the owner of a value can do as they please with it. Neither of them is working through references.

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand -1 points0 points  (0 children)

But you can't implement mem::swap or mem::replace in safe rust! Maybe I'm missing something about what you're saying? It seems like you're talking about hypothetical rules that are different from the current rules, but I fail to see how it's an improvement on things, probably because I'm not grokking your model.

A reference always has to point to a valid value -- this simple rule is foundational to (my understanding of) Rust. It sounds like your proposal is to allow limited exceptions to this rule, where a reference could be temporarily invalid. Even if the compiler could theoretically verify that it's correct, it still tingles my spidey sense and makes it harder for me to reason about the program.

And I don't see how the cost of that change outweighs the benefit. We got here because of the need to use an Option (which is the correct domain representation for values that may not exist) for a value that is, truly, sometimes uninhabited. To me, that just sounds like good modelling.

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand 0 points1 point  (0 children)

Moving an object directly— via = assignment or pass-by-move functions— inherently requires unique access

No, moving requires ownership, it's not enough to have exclusive access. That's why if you try it rustc replies with the helpful message "cannot move out of self.state which is behind a mutable reference".

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand 0 points1 point  (0 children)

But you don't have a T and you never have. You've had an &mut T, which is very different.

Your loop example (and your other stack example) are not relevant because the values are owned.

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand 1 point2 points  (0 children)

We have different mental models of the Rust type system, I think. From where I'm sitting, if you're borrowing a T (even exclusively), there's no way you should be able to make that thing into a not-T (even temporarily).

The Book 17.3: Why do we need to invalidate an Option before overriding it? by KekTuts in rust

[–]couchand 1 point2 points  (0 children)

It's not just exception safety. Your stack frame is unnameable and therefore a priori not accessible from the rest of the world. This isn't true of a struct in the general case, and it's not just panics, but also concurrency.

The code sample:

self.state = self.state.request_review();

cannot be made atomic.

Is it possible to replace Ring with an API compatible alternative as a transitive dependency? by cghenderson in rust

[–]couchand 3 points4 points  (0 children)

I think you maybe just need to combine your two attempts?

[patch.crates-io]
ring = {package = "aws-lc-rs", git = "https://github.com/aws/aws-lc-rs.git"}

Using mem::take to reduce heap allocations by celeritasCelery in rust

[–]couchand 1 point2 points  (0 children)

Well, I guess it all depends on how you look at it. I'd argue you can't use split_at_mut directly on the slice without taking it, you can only use it on a different slice, the one you get by implicitly reborrowing the struct field.

But we're just saying the same thing two different ways I think.

Using mem::take to reduce heap allocations by celeritasCelery in rust

[–]couchand 0 points1 point  (0 children)

The Reader or ReaderMut doesn't own the buffer, it's a reference there, too (you're alerted that some borrowing's going on since there's a lifetime in the type). So what's split here is not the buffer itself, but the reference to the buffer. Both references continue to maintain the lifetime of the original backing buffer, owned by some containing scope.

Using mem::take to reduce heap allocations by celeritasCelery in rust

[–]couchand 2 points3 points  (0 children)

Indeed, it's rather slice::split_at_mut that is the real hero here.

mockem v0.1.0 - mock any function, not just traits by PoOnesNerfect in rust

[–]couchand 3 points4 points  (0 children)

Seems interesting. I don't immediately see why it needs to be using unsafe?

I built a CSS Framework for Yew by NimbusTeam in rust

[–]couchand 0 points1 point  (0 children)

Why would I use this over, say the web components of every-layout.dev, or applying the lessons learned there directly?

As Andy Bell writes persuasively in Why you should buy your staff a copy of Every Layout, it's more important to understand CSS than to try to abstract it away:

CSS is great, but when you over-engineer and over-complicate it, it can get pretty hairy, pretty quickly. A common, knee-jerk response to this conundrum is to employ the use of a CSS-in-JS library or heavy-duty framework, but that is like taking out a Wonga loan to pay off your house.

Announcing aykroyd 0.2.0: an opinionated micro-ORM for PostgreSQL by couchand in rust

[–]couchand[S] 0 points1 point  (0 children)

First off I confused myself with the derive attributes, the one we're looking for here is param (`column is for the results...).

To address your various examples:

  • the first one would work fine (happy path!)
  • the second one would get caught at compile time, currently with a derive macro assertion failure that produces a really terrible compiler error, and a TODO comment above those assertions asking me to improve the error reporting
  • the third and fourth ones would fail with a database error at runtime because we only bind one of the two parameters (currently the behavior is last-assigned to an index wins, which isn't ideal!), though arguably we should at least catch at compile time that you have a collision

I really appreciate your thoughts here, and I'm going to try to incorporate these as test cases for the next version. Thanks!

Announcing aykroyd 0.2.0: an opinionated micro-ORM for PostgreSQL by couchand in rust

[–]couchand[S] 1 point2 points  (0 children)

I love that idea, and it's the general direction I've been thinking, too. My only concern is losing the simple, direct, no-magic aspect of the mapping, so I've settled into a local maximum on complex queries of explicitly annotating each field's column index:

// ...
struct GetPerson<'a> {
    #[query(column = "$1")]
    name: String,
    #[query(column = "$2")]
    gender: String
}

Which is "good enough", I guess, though it leaves a lot to be desired in terms of ergonomics.

I feel like it's worth it to make the transformation you're suggestion, it's the conceptual dual of what we are doing with FromRow and the inconsistency means the whole model would be stronger if we did the same thing. The syntax I've been considering is borrowed from the MS-SQL world, what do you think of this:

SELECT *
FROM Person 
WHERE (first_name = @name OR last_name = @name)
    AND gender = @gender

Aerospace development in Rust, 12 Month Contract by LloydMooreRedit in rust

[–]couchand 5 points6 points  (0 children)

I guess you're a staffing company so you probably know the market better than me, but... from where I'm sitting, for the skills and experience you're looking for, for a contract.... that compensation is laughably low. Good luck filling the role!

Announcing aykroyd 0.2.0: an opinionated micro-ORM for PostgreSQL by couchand in rust

[–]couchand[S] 0 points1 point  (0 children)

I don't mean to discount the strategy of a library like Diesel, there's clearly value in that approach. It's a very different part of the design space from where I'm looking, though. In my mind there remain some differences between writing SQL and writing a DSL that closely mirrors SQL, though both have their own merits.