(codegen) improve derive for InputObject
* Implement hack to allow usage in juniper crate * implement (default) attribute for Default::default() * Improve tests
This commit is contained in:
parent
45859bf405
commit
643875838d
3 changed files with 115 additions and 27 deletions
|
@ -9,6 +9,7 @@ use util::*;
|
|||
struct ObjAttrs {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
internal: bool,
|
||||
}
|
||||
|
||||
impl ObjAttrs {
|
||||
|
@ -26,6 +27,15 @@ impl ObjAttrs {
|
|||
res.description = Some(val);
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||
if ident == "_internal" {
|
||||
res.internal = true;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
panic!(format!(
|
||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||
item
|
||||
|
@ -40,7 +50,8 @@ impl ObjAttrs {
|
|||
struct ObjFieldAttrs {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
default: Option<String>,
|
||||
default: bool,
|
||||
default_expr: Option<String>,
|
||||
}
|
||||
|
||||
impl ObjFieldAttrs {
|
||||
|
@ -59,9 +70,18 @@ impl ObjFieldAttrs {
|
|||
continue;
|
||||
}
|
||||
if let Some(val) = keyed_item_value(item, "default", true) {
|
||||
res.default = Some(val);
|
||||
res.default_expr = Some(val);
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||
if ident == "default" {
|
||||
res.default = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
panic!(format!(
|
||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||
item
|
||||
|
@ -122,14 +142,20 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
None => quote!{ let field = field; },
|
||||
};
|
||||
|
||||
let default = match field_attrs.default {
|
||||
let default = {
|
||||
if field_attrs.default {
|
||||
Some(quote! { Default::default() } )
|
||||
} else {
|
||||
match field_attrs.default_expr {
|
||||
Some(ref def) => match syn::parse_token_trees(def) {
|
||||
Ok(t) => Some(quote!{ #(#t)* }),
|
||||
Ok(t) => Some(quote! { #(#t)* }),
|
||||
Err(_) => {
|
||||
panic!("#graphql(default = ?) must be a valid Rust expression inside a string");
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let create_meta_field = match default {
|
||||
|
@ -158,7 +184,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
let from_input_default = match default {
|
||||
Some(ref def) => {
|
||||
quote!{
|
||||
Some(&&::juniper::InputValue::Null) | None if true => #def,
|
||||
Some(&&_juniper::InputValue::Null) | None if true => #def,
|
||||
}
|
||||
}
|
||||
None => quote!{},
|
||||
|
@ -169,8 +195,11 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
// TODO: investigate the unwraps here, they seem dangerous!
|
||||
match obj.get(#name) {
|
||||
#from_input_default
|
||||
Some(v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
_ => ::juniper::FromInputValue::from_input_value(&::juniper::InputValue::null()).unwrap()
|
||||
Some(v) => _juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
_ => {
|
||||
_juniper::FromInputValue::from_input_value(&_juniper::InputValue::null())
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -183,8 +212,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
to_inputs.push(to_input);
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl ::juniper::GraphQLType for #ident {
|
||||
let body = quote! {
|
||||
impl _juniper::GraphQLType for #ident {
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
|
@ -192,7 +221,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(_: &(), registry: &mut ::juniper::Registry<'r>) -> ::juniper::meta::MetaType<'r> {
|
||||
fn meta<'r>(_: &(), registry: &mut _juniper::Registry<'r>) -> _juniper::meta::MetaType<'r> {
|
||||
let fields = &[
|
||||
#(#meta_fields)*
|
||||
];
|
||||
|
@ -202,8 +231,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::juniper::FromInputValue for #ident {
|
||||
fn from_input_value(value: &::juniper::InputValue) -> Option<#ident> {
|
||||
impl _juniper::FromInputValue for #ident {
|
||||
fn from_input_value(value: &_juniper::InputValue) -> Option<#ident> {
|
||||
if let Some(obj) = value.to_object_value() {
|
||||
let item = #ident {
|
||||
#(#from_inputs)*
|
||||
|
@ -216,12 +245,46 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> ::juniper::InputValue {
|
||||
::juniper::InputValue::object(vec![
|
||||
impl _juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> _juniper::InputValue {
|
||||
_juniper::InputValue::object(vec![
|
||||
#(#to_inputs)*
|
||||
].into_iter().collect())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let dummy_const = Ident::new(format!("_IMPL_GRAPHQLINPUTOBJECT_FOR_{}", ident));
|
||||
|
||||
// This ugly hack makes it possible to use the derive inside juniper itself.
|
||||
// FIXME: Figure out a better way to do this!
|
||||
let crate_reference = if attrs.internal {
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
mod _juniper {
|
||||
pub use ::{
|
||||
InputValue,
|
||||
FromInputValue,
|
||||
GraphQLType,
|
||||
Registry,
|
||||
meta,
|
||||
ToInputValue
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
extern crate juniper as _juniper;
|
||||
}
|
||||
};
|
||||
let generated = quote! {
|
||||
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
|
||||
#[doc(hidden)]
|
||||
const #dummy_const : () = {
|
||||
#crate_reference
|
||||
#body
|
||||
};
|
||||
};
|
||||
|
||||
generated
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use fnv::FnvHashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
use juniper::{self, FromInputValue, GraphQLType, ToInputValue};
|
||||
use juniper::{self, FromInputValue, GraphQLType, InputValue};
|
||||
|
||||
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||
#[graphql(name = "MyInput", description = "input descr")]
|
||||
|
@ -10,6 +10,9 @@ struct Input {
|
|||
regular_field: String,
|
||||
#[graphql(name = "haha", default = "33", description = "haha descr")]
|
||||
c: i32,
|
||||
|
||||
#[graphql(default)]
|
||||
other: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -22,10 +25,31 @@ fn test_derived_input_object() {
|
|||
assert_eq!(meta.name(), Some("MyInput"));
|
||||
assert_eq!(meta.description(), Some(&"input descr".to_string()));
|
||||
|
||||
let obj = Input {
|
||||
regular_field: "a".to_string(),
|
||||
// Test default value injection.
|
||||
|
||||
let input_no_defaults: InputValue = ::serde_json::from_value(json!({
|
||||
"regularField": "a",
|
||||
})).unwrap();
|
||||
|
||||
let output_no_defaults: Input = FromInputValue::from_input_value(&input_no_defaults).unwrap();
|
||||
assert_eq!(output_no_defaults, Input{
|
||||
regular_field: "a".into(),
|
||||
c: 33,
|
||||
};
|
||||
let restored: Input = FromInputValue::from_input_value(&obj.to_input_value()).unwrap();
|
||||
assert_eq!(obj, restored);
|
||||
other: None,
|
||||
});
|
||||
|
||||
// Test with all values supplied.
|
||||
|
||||
let input: InputValue = ::serde_json::from_value(json!({
|
||||
"regularField": "a",
|
||||
"haha": 55,
|
||||
"other": true,
|
||||
})).unwrap();
|
||||
|
||||
let output: Input = FromInputValue::from_input_value(&input).unwrap();
|
||||
assert_eq!(output, Input{
|
||||
regular_field: "a".into(),
|
||||
c: 55,
|
||||
other: Some(true),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate juniper;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue