thedodd / wither

An ODM for MongoDB built on the official MongoDB Rust driver.
https://docs.rs/wither
Other
324 stars 40 forks source link

How to Specify Enum Literals? #76

Open wbrickner opened 3 years ago

wbrickner commented 3 years ago

Hello πŸ‘‹πŸ»

I am trying to update a document.

I need to set the field standing, of type UserStanding:

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserStanding {
  /// the request is received, pending approval
  Pending,

  /// user account approved, active, fully enabled, etc.
  Active
}

#[derive(Model, Serialize, Deserialize, Clone, Debug)]
pub struct User {
  /// User standing (for customer signup requests)
  pub standing: UserStanding
}

When I try to do this:

let update = {
    doc! {
      "$set": doc! {
        "location": &form.location,
        "standing": UserStanding::Active
      }
    }
  };

let result = User::find_one_and_update(
    &db, 
    doc! { "id": &path.user_id }, 
    update, None
  ).await;

I get the error

error[E0277]: the trait bound `Bson: From<UserStanding>` is not satisfied
  --> src/services/dashboard/users/review.rs:24:15
   |
24 |         "$set": doc! {
   |  _______________^
25 | |         "location": &form.location,
26 | |         "standing": UserStanding::Active
27 | |       }
   | |_______^ the trait `From<UserStanding>` is not implemented for `Bson`
   |
   = help: the following implementations were found:
             <Bson as From<&T>>
             <Bson as From<&[T]>>
             <Bson as From<&str>>
             <Bson as From<Vec<T>>>
           and 19 others
   = note: required by `std::convert::From::from`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

Thank you.

thedodd commented 3 years ago

Hey there. So, take a look at the bson docs here: https://docs.rs/bson/1.2.0/bson/ser/fn.to_bson.html

Though there are a few ways to deal with this, using this function to serialize your type as Bson is probably best, as it will ensure that if you actually have some BSON incompatible serde rules on your enum, this function will catch such issues (as an error).

That is generally what I would recommend in such cases, as this is the same mechanism which would be used if you have that enum type embedded in a struct which you are serializing/deserializing as bson.

wbrickner commented 3 years ago

So I would instead do something like

"$set": doc! {
  "standing": to_bson(&UserStanding::Active).unwrap()
}

Is that correct?

wbrickner commented 3 years ago

In another case, imagine I have

struct User {
  pub my_field: Option<MyWeirdType>
}

How can I find all documents which have a Some variant without explicitly constructing the thing I'm looking for? Do I need to step down to the serde/BSON serialization level and explicitly search for something like:

{ "my_field": {"tag": "Some" } }

This feels ugly and error-prone, I hope there is a better way to do this.

thedodd commented 3 years ago

@wbrickner so, at the end of the day the enum is just another field in the DB. So as long as your enum field in indexed in the DB (otherwise it may be dead slow and may negatively impact your DB), then you have to treat it like any other field you are filtering on.

So if you know that your enum value serializes as {"tag": "Some"}, then you could manually write that, or you could use to_bson(...) to serialize its representation first and use that as the query. If you don't like those options, you could create your own as_bson method (or whatever you want to call it) which will just return the Bson enum representation for you as static/hard-coded values.

Thoughts?