Skip to content

Feature Request: Derivable custom input objects #1055

Open
@msterny

Description

@msterny

Is your feature request related to a problem? Please describe.
I want to be able to define a custom GraphQL input object for a struct/enum that cannot support derive(GraphQLInputObject). A clear way of doing this would be to automatically define a conversion between a Juniper-supported GraphQLInputObject and a Rust enum (which can be used to represent a discriminant union).

An Example
Given an enum FooBar that we want to define as a GraphQL input type:

enum FooBar
{ 
   Foo(i32),
   Bar(String),
}

Since GraphQL doesn’t support input unions as of this writing, the GraphQL schema would need to look like this:

input FooBar 
{
   Foo: Int
   Bar: String
}

This means that we’d need to create a separate FooBarInputObject, and validate/map into FooBar after the input has been parsed.

#[derive(GraphQLInputObject)]
#[graphql(name = "FooBar")]
struct FooBarInputObject {
   foo: Option<i32>,
   bar: Option<String>
}
impl From<FooBarInputObject> for FooBar {
   fn from(f: FooBarInputObject) -> Self {
      match (f.foo, f.bar) {
         (Some(foo), None)) => FooBar::Foo(foo),
         (None, Some(bar)) => FooBar::Bar(bar),
         _ => panic!("Bad input")
      }
   }
}

This works well enough for a very simple case, but we run into issues with reusable and composite input objects. Consider the case where FooBar is part of a struct that we want to define as an input object:

#[derive(GraphQLInputObject)] <=== Fails to compile!
struct FooBarBaz {
   foo_bar: FooBar,
   baz: String
}

Since FooBar cannot be an input object, neither can FooBarBaz. This would require us to define another struct just to be able to map FooBar. And this issue gets worse every-time we create another higher-level struct - if we want to nest FooBarBaz in another level, we have to create a third new-type!

Describe the solution you'd like
It would be useful to separate a GraphQLInputObject’s schema representation from its form in Rust. One way to do this is to support custom conversions for input types.

We could create a juniper::ConvertInput trait that can enable custom type conversions.

trait ConvertInput {
   type Input: FromInputValue;
   
   fn from(in: Self::Input) -> Result<Self>;
}

impl <__S, T> juniper::FromInputValue<__S> for T 
where T: juniper::ConvertInput {
   fn from_input_value(v: &juniper::InputValue<__S>) -> Option<Self> {
      let parsed: <Self as ConvertInput>::Input = juniper::FromInputValue::from_input_value(v);
      <T as juniper::ConvertInput>::from(parsed).ok()
   }
} 

Metadata

Metadata

Assignees

Labels

enhancementImprovement of existing features or bugfixk::apiRelated to API (application interface)

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions