bson-rust icon indicating copy to clipboard operation
bson-rust copied to clipboard

RUST-1178 The ability to custom tag DateTime<Utc> for automatic conversion.

Open ruseinov opened this issue 4 years ago • 8 comments

Here's an example of a collection with records expiring by expires_at:

#[collection = "subscriptions"]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Subscription {
    #[serde(rename = "_id")]
    pub id: Option<ObjectId>,
    pub expires_at: DateTime<Utc>,
}

In order for that to work I need to use the Bson::UtcDateTime helper (now DateTime), but in that case when I deliver this model to the frontend I end up with

    "expires_at": {
        "$date": {
            "$numberLong": 1591700287095
        }
    },

instead of proper date. The approach with duplicating the object just for the sake date serialization is not the most convenient one.

What would be nice to have is some sort of annotation that would tell Bson Encoder/Decoder that this DateTime<Utc> field has to be de/serialized as Bson date, while serde_json will serialize it as usual.

The same applies to ObjectId, currently if one wants a readable hex string instead of a json object there's a lot of duplication involved.

ruseinov avatar Jun 09 '20 12:06 ruseinov

Here's my temp fix, which is verbose, but it does the job.

pub struct SubscriptionDTO {
    pub data: Subscription,
}

impl Serialize for SubscriptionDTO {
    fn serialize<S>(
        &self,
        serializer: S,
    ) -> result::Result<<S as Serializer>::Ok, <S as Serializer>::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("SubscriptionDTO", 5)?;
       ... other fields
        state.serialize_field("expires_at", &self.data.expires_at.0)?;
        state.end()
    }
}

ruseinov avatar Jun 09 '20 13:06 ruseinov

To reduce the amount of code required to achieve this, you could use #[serde(serialize_with)] on the date field.

I filed RUST-506 to track the status of work on this. Thank you for filing the issue!

patrickfreed avatar Jul 17 '20 22:07 patrickfreed

Any updates on this? The $numberLong format really isn't convenient for front-end, and using a custom serializer (or a helper like #[serde(serialize_with = "bson_datetime_as_rfc3339_string::serialize")] causes the field to be inserted as a string in Mongo.

Also why is the jira issue closed?

Cosmiiko avatar Jan 31 '22 14:01 Cosmiiko

In RUST-506 we introduced some more general purpose helpers, but as you point out they didn't quite solve the initial problem of automatic serialization changes on a per-format basis. For that, I've filed RUST-1178. Thanks for bringing this to our attention!

Until that ticket gets completed, you can use separate structs for communicating with the DB and with the frontend which have different serialization implementations.

patrickfreed avatar Feb 08 '22 15:02 patrickfreed

@ruseinov , have you found any other solutions to this problem except an extra wrapper struct?

max-block avatar Jun 02 '22 10:06 max-block

@max-block TBH I don't remember anymore, but I think not. Haven't been interfacing with MongoDB lately, therefore I have got no recent experience with BSON.

ruseinov avatar Jun 03 '22 20:06 ruseinov

Any updates on this?

I ended up with this. When I pass this into my database create function, the create function converts it into a Foobar<bson::DateTime>. This requires all sorts of unnecessary cloning, so a real solution would still be better.

struct Foobar<T = DateTime<Utc>> {
    created_date: T,
}

impl From<Foobar<DateTime<Utc>>> for Foobar<bson::DateTime> {
    fn from(value: Foobar<DateTime<Utc>>) -> Self {
        Foobar {
            created_date: value.created_date.into(),
        }
    }
}

clarkmcc avatar Jan 23 '23 17:01 clarkmcc

Hi @clarkmcc, while our current development plan is at capacity, this remains in our backlog for future prioritization. Thank you for the suggestion!

bajanam avatar Jan 24 '23 19:01 bajanam