From 643875838dd5d0b589240f5f467c0f4c3792dc23 Mon Sep 17 00:00:00 2001 From: theduke Date: Sat, 2 Dec 2017 15:02:17 +0100 Subject: [PATCH] (codegen) improve derive for InputObject * Implement hack to allow usage in juniper crate * implement (default) attribute for Default::default() * Improve tests --- juniper_codegen/src/derive_input_object.rs | 105 ++++++++++++++---- .../src/codegen/derive_input_object.rs | 36 +++++- juniper_tests/src/lib.rs | 1 + 3 files changed, 115 insertions(+), 27 deletions(-) diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs index 469be09b..47f5018d 100644 --- a/juniper_codegen/src/derive_input_object.rs +++ b/juniper_codegen/src/derive_input_object.rs @@ -9,6 +9,7 @@ use util::*; struct ObjAttrs { name: Option, description: Option, + 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, description: Option, - default: Option, + default: bool, + default_expr: Option, } 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 { - Some(ref def) => match syn::parse_token_trees(def) { - Ok(t) => Some(quote!{ #(#t)* }), - Err(_) => { - panic!("#graphql(default = ?) must be a valid Rust expression inside a string"); + 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)* }), + Err(_) => { + panic!("#graphql(default = ?) must be a valid Rust expression inside a string"); + } + }, + None => None, } - }, - 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 } diff --git a/juniper_tests/src/codegen/derive_input_object.rs b/juniper_tests/src/codegen/derive_input_object.rs index 57c24556..caed94dd 100644 --- a/juniper_tests/src/codegen/derive_input_object.rs +++ b/juniper_tests/src/codegen/derive_input_object.rs @@ -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, } #[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), + }); } diff --git a/juniper_tests/src/lib.rs b/juniper_tests/src/lib.rs index c4fc104c..61f980e7 100644 --- a/juniper_tests/src/lib.rs +++ b/juniper_tests/src/lib.rs @@ -1,5 +1,6 @@ #[macro_use] extern crate juniper; +#[macro_use] extern crate serde_json; #[cfg(test)]