Make struct with EdgeDB specific types compatable with async-graphql OutputType

54 views Asked by At

I have following GraphQL mutation:

#[derive(Queryable, SimpleObject)]
struct OtpPhoneRequest {
   id: edgedb_protocol::model::Uuid,
   otp: i32,
   phone: String,
   confirmed_at: Option<edgedb_protocol::model::Datetime>,
}
#[Object]
impl Authentication {
   async fn confirm_otp<'ctx>(&self, ctx: &Context<'ctx>, token: String, otp: i32) -> OtpPhoneRequest {
      let edb = &ctx.data_unchecked::<AppContext>().edb;
      let otp_request = edb
         .query_single::<OtpPhoneRequest, _>(
            "
         select std::assert_single((
            select OtpPhoneRequest {
               id,
               otp,
               phone,
               confirmedAt
            } filter (OtpPhoneRequest.id = <uuid>$0)
         )) 
      ",
            &(edgedb_protocol::model::Uuid::from_str(token.as_str()).unwrap(),),
         )
         .await.unwrap();
      return otp_request.unwrap()
   }
}

The problem is that async-graphql handlers require to return type compatible with OutputType, so I get an error when deriving OtpPhoneRequest from SimpleObject

the trait bound `edgedb_protocol::model::Uuid: OutputType` is not satisfied
required for `&edgedb_protocol::model::Uuid` to implement `OutputType`

the trait bound `edgedb_protocol::model::Datetime: OutputType` is not satisfied
required for `std::option::Option<edgedb_protocol::model::Datetime>` to implement `OutputType`

I also tried to create custom scalars for edgedb types:

pub struct UuidScalar(edgedb_protocol::model::Uuid);
#[async_graphql::Scalar]
impl ScalarType for UuidScalar {
   fn parse(value: async_graphql::Value) -> std::result::Result<UuidScalar, InputValueError<UuidScalar>> {
      if let async_graphql::Value::String(s) = value {
         let parsed = edgedb_protocol::model::Uuid::from_str(s.as_str())?;
         Ok(UuidScalar(parsed))
         // How to convert Uuid to UuidScalar?
      } else {
         Err(InputValueError::custom("Value for Uuid scalar should be String"))
      }
   }
   fn to_value(&self) -> async_graphql::Value {
      async_graphql::Value::String(self.0.to_string())
   }
}

But then this scalars aren't compatible with Queryable.

I can create two separate structs deriving from Queryable and SimpleObject and manually convert between them or implement From trait, but, how can I avoid code duplication and use single struct?

1

There are 1 answers

0
ZiiMakc On

I would not try to return or align database type to graphql response type and instead map one to another, this is not code duplication, but separation of concerns and a good practice.

Regarding uuid, you can include a feature flag which integrates async-graphql with uuid crate in Cargo.toml:

async-graphql = { version = "7.0.0", features = ["uuid"] }

or implement your own scalar if you use different uuid library based on example.