Get specific data type from aggregation instead of Document

I am trying to use the aggregation pipeline in rust driver to return a vector of a specific type. At the moment I am only able to get Vec Document type. How do I transform this to Vec T type?

my current code looks like this

#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Bill {
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "serialize_object_id",
        rename(serialize = "id")
    )]
    pub _id: Option<oid::ObjectId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(required)]
    pub frequency: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(required)]
    pub amount: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_date: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<DateTime<Utc>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(required)]
    pub contact_number: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub policy_number: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[validate(required)]
    pub title: Option<String>,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "serialize_object_id"
    )]
    #[validate(required)]
    pub property_id: Option<oid::ObjectId>,
}

let collection = db.collection::<Document>("bills");

let pipeline: Vec<Document> = vec![doc! {
  "$match": {
     "property_id": oid::ObjectId::parse_str(id)?
   },
}];

let cursor: Cursor<Document> = collection.aggregate(pipeline, None).await?;
let bills: Result<Vec<Document>, _> = cursor.try_collect().await;

 Ok(bills?)

I have tried to change the collection type using turbo fish syntax but that only seems to work with basic query syntax like find but not aggregation. Something like this

 let collection = db.collection::<Bill>("bills");

 let pipeline: Vec<Document> = vec![doc! {
   "$match": {
      "property_id": oid::ObjectId::parse_str(id)?
     },
 }];

 let cursor: Cursor<Bill> = collection.aggregate(pipeline, None).await?;
 let bills: Result<Vec<Bill>, _> = cursor.try_collect().await;

 Ok(bills?)

but then I get the following error

`?` operator has incompatible types
`?` operator cannot convert from `mongodb::Cursor<mongodb::bson::Document>` to `mongodb::Cursor<Bill>`
expected struct `mongodb::Cursor<Bill>`
   found struct `mongodb::Cursor<mongodb::bson::Document>`

My only other thing I can think of is to map over the bills iterator and use from_document bson function to deserialize to the Bill type but that doesnt seem to be performant and also Im struggling to implement it. It currently looks something like this.

let newBills:Vec<Bill> = bills?.iter().map(|&e| from_document::<Bill>(e)).collect();

I then get the following error

a value of type `Vec<Bill>` cannot be built from an iterator over elements of type `Result<Bill, mongodb::bson::de::Error>`
the trait `FromIterator<Result<Bill, mongodb::bson::de::Error>>` is not implemented for `Vec<Bill>`
the trait `FromIterator<T>` is implemented for `Vec<T>`

Any help would be greatly appreciated

ok so I managed to convert the mongo Documents to Bills by using into_iter() instead which iterates over T instead of &T. Also from_document returns a Result which I needed to unwrap.

let cursor: Cursor<Document> = collection.aggregate(pipeline, None).await?;
let bills: Result<Vec<Document>, _> = cursor.try_collect().await;

let new_bills: Vec<Bill> = bills?
  .into_iter()
  .map(|e| mongodb::from_document::<Bill>(e).unwrap())
  .collect();

I still feel like there is a better way of doing this. Collecting a vector then iterating over it doesn’t seem optimal.

1 Like