Typing of return type after mongodb projection

Viewed 265

I am making a graphql resolver in rust, and am only fetching the fields from the graphql query in my mongodb database. However Rust complains that the fetched data, of course, is now not of the same type as the specified return type. What is the right way to do something like this.

I guess I could do #[serde(default)], but that doesn't work exactly as expected (I will explain later)

use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};

#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
    #[serde(rename = "_id")]
    pub id: ObjectId,
    pub title: String,
    // I could do something like
    // #[serde(default)]
    pub body: String,
}

#[ComplexObject]
impl Post {
    async fn text_snippet(&self) -> &str {
        let length = self.body.len();
        let end = min(length, 5);
        &self.body[0..end]
    }
}

struct Query;
#[Object]
impl Query {
    // fetching posts
    async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<Post> {
        let posts = ctx.data_unchecked::<Collection<Post>>();
        let projection = // getting the projection doc here based on graphql fields, lets say doc! {"title": 1}

        let options = FindOptions::builder().limit(10).projection(projection).build();
        let cursor = posts.find(None, options).await.unwrap();

        cursor.try_collect().await.unwrap_or_else(|_| vec![])
    }
}

But when I run the query

{
    posts {
        id
        title
        textSnippet
    }
}

i get

thread 'actix-rt:worker:0' panicked at 'called `Result::unwrap()` on an `Err` value: Error { kind: BsonDecode(DeserializationError { message: "missing field `body`" }), labels: [] }', server/src/schema/post.rs:20:46
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

and when i do the #[serde(default)] stuff on body, and I then query textSnippet and not body, the textSnippet is an empty string.

How do i fix this?

2 Answers

Could you wrap every field in Post with an Option and let the try_collect fill the returned fields for you?

You can create a struct with those fileds you need and use a collection of the new struct.

use async_graphql::*;
use serde::{Deserialize, Serialize};
use mongodb::{bson::doc, bson::oid::ObjectId, options::FindOptions, Collection};

#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct Post {
    #[serde(rename = "_id")]
    pub id: ObjectId,
    pub title: String,
    // I could do something like
    // #[serde(default)]
    pub body: String,
}

#[derive(SimpleObject, Serialize, Deserialize, Debug)]
#[graphql(complex)]
struct PostTitle {
    #[serde(rename = "_id")]
    pub id: ObjectId,
    pub title: String,
}

struct Query;
#[Object]
impl Query {
    // fetching posts
    async fn posts<'ctx>(&self, ctx: &Context<'ctx>) -> Vec<PostTitle> {
        let posts = ctx.data_unchecked::<Collection<PostTitle>>();
        let projection = doc! {"title": 1}

        let options = FindOptions::builder().limit(10).projection(projection).build();
        let cursor = posts.find(None, options).await.unwrap();

        cursor.try_collect().await.unwrap_or_else(|_| vec![])
    }
}
Related