From 0b07dbe99a3a334f087739a0770244beacc1078a Mon Sep 17 00:00:00 2001 From: Magnus Hallin Date: Sun, 19 Feb 2017 12:08:52 +0100 Subject: [PATCH] Add support for default values in input objects --- src/executor.rs | 23 +++++++++++++- src/executor_tests/variables.rs | 54 ++++++++++++++++++++++++++++++++ src/macros/input_object.rs | 53 ++++++++++++++++++++++++------- src/macros/tests/input_object.rs | 48 ++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 13 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index 1f36b7ba..9715f704 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -330,13 +330,34 @@ pub fn execute_validated_query<'a, QueryT, MutationT, CtxT>( None => return Err(GraphQLError::UnknownOperationName), }; + let default_variable_values = op.item.variable_definitions + .map(|defs| defs.item.items.iter().filter_map( + |&(ref name, ref def)| def.default_value.as_ref().map( + |i| (name.item.to_owned(), i.item.clone()))) + .collect::>()); + let errors = RwLock::new(Vec::new()); let value; { + let mut all_vars; + let mut final_vars = variables; + + if let Some(defaults) = default_variable_values { + all_vars = variables.clone(); + + for (name, value) in defaults { + if !all_vars.contains_key(&name) { + all_vars.insert(name, value); + } + } + + final_vars = &all_vars; + } + let executor = Executor { fragments: &fragments.iter().map(|f| (f.item.name.item, &f.item)).collect(), - variables: variables, + variables: final_vars, current_selection_set: Some(&op.item.selection_set[..]), schema: &root_node.schema, context: context, diff --git a/src/executor_tests/variables.rs b/src/executor_tests/variables.rs index bff812ab..5ec1b6b1 100644 --- a/src/executor_tests/variables.rs +++ b/src/executor_tests/variables.rs @@ -57,6 +57,13 @@ graphql_input_object!( } ); +graphql_input_object!( + #[derive(Debug)] + struct InputWithDefaults { + a = 123: i64, + } +); + graphql_object!(TestType: () |&self| { field field_with_object_input(input: Option) -> String { format!("{:?}", input) @@ -97,6 +104,10 @@ graphql_object!(TestType: () |&self| { field example_input(arg: ExampleInputObject) -> String { format!("a: {:?}, b: {:?}", arg.a, arg.b) } + + field input_with_defaults(arg: InputWithDefaults) -> String { + format!("a: {:?}", arg.a) + } }); fn run_variable_query(query: &str, vars: Variables, f: F) @@ -891,3 +902,46 @@ fn does_not_allow_null_variable_for_required_field() { ), ])); } + +#[test] +fn input_object_with_default_values() { + run_query( + r#"{ inputWithDefaults(arg: {a: 1}) }"#, + |result| { + assert_eq!( + result.get("inputWithDefaults"), + Some(&Value::string(r#"a: 1"#))); + }); + + run_variable_query( + r#"query q($var: Int!) { inputWithDefaults(arg: {a: $var}) }"#, + vec![ + ("var".to_owned(), InputValue::int(1)), + ].into_iter().collect(), + |result| { + assert_eq!( + result.get("inputWithDefaults"), + Some(&Value::string(r#"a: 1"#))); + }); + + run_variable_query( + r#"query q($var: Int = 1) { inputWithDefaults(arg: {a: $var}) }"#, + vec![ + ].into_iter().collect(), + |result| { + assert_eq!( + result.get("inputWithDefaults"), + Some(&Value::string(r#"a: 1"#))); + }); + + run_variable_query( + r#"query q($var: Int = 1) { inputWithDefaults(arg: {a: $var}) }"#, + vec![ + ("var".to_owned(), InputValue::int(2)), + ].into_iter().collect(), + |result| { + assert_eq!( + result.get("inputWithDefaults"), + Some(&Value::string(r#"a: 2"#))); + }); +} diff --git a/src/macros/input_object.rs b/src/macros/input_object.rs index a7b8067d..c596e482 100644 --- a/src/macros/input_object.rs +++ b/src/macros/input_object.rs @@ -45,17 +45,19 @@ macro_rules! graphql_input_object { ( @generate_from_input_value, $name:tt, $var:tt, - ( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* ) + ( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* ) ) => { Some($name { $( $field_name: { let n: String = $crate::to_camel_case(stringify!($field_name)); let v: Option<&&$crate::InputValue> = $var.get(&n[..]); - if let Some(v) = v { - $crate::FromInputValue::from(v).unwrap() - } else { - $crate::FromInputValue::from(&$crate::InputValue::null()).unwrap() + println!("Found variable for {:?}: {:?} in {:?}", n, v, $var); + + match v { + $( Some(&&InputValue::Null) | None if true => $default, )* + Some(v) => $crate::FromInputValue::from(v).unwrap(), + _ => $crate::FromInputValue::from(&$crate::InputValue::null()).unwrap() } } ),* }) @@ -65,26 +67,53 @@ macro_rules! graphql_input_object { ( @generate_struct_fields, ( $($meta:tt)* ), ( $($pubmod:tt)* ), $name:tt, - ( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* ) + ( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* ) ) => { $($meta)* $($pubmod)* struct $name { $( $field_name: $field_type, )* } }; - // Generate the input field meta list, i.e. &[Argument]. + // Generate single field meta for field with default value + ( + @generate_single_meta_field, + $reg:tt, + ( $field_name:ident = $default:tt : $field_type:ty $(as $descr:tt)* ) + ) => { + graphql_input_object!( + @apply_description, + $($descr)*, + $reg.arg_with_default::<$field_type>( + &$crate::to_camel_case(stringify!($field_name)), + &$default)) + }; + + // Generate single field meta for field without default value + ( + @generate_single_meta_field, + $reg:tt, + ( $field_name:ident : $field_type:ty $(as $descr:tt)* ) + ) => { + graphql_input_object!( + @apply_description, + $($descr)*, + $reg.arg::<$field_type>( + &$crate::to_camel_case(stringify!($field_name)))) + }; + + // Generate the input field meta list, i.e. &[Argument] for ( @generate_meta_fields, $reg:tt, - ( $($field_name:ident : $field_type:ty $(as $descr:tt)* $(,)* ),* ) + ( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* ) ) => { &[ $( graphql_input_object!( - @apply_description, - $($descr)*, - $reg.arg::<$field_type>( - &$crate::to_camel_case(stringify!($field_name)))) + @generate_single_meta_field, + $reg, + ( $field_name $(= $default)* : $field_type $(as $descr)* ) + ) ),* ] }; diff --git a/src/macros/tests/input_object.rs b/src/macros/tests/input_object.rs index 30ee606f..30596eb5 100644 --- a/src/macros/tests/input_object.rs +++ b/src/macros/tests/input_object.rs @@ -78,6 +78,13 @@ graphql_input_object!( } ); +graphql_input_object!( + struct FieldWithDefaults { + field_one = 123: i64, + field_two = 456: i64 as "The second field", + } +); + graphql_object!(Root: () |&self| { field test_field( a1: DefaultName, @@ -90,6 +97,7 @@ graphql_object!(Root: () |&self| { a8: PublicWithDescription, a9: NamedPublicWithDescription, a10: NamedPublic, + a11: FieldWithDefaults, ) -> i64 { 0 } @@ -414,3 +422,43 @@ fn field_description_introspection() { ].into_iter().collect()))); }); } + +#[test] +fn field_with_defaults_introspection() { + let doc = r#" + { + __type(name: "FieldWithDefaults") { + name + inputFields { + name + type { + name + } + defaultValue + } + } + } + "#; + + run_type_info_query(doc, |type_info, fields| { + assert_eq!(type_info.get("name"), Some(&Value::string("FieldWithDefaults"))); + + assert_eq!(fields.len(), 2); + + assert!(fields.contains(&Value::object(vec![ + ("name", Value::string("fieldOne")), + ("type", Value::object(vec![ + ("name", Value::string("Int")), + ].into_iter().collect())), + ("defaultValue", Value::string("123")), + ].into_iter().collect()))); + + assert!(fields.contains(&Value::object(vec![ + ("name", Value::string("fieldTwo")), + ("type", Value::object(vec![ + ("name", Value::string("Int")), + ].into_iter().collect())), + ("defaultValue", Value::string("456")), + ].into_iter().collect()))); + }); +}