graphql-rust / juniper

GraphQL server library for Rust
Other
5.62k stars 416 forks source link

GraphQL Union types on generic Rust enums #1262

Open murtlatif opened 1 month ago

murtlatif commented 1 month ago

I've been struggling to figure out the best way to implement a generic rust enum as a GraphQL union.

Objective

My goal is to have a generic enum type in rust enum Thing<T> and be able to export concrete types of this generic type to my GraphQL schema.

For example, having:

#[derive(GraphQLUnion)]
pub enum Thing<T> {
  A(ThingA<T>),
  B(ThingB<T>),
  C(ThingC<T>),
}

where I can explicitly export types like

union IntThing = IntThingA | IntThingB | IntThingC

for Thing<i32>.

My current solution is to create the entire enum and enum variant structs in a macro for each type, but it would be a significant improvement for me to be able to define a generic struct and just have some sort of implementation for each type I want to export. This will allow me to define more implementations like methods on the generic type without leaning so much into the macro (I would prefer not having to write all function methods in the macro definition).

We can export generic structs as GraphQL objects using the #[juniper::graphql_object] on a concrete implementation like so:

struct Thing<T> {
  something: T,
}

#[juniper::graphql_object(name = "IntThing")]
impl Thing<i32> {
  fn something(&self) -> i32 {
    self.something
  }
}

But it's unclear to me how to do a similar thing for union types.

Attempt

My current best attempt looks similar to what I would do for enums with generic lifetimes:

#[derive(GraphQLUnion)]
#[graphql(
  on ThingA<T> = Thing::resolve_a,
  on ThingB<T> = Thing::resolve_b,
  on ThingC<T> = Thing::resolve_c,
)]
enum Thing<T> {
  #[graphql(skip)]
  A(ThingA<T>),
  #[grapqhl(skip)]
  B(ThingB<T>),
  #[graphql(skip)]
  C(ThingC<T>),
}

This fails with:

error[E0401]: can't use generic parameters from outer item
   |
13 |     on ThingA<T> = Thing::resolve_a,           
   |               ^ use of generic parameter from outer item             
...                                                                                
18 | enum Thing<T> {                                                      
   |            - type parameter from outer item                          
   |                                                                               
   = note: a `const` is a separate item from the item that contains it   

I can only implement #[graphql_union] on traits, so that is not an option, and #[graphql_object] isn't appropriate here either.