Partial Structs

Hello Community!

I'm new to Rust and need some help :slight_smile:
Is there a way to cast a struct in a way that makes all fields to Options without defining an additional struct, like Typescript's Partial Generic?

What I currently do is:

#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
pub struct Schema {
    pub _id: ObjectId,
    pub email: String,
    pub password: String,
    pub first_name: String,
    pub last_name: String,
    pub access_level: AccessLevel,
    pub create_ms: i64,
    pub create_admin_id: Option<ObjectId>,
    pub last_modified_ms: Option<i64>,
    pub last_modified_admin_id: Option<ObjectId>
}

#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
pub struct Partial {
    pub _id: Option<ObjectId>,
    pub email: Option<String>,
    pub password: Option<String>,
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub access_level: Option<AccessLevel>,
    pub create_ms: Option<i64>,
    pub create_admin_id: Option<ObjectId>,
    pub last_modified_ms: Option<i64>,
    pub last_modified_admin_id: Option<ObjectId>
}

There is no built-in feature for this, but you can do it easily with a macro. Anyway, what do you need this for?

When I query mongodb and just want certain fields to be returned on an endpoint, it will panic when I don't provide the full set of data:

let find_options = FindOneOptions::builder()
    .projection(doc!{ "first_name": true, "last_name": true, "email": true, "access_level": true, "create_ms": true })
    .build();
let user: Option<user::Schema> = db.collection::<user::Schema>(user::COLLECTION).find_one(doc! { "_id": user_id? }, find_options).await?;
// panic because I dont query "_id" and "password"

So my solution currently is to query for a "Partial":

let find_options = FindOneOptions::builder()
    .projection(doc!{ "first_name": true, "last_name": true, "email": true, "access_level": true, "create_ms": true })
    .build();
let user: Option<user::Partial> = db.collection::<user::Partial>(user::COLLECTION).find_one(doc! { "_id": user_id? }, find_options).await?;

I guess he wants it for a "user builder".

If that's the case, imho It's better if you implement the builder pattern rather than having a Partial representation of your models.

An fully general solution would be one of

  • return a dynamic map from a query rather than a static type
  • generate a new type for each query with just the requested fields

Using something like frunk's LabelledGeneric this can be made quite convenient and avoid the pain of having a unique type for each query.

Generally, if you want to play around with that level of meta type mapping you need to use procedural macros or something like frunk (and its proc macros) to lift type declarations into the type system.

Disclaimer: I haven't actually used frunk, just admired the extreme use of the type system. Using frunk may absolutely kill your compile times and lead to completely inscrutable type errors. Maybe don't.

It may be interesting to see how sqlx handles querying for select/join/etc, since it's very statically typed. (To the point it checks your DB queries are correctly typed!)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.