Description
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()
}
}