From 3c8cf55da571b3a5ff0207ee91ac0f6f4ef32c09 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 20 Oct 2019 17:29:38 +0200 Subject: [PATCH 01/12] Add failing test --- .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/proc_macro_param_attrs.rs | 214 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 498258bd7..868873f2c 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,3 +4,4 @@ mod derive_enum; mod derive_input_object; mod derive_object; mod scalar_value_transparent; +mod proc_macro_param_attrs; diff --git a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs new file mode 100644 index 000000000..d95ddf70a --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs @@ -0,0 +1,214 @@ +use juniper::*; +use serde_json::{self, Value}; + +struct Query; + +#[juniper::object] +impl Query { + #[graphql(arguments( + arg1(default = true, description = "arg1 desc"), + arg2(default = false, description = "arg2 desc"), + ))] + fn field_old_attrs(arg1: bool, arg2: bool) -> bool { + arg1 && arg2 + } + + fn field_new_attrs( + #[graphql(default = true, description = "arg1 desc")] arg1: bool, + #[graphql(default = false, description = "arg2 desc")] arg2: bool, + ) -> bool { + arg1 && arg2 + } +} + +// The query that GraphiQL runs to inspect the schema +static SCHEMA_INTROSPECTION_QUERY: &str = r#" + query IntrospectionQuery { + __schema { + types { + ...FullType + } + } + } + + fragment FullType on __Type { + name + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + } + } + + fragment InputValue on __InputValue { + name + description + defaultValue + } +"#; + +// TODO: Test for `rename` attr + +#[test] +fn descriptions_applied_correctly() { + let schema = introspect_schema(); + + let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); + + // old deprecated `#[graphql(arguments(...))]` style + { + let field = query + .fields + .iter() + .find(|field| field.name == "fieldOldAttrs") + .unwrap(); + + let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); + assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); + assert_eq!( + &arg1.default_value, + &Some(Value::String("true".to_string())) + ); + + let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); + assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); + assert_eq!( + &arg2.default_value, + &Some(Value::String("false".to_string())) + ); + } + + // new style with attrs directly on the args + { + let field = query + .fields + .iter() + .find(|field| field.name == "fieldNewAttrs") + .unwrap(); + + let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); + assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); + assert_eq!( + &arg1.default_value, + &Some(Value::String("true".to_string())) + ); + + let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); + assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); + assert_eq!( + &arg2.default_value, + &Some(Value::String("false".to_string())) + ); + } +} + +#[derive(Debug)] +struct Schema { + types: Vec, +} + +#[derive(Debug)] +struct Type { + name: String, + fields: Vec, +} + +#[derive(Debug)] +struct Field { + name: String, + args: Vec, + description: Option, +} + +#[derive(Debug)] +struct Arg { + name: String, + description: Option, + default_value: Option, +} + +fn introspect_schema() -> Schema { + let (value, _errors) = juniper::execute( + SCHEMA_INTROSPECTION_QUERY, + None, + &RootNode::new(Query, juniper::EmptyMutation::new()), + &Variables::new(), + &(), + ) + .unwrap(); + + let value: Value = serde_json::from_str(&serde_json::to_string(&value).unwrap()).unwrap(); + + let types = value["__schema"]["types"] + .as_array() + .unwrap() + .iter() + .map(parse_type) + .collect::>(); + + Schema { types } +} + +fn parse_type(value: &Value) -> Type { + let name = value["name"].as_str().unwrap().to_string(); + + let fields = if value["fields"].is_null() { + vec![] + } else { + value["fields"] + .as_array() + .unwrap() + .iter() + .map(parse_field) + .collect::>() + }; + + Type { name, fields } +} + +fn parse_field(value: &Value) -> Field { + let name = value["name"].as_str().unwrap().to_string(); + + let description = if value["description"].is_null() { + None + } else { + Some(value["description"].as_str().unwrap().to_string()) + }; + + let args = value["args"] + .as_array() + .unwrap() + .iter() + .map(parse_arg) + .collect::>(); + + Field { + name, + description, + args, + } +} + +fn parse_arg(value: &Value) -> Arg { + let name = value["name"].as_str().unwrap().to_string(); + + let description = if value["description"].is_null() { + None + } else { + Some(value["description"].as_str().unwrap().to_string()) + }; + + let default_value = if value["defaultValue"].is_null() { + None + } else { + Some(value["defaultValue"].clone()) + }; + + Arg { + name, + description, + default_value, + } +} From 5a3cccfd7328bbf77b5e1a0901a22d4ecc67d2e4 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 21 Oct 2019 22:35:48 +0200 Subject: [PATCH 02/12] Support customizing proc macro args with attrs directly on the args Fixes #421 --- .../src/codegen/proc_macro_param_attrs.rs | 90 +++++++++---------- juniper_codegen/Cargo.toml | 1 + juniper_codegen/src/impl_object.rs | 62 ++++++++++++- juniper_codegen/src/lib.rs | 36 ++++---- juniper_codegen/src/util.rs | 47 ++++++---- 5 files changed, 152 insertions(+), 84 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs index d95ddf70a..5a43d40a3 100644 --- a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs +++ b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs @@ -52,56 +52,56 @@ static SCHEMA_INTROSPECTION_QUERY: &str = r#" // TODO: Test for `rename` attr #[test] -fn descriptions_applied_correctly() { +fn old_descriptions_applied_correctly() { let schema = introspect_schema(); - let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); // old deprecated `#[graphql(arguments(...))]` style - { - let field = query - .fields - .iter() - .find(|field| field.name == "fieldOldAttrs") - .unwrap(); - - let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); - assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); - assert_eq!( - &arg1.default_value, - &Some(Value::String("true".to_string())) - ); - - let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); - assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); - assert_eq!( - &arg2.default_value, - &Some(Value::String("false".to_string())) - ); - } + let field = query + .fields + .iter() + .find(|field| field.name == "fieldOldAttrs") + .unwrap(); + + let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); + assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); + assert_eq!( + &arg1.default_value, + &Some(Value::String("true".to_string())) + ); + + let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); + assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); + assert_eq!( + &arg2.default_value, + &Some(Value::String("false".to_string())) + ); +} - // new style with attrs directly on the args - { - let field = query - .fields - .iter() - .find(|field| field.name == "fieldNewAttrs") - .unwrap(); - - let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); - assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); - assert_eq!( - &arg1.default_value, - &Some(Value::String("true".to_string())) - ); - - let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); - assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); - assert_eq!( - &arg2.default_value, - &Some(Value::String("false".to_string())) - ); - } +#[test] +fn new_descriptions_applied_correctly() { + let schema = introspect_schema(); + let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); + + let field = query + .fields + .iter() + .find(|field| field.name == "fieldNewAttrs") + .unwrap(); + + let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); + assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); + assert_eq!( + &arg1.default_value, + &Some(Value::String("true".to_string())) + ); + + let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); + assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); + assert_eq!( + &arg2.default_value, + &Some(Value::String("false".to_string())) + ); } #[derive(Debug)] diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index fe4ed6ae1..aaaf0d410 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -18,6 +18,7 @@ proc-macro = true proc-macro2 = "1.0.1" syn = { version = "1.0.3", features = ["full", "extra-traits", "parsing"] } quote = "1.0.2" +proc-macro-error = "0.3.4" [dev-dependencies] juniper = { version = "0.14.0", path = "../juniper" } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 12af7f414..3c0df1292 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -1,6 +1,8 @@ use crate::util; use proc_macro::TokenStream; +use proc_macro_error::*; use quote::quote; +use syn::spanned::Spanned; /// Generate code for the juniper::object macro. pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { @@ -101,7 +103,7 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> } }; - let attrs = match util::FieldAttributes::from_attrs( + let mut attrs = match util::FieldAttributes::from_attrs( method.attrs, util::FieldAttributeParseMode::Impl, ) { @@ -112,6 +114,16 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> ), }; + if !attrs.arguments.is_empty() { + let deprecation_warning = vec![ + "Setting arguments via #[graphql(arguments(...))] on the method", + "is deprecrated. Instead use #[graphql(...)] as attributes directly", + "on the arguments themselves.", + ] + .join(" "); + eprintln!("{}", deprecation_warning); + } + let mut args = Vec::new(); let mut resolve_parts = Vec::new(); @@ -126,6 +138,12 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> } } syn::FnArg::Typed(ref captured) => { + if let Some(field_arg) = parse_argument_attrs(&captured) { + attrs + .arguments + .insert(field_arg.name.to_string(), field_arg); + } + let (arg_ident, is_mut) = match &*captured.pat { syn::Pat::Ident(ref pat_ident) => { (&pat_ident.ident, pat_ident.mutability.is_some()) @@ -224,3 +242,45 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; definition.into_tokens(juniper_crate_name).into() } + +fn parse_argument_attrs(pat: &syn::PatType) -> Option { + let graphql_attrs = pat + .attrs + .iter() + .filter(|attr| { + let name = attr.path.get_ident().map(|i| i.to_string()); + name == Some("graphql".to_string()) + }) + .collect::>(); + + let graphql_attr = match graphql_attrs.len() { + 0 => return None, + 1 => &graphql_attrs[0], + _ => { + let last_attr = graphql_attrs.last().unwrap(); + abort!( + last_attr.span(), + "You cannot have multiple #[graphql] attributes on the same arg" + ); + } + }; + + let name = match &*pat.pat { + syn::Pat::Ident(i) => &i.ident, + other => unimplemented!("{:?}", other), + }; + + let mut arg = util::FieldAttributeArgument { + name: name.to_owned(), + default: None, + description: None, + }; + + graphql_attr + .parse_args_with(|content: syn::parse::ParseStream| { + util::parse_field_attr_arg_contents(&content, &mut arg) + }) + .unwrap_or_else(|err| abort!(err.span(), "{}", err)); + + Some(arg) +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index d977fe027..60666d74c 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -16,6 +16,7 @@ mod derive_scalar_value; mod impl_object; mod util; +use proc_macro_error::*; use proc_macro::TokenStream; #[proc_macro_derive(GraphQLEnum, attributes(graphql))] @@ -289,25 +290,21 @@ impl InternalQuery { fn deprecated_field_simple() -> bool { true } - // Customizing field arguments is a little awkward right now. - // This will improve once [RFC 2564](https://github.com/rust-lang/rust/issues/60406) - // is implemented, which will allow attributes on function parameters. - - #[graphql( - arguments( - arg1( - // You can specify default values. - // A default can be any valid expression that yields the right type. - default = true, - description = "Argument description....", - ), - arg2( - default = false, - description = "arg2 description...", - ), - ), - )] - fn args(arg1: bool, arg2: bool) -> bool { + // Customizing field arguments can be done like so: + // Note that attributes on arguments requires Rust 1.39 + fn args( + #[graphql( + // You can specify default values. + // A default can be any valid expression that yields the right type. + default = true, + description = "Argument description....", + )] arg1: bool, + + #[graphql( + default = false, + description = "arg2 description...", + )] arg2: bool, + ) -> bool { arg1 && arg2 } } @@ -354,6 +351,7 @@ impl Query { */ #[proc_macro_attribute] +#[proc_macro_error] pub fn object(args: TokenStream, input: TokenStream) -> TokenStream { let gen = impl_object::build_object(args, input, false); gen.into() diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index d11f8788d..68bae2335 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -392,7 +392,7 @@ pub struct FieldAttributeArgument { impl parse::Parse for FieldAttributeArgument { fn parse(input: parse::ParseStream) -> parse::Result { - let name = input.parse()?; + let name = input.parse::()?; let mut arg = Self { name, @@ -402,28 +402,37 @@ impl parse::Parse for FieldAttributeArgument { let content; syn::parenthesized!(content in input); - while !content.is_empty() { - let name = content.parse::()?; - content.parse::()?; + parse_field_attr_arg_contents(&content, &mut arg)?; - match name.to_string().as_str() { - "description" => { - arg.description = Some(content.parse()?); - } - "default" => { - arg.default = Some(content.parse()?); - } - other => { - return Err(content.error(format!("Invalid attribute argument key {}", other))); - } - } + Ok(arg) + } +} - // Discard trailing comma. - content.parse::().ok(); +pub fn parse_field_attr_arg_contents( + content: syn::parse::ParseStream, + arg: &mut FieldAttributeArgument, +) -> parse::Result<()> { + while !content.is_empty() { + let name = content.parse::()?; + content.parse::()?; + + match name.to_string().as_str() { + "description" => { + arg.description = Some(content.parse()?); + } + "default" => { + arg.default = Some(content.parse()?); + } + other => { + return Err(content.error(format!("Invalid attribute argument key `{}`", other))); + } } - Ok(arg) + // Discard trailing comma. + content.parse::().ok(); } + + Ok(()) } #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -493,7 +502,7 @@ impl parse::Parse for FieldAttribute { } } -#[derive(Default)] +#[derive(Default, Debug)] pub struct FieldAttributes { pub name: Option, pub description: Option, From 1239eb7524e739c9b5a87a928385610c37cc2cf7 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 21 Oct 2019 22:46:04 +0200 Subject: [PATCH 03/12] Improve error message --- juniper_codegen/src/impl_object.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 3c0df1292..a1c6f71e1 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -267,7 +267,9 @@ fn parse_argument_attrs(pat: &syn::PatType) -> Option &i.ident, - other => unimplemented!("{:?}", other), + other => { + abort!(other.span(), "Invalid token for function argument") + } }; let mut arg = util::FieldAttributeArgument { From 51df5b9c2210e5b6297f8135586219f00f4158ca Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 21 Oct 2019 22:47:57 +0200 Subject: [PATCH 04/12] Remove needless comments --- .../juniper_tests/src/codegen/proc_macro_param_attrs.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs index 5a43d40a3..9e8e5d62a 100644 --- a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs +++ b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs @@ -49,14 +49,11 @@ static SCHEMA_INTROSPECTION_QUERY: &str = r#" } "#; -// TODO: Test for `rename` attr - #[test] fn old_descriptions_applied_correctly() { let schema = introspect_schema(); let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); - // old deprecated `#[graphql(arguments(...))]` style let field = query .fields .iter() From eee37842c396af9447b0aac1c5c6a61a2b2a77a4 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 21 Oct 2019 22:59:16 +0200 Subject: [PATCH 05/12] Format --- .../juniper_tests/src/codegen/mod.rs | 2 +- juniper/src/executor_tests/variables.rs | 98 ++++++++++++------- juniper/src/http/graphiql.rs | 6 +- juniper/src/integrations/serde.rs | 3 +- juniper/src/validation/rules/scalar_leafs.rs | 3 +- juniper_codegen/src/impl_object.rs | 4 +- juniper_codegen/src/lib.rs | 2 +- 7 files changed, 73 insertions(+), 45 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 868873f2c..5b2cdf291 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,5 +3,5 @@ mod util; mod derive_enum; mod derive_input_object; mod derive_object; -mod scalar_value_transparent; mod proc_macro_param_attrs; +mod scalar_value_transparent; diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 8024588a0..cce48291d 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -158,7 +158,10 @@ fn inline_complex_input() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# + )) + ); }, ); } @@ -170,7 +173,10 @@ fn inline_parse_single_value_to_list() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# + )) + ); }, ); } @@ -182,7 +188,10 @@ fn inline_runs_from_input_value_on_scalar() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"# + )) + ); }, ); } @@ -208,7 +217,10 @@ fn variable_complex_input() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# + )) + ); }, ); } @@ -234,7 +246,10 @@ fn variable_parse_single_value_to_list() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"# + )) + ); }, ); } @@ -259,7 +274,10 @@ fn variable_runs_from_input_value_on_scalar() { |result: &Object| { assert_eq!( result.get_field_value("fieldWithObjectInput"), - Some(&Value::scalar(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#))); + Some(&Value::scalar( + r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"# + )) + ); }, ); } @@ -306,12 +324,13 @@ fn variable_error_on_incorrect_type() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( + assert_eq!( + error, + ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. Expected "TestInputObject", found not an object."#, &[SourcePosition::new(8, 0, 8)], - ), - ])); + ),]) + ); } #[test] @@ -366,16 +385,19 @@ fn variable_multiple_errors_with_nesting() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( - r#"Variable "$input" got invalid value. In field "na": In field "c": Expected "String!", found null."#, - &[SourcePosition::new(8, 0, 8)], - ), - RuleError::new( - r#"Variable "$input" got invalid value. In field "nb": Expected "String!", found null."#, - &[SourcePosition::new(8, 0, 8)], - ), - ])); + assert_eq!( + error, + ValidationError(vec![ + RuleError::new( + r#"Variable "$input" got invalid value. In field "na": In field "c": Expected "String!", found null."#, + &[SourcePosition::new(8, 0, 8)], + ), + RuleError::new( + r#"Variable "$input" got invalid value. In field "nb": Expected "String!", found null."#, + &[SourcePosition::new(8, 0, 8)], + ), + ]) + ); } #[test] @@ -733,12 +755,13 @@ fn does_not_allow_lists_of_non_null_to_contain_null() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( + assert_eq!( + error, + ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], - ), - ])); + ),]) + ); } #[test] @@ -759,12 +782,13 @@ fn does_not_allow_non_null_lists_of_non_null_to_contain_null() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( + assert_eq!( + error, + ValidationError(vec![RuleError::new( r#"Variable "$input" got invalid value. In element #1: Expected "String!", found null."#, &[SourcePosition::new(8, 0, 8)], - ), - ])); + ),]) + ); } #[test] @@ -820,12 +844,13 @@ fn does_not_allow_invalid_types_to_be_used_as_values() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( + assert_eq!( + error, + ValidationError(vec![RuleError::new( r#"Variable "$input" expected value of type "TestType!" which cannot be used as an input type."#, &[SourcePosition::new(8, 0, 8)], - ), - ])); + ),]) + ); } #[test] @@ -842,12 +867,13 @@ fn does_not_allow_unknown_types_to_be_used_as_values() { let error = crate::execute(query, None, &schema, &vars, &()).unwrap_err(); - assert_eq!(error, ValidationError(vec![ - RuleError::new( + assert_eq!( + error, + ValidationError(vec![RuleError::new( r#"Variable "$input" expected value of type "UnknownType!" which cannot be used as an input type."#, &[SourcePosition::new(8, 0, 8)], - ), - ])); + ),]) + ); } #[test] diff --git a/juniper/src/http/graphiql.rs b/juniper/src/http/graphiql.rs index 590909cb2..7b9e1ebe3 100644 --- a/juniper/src/http/graphiql.rs +++ b/juniper/src/http/graphiql.rs @@ -41,7 +41,8 @@ pub fn graphiql_source(graphql_endpoint_url: &str) -> String { "#; - format!(r#" + format!( + r#" @@ -62,5 +63,6 @@ pub fn graphiql_source(graphql_endpoint_url: &str) -> String { "#, graphql_url = graphql_endpoint_url, stylesheet_source = stylesheet_source, - fetcher_source = fetcher_source) + fetcher_source = fetcher_source + ) } diff --git a/juniper/src/integrations/serde.rs b/juniper/src/integrations/serde.rs index c91c52629..97b465096 100644 --- a/juniper/src/integrations/serde.rs +++ b/juniper/src/integrations/serde.rs @@ -450,7 +450,8 @@ mod tests { to_string(&ExecutionError::at_origin(FieldError::new( "foo error", Value::Object(obj), - ))).unwrap(), + ))) + .unwrap(), r#"{"message":"foo error","locations":[{"line":1,"column":1}],"path":[],"extensions":{"foo":"bar"}}"# ); } diff --git a/juniper/src/validation/rules/scalar_leafs.rs b/juniper/src/validation/rules/scalar_leafs.rs index 55f6a47fb..fe5ac7778 100644 --- a/juniper/src/validation/rules/scalar_leafs.rs +++ b/juniper/src/validation/rules/scalar_leafs.rs @@ -52,7 +52,8 @@ fn no_allowed_error_message(field_name: &str, type_name: &str) -> String { fn required_error_message(field_name: &str, type_name: &str) -> String { format!( r#"Field "{}" of type "{}" must have a selection of subfields. Did you mean "{} {{ ... }}"?"#, - field_name, type_name, field_name) + field_name, type_name, field_name + ) } #[cfg(test)] diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index a1c6f71e1..b152c81a1 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -267,9 +267,7 @@ fn parse_argument_attrs(pat: &syn::PatType) -> Option &i.ident, - other => { - abort!(other.span(), "Invalid token for function argument") - } + other => abort!(other.span(), "Invalid token for function argument"), }; let mut arg = util::FieldAttributeArgument { diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 60666d74c..cff9f1bf3 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -16,8 +16,8 @@ mod derive_scalar_value; mod impl_object; mod util; -use proc_macro_error::*; use proc_macro::TokenStream; +use proc_macro_error::*; #[proc_macro_derive(GraphQLEnum, attributes(graphql))] pub fn derive_enum(input: TokenStream) -> TokenStream { From 5a024cf144aec4c8d2c749c3e184ea828eabcb9c Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 26 Oct 2019 11:46:01 +0200 Subject: [PATCH 06/12] Remove support for `#[graphql(arguments(...))]` attribute And update all existing code --- .../content/types/objects/complex_fields.md | 20 +- .../juniper_tests/src/codegen/mod.rs | 1 - .../src/codegen/proc_macro_param_attrs.rs | 211 ------------------ .../src/executor_tests/introspection/mod.rs | 10 +- juniper/src/executor_tests/variables.rs | 11 +- juniper/src/macros/tests/args.rs | 91 ++++---- juniper/src/macros/tests/impl_object.rs | 8 +- juniper/src/schema/schema.rs | 12 +- juniper/src/tests/schema.rs | 23 +- juniper_codegen/src/impl_object.rs | 61 +---- juniper_codegen/src/lib.rs | 1 + juniper_codegen/src/util.rs | 75 ++++--- 12 files changed, 127 insertions(+), 397 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs diff --git a/docs/book/content/types/objects/complex_fields.md b/docs/book/content/types/objects/complex_fields.md index 902b442ee..f72147183 100644 --- a/docs/book/content/types/objects/complex_fields.md +++ b/docs/book/content/types/objects/complex_fields.md @@ -127,21 +127,11 @@ struct Person {} #[juniper::object] impl Person { - #[graphql( - arguments( - arg1( - // Set a default value which will be injected if not present. - // The default can be any valid Rust expression, including a function call, etc. - default = true, - // Set a description. - description = "The first argument..." - ), - arg2( - default = 0, - ) - ) - )] - fn field1(&self, arg1: bool, arg2: i32) -> String { + fn field1( + &self, + #[graphql(default = true, description = "The first argument...")] arg1: bool, + #[graphql(default = 0)] arg2: i32, + ) -> String { format!("{} {}", arg1, arg2) } } diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 5b2cdf291..498258bd7 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -3,5 +3,4 @@ mod util; mod derive_enum; mod derive_input_object; mod derive_object; -mod proc_macro_param_attrs; mod scalar_value_transparent; diff --git a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs b/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs deleted file mode 100644 index 9e8e5d62a..000000000 --- a/integration_tests/juniper_tests/src/codegen/proc_macro_param_attrs.rs +++ /dev/null @@ -1,211 +0,0 @@ -use juniper::*; -use serde_json::{self, Value}; - -struct Query; - -#[juniper::object] -impl Query { - #[graphql(arguments( - arg1(default = true, description = "arg1 desc"), - arg2(default = false, description = "arg2 desc"), - ))] - fn field_old_attrs(arg1: bool, arg2: bool) -> bool { - arg1 && arg2 - } - - fn field_new_attrs( - #[graphql(default = true, description = "arg1 desc")] arg1: bool, - #[graphql(default = false, description = "arg2 desc")] arg2: bool, - ) -> bool { - arg1 && arg2 - } -} - -// The query that GraphiQL runs to inspect the schema -static SCHEMA_INTROSPECTION_QUERY: &str = r#" - query IntrospectionQuery { - __schema { - types { - ...FullType - } - } - } - - fragment FullType on __Type { - name - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - } - } - - fragment InputValue on __InputValue { - name - description - defaultValue - } -"#; - -#[test] -fn old_descriptions_applied_correctly() { - let schema = introspect_schema(); - let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); - - let field = query - .fields - .iter() - .find(|field| field.name == "fieldOldAttrs") - .unwrap(); - - let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); - assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); - assert_eq!( - &arg1.default_value, - &Some(Value::String("true".to_string())) - ); - - let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); - assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); - assert_eq!( - &arg2.default_value, - &Some(Value::String("false".to_string())) - ); -} - -#[test] -fn new_descriptions_applied_correctly() { - let schema = introspect_schema(); - let query = schema.types.iter().find(|ty| ty.name == "Query").unwrap(); - - let field = query - .fields - .iter() - .find(|field| field.name == "fieldNewAttrs") - .unwrap(); - - let arg1 = field.args.iter().find(|arg| arg.name == "arg1").unwrap(); - assert_eq!(&arg1.description, &Some("arg1 desc".to_string())); - assert_eq!( - &arg1.default_value, - &Some(Value::String("true".to_string())) - ); - - let arg2 = field.args.iter().find(|arg| arg.name == "arg2").unwrap(); - assert_eq!(&arg2.description, &Some("arg2 desc".to_string())); - assert_eq!( - &arg2.default_value, - &Some(Value::String("false".to_string())) - ); -} - -#[derive(Debug)] -struct Schema { - types: Vec, -} - -#[derive(Debug)] -struct Type { - name: String, - fields: Vec, -} - -#[derive(Debug)] -struct Field { - name: String, - args: Vec, - description: Option, -} - -#[derive(Debug)] -struct Arg { - name: String, - description: Option, - default_value: Option, -} - -fn introspect_schema() -> Schema { - let (value, _errors) = juniper::execute( - SCHEMA_INTROSPECTION_QUERY, - None, - &RootNode::new(Query, juniper::EmptyMutation::new()), - &Variables::new(), - &(), - ) - .unwrap(); - - let value: Value = serde_json::from_str(&serde_json::to_string(&value).unwrap()).unwrap(); - - let types = value["__schema"]["types"] - .as_array() - .unwrap() - .iter() - .map(parse_type) - .collect::>(); - - Schema { types } -} - -fn parse_type(value: &Value) -> Type { - let name = value["name"].as_str().unwrap().to_string(); - - let fields = if value["fields"].is_null() { - vec![] - } else { - value["fields"] - .as_array() - .unwrap() - .iter() - .map(parse_field) - .collect::>() - }; - - Type { name, fields } -} - -fn parse_field(value: &Value) -> Field { - let name = value["name"].as_str().unwrap().to_string(); - - let description = if value["description"].is_null() { - None - } else { - Some(value["description"].as_str().unwrap().to_string()) - }; - - let args = value["args"] - .as_array() - .unwrap() - .iter() - .map(parse_arg) - .collect::>(); - - Field { - name, - description, - args, - } -} - -fn parse_arg(value: &Value) -> Arg { - let name = value["name"].as_str().unwrap().to_string(); - - let description = if value["description"].is_null() { - None - } else { - Some(value["description"].as_str().unwrap().to_string()) - }; - - let default_value = if value["defaultValue"].is_null() { - None - } else { - Some(value["defaultValue"].clone()) - }; - - Arg { - name, - description, - default_value, - } -} diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 00fa01406..d7accb100 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -63,13 +63,11 @@ impl Root { Sample::One } - #[graphql(arguments( - first(description = "The first number",), - second(description = "The second number", default = 123,), - ))] - /// A sample scalar field on the object - fn sample_scalar(first: i32, second: i32) -> Scalar { + fn sample_scalar( + #[graphql(description = "The first number")] first: i32, + #[graphql(description = "The second number", default = 123)] second: i32, + ) -> Scalar { Scalar(first + second) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index cce48291d..8689587e9 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -78,14 +78,9 @@ impl TestType { format!("{:?}", input) } - #[graphql( - arguments( - input( - default = "Hello World".to_string(), - ) - ) - )] - fn field_with_default_argument_value(input: String) -> String { + fn field_with_default_argument_value( + #[graphql(default = "Hello World".to_string())] input: String, + ) -> String { format!("{:?}", input) } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 99915094f..3286b5bd2 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -52,84 +52,73 @@ impl Root { 0 } - #[graphql(arguments(arg(description = "The arg")))] - fn single_arg_descr(arg: i32) -> i32 { + fn single_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { 0 } - #[graphql(arguments( - arg1(description = "The first arg",), - arg2(description = "The second arg") - ))] - fn multi_args_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_descr( + #[graphql(description = "The first arg")] arg1: i32, + #[graphql(description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - #[graphql(arguments( - arg1(description = "The first arg",), - arg2(description = "The second arg") - ))] - fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 { + fn multi_args_descr_trailing_comma( + #[graphql(description = "The first arg")] arg1: i32, + #[graphql(description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - // TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented - // fn attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 } - // fn attr_arg_descr_collapse( - // #[doc = "The arg"] - // #[doc = "and more details"] - // arg: i32, - // ) -> i32 { 0 } - - #[graphql(arguments(arg(default = 123,),))] - fn arg_with_default(arg: i32) -> i32 { + fn arg_with_default(#[graphql(default = 123)] arg: i32) -> i32 { 0 } - #[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))] - fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default( + #[graphql(default = 123)] arg1: i32, + #[graphql(default = 456)] arg2: i32, + ) -> i32 { 0 } - #[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))] - fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_trailing_comma( + #[graphql(default = 123)] arg1: i32, + #[graphql(default = 456)] arg2: i32, + ) -> i32 { 0 } - #[graphql(arguments(arg(default = 123, description = "The arg")))] - fn arg_with_default_descr(arg: i32) -> i32 { + fn arg_with_default_descr(#[graphql(default = 123, description = "The arg")] arg: i32) -> i32 { 0 } - #[graphql(arguments( - arg1(default = 123, description = "The first arg"), - arg2(default = 456, description = "The second arg") - ))] - fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_descr( + #[graphql(default = 123, description = "The first arg")] arg1: i32, + #[graphql(default = 456, description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - #[graphql(arguments( - arg1(default = 123, description = "The first arg",), - arg2(default = 456, description = "The second arg",) - ))] - fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_trailing_comma_descr( + #[graphql(default = 123, description = "The first arg")] arg1: i32, + #[graphql(default = 456, description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - #[graphql( - arguments( - arg1( - default = "test".to_string(), - description = "A string default argument", - ), - arg2( - default = Point{ x: 1 }, - description = "An input object default argument", - ) - ), - )] - fn args_with_complex_default(arg1: String, arg2: Point) -> i32 { + fn args_with_complex_default( + #[graphql( + default = "test".to_string(), + description = "A string default argument", + )] + arg1: String, + + #[graphql( + default = Point{ x: 1 }, + description = "An input object default argument", + )] + arg2: Point, + ) -> i32 { 0 } } diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index bbcfdb18b..7fe2b9f3b 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -35,7 +35,7 @@ struct Query { #[crate::object_internal( scalar = crate::DefaultScalarValue, - name = "Query", + name = "Query", context = Context, )] /// Query Description. @@ -84,13 +84,11 @@ impl<'a> Query { arg1 } - #[graphql(arguments(default_arg(default = true)))] - fn default_argument(default_arg: bool) -> bool { + fn default_argument(#[graphql(default = true)] default_arg: bool) -> bool { default_arg } - #[graphql(arguments(arg(description = "my argument description")))] - fn arg_with_description(arg: bool) -> bool { + fn arg_with_description(#[graphql(description = "my argument description")] arg: bool) -> bool { arg } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index a273fa7ec..72dc93fe0 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -144,8 +144,10 @@ where } } - #[graphql(arguments(include_deprecated(default = false)))] - fn fields(&self, include_deprecated: bool) -> Option>> { + fn fields( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option>> { match *self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -230,8 +232,10 @@ where } } - #[graphql(arguments(include_deprecated(default = false)))] - fn enum_values(&self, include_deprecated: bool) -> Option> { + fn enum_values( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option> { match *self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index 37a7a4fb8..731369821 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -107,20 +107,27 @@ pub struct Query; )] /// The root query object of the schema impl Query { - #[graphql(arguments(id(description = "id of the human")))] - fn human(database: &Database, id: String) -> Option<&dyn Human> { + fn human( + database: &Database, + #[graphql(description = "id of the human")] id: String, + ) -> Option<&dyn Human> { database.get_human(&id) } - #[graphql(arguments(id(description = "id of the droid")))] - fn droid(database: &Database, id: String) -> Option<&dyn Droid> { + fn droid( + database: &Database, + #[graphql(description = "id of the droid")] id: String, + ) -> Option<&dyn Droid> { database.get_droid(&id) } - #[graphql(arguments(episode( - description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" - )))] - fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { + fn hero( + database: &Database, + #[graphql( + description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" + )] + episode: Option, + ) -> Option<&dyn Character> { Some(database.get_hero(episode).as_character()) } } diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index b152c81a1..87aaccae2 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -2,7 +2,6 @@ use crate::util; use proc_macro::TokenStream; use proc_macro_error::*; use quote::quote; -use syn::spanned::Spanned; /// Generate code for the juniper::object macro. pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream { @@ -108,22 +107,14 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> util::FieldAttributeParseMode::Impl, ) { Ok(attrs) => attrs, - Err(err) => panic!( + Err(err) => abort!( + err.span(), "Invalid #[graphql(...)] attribute on field {}:\n{}", - method.sig.ident, err + method.sig.ident, + err ), }; - if !attrs.arguments.is_empty() { - let deprecation_warning = vec![ - "Setting arguments via #[graphql(arguments(...))] on the method", - "is deprecrated. Instead use #[graphql(...)] as attributes directly", - "on the arguments themselves.", - ] - .join(" "); - eprintln!("{}", deprecation_warning); - } - let mut args = Vec::new(); let mut resolve_parts = Vec::new(); @@ -138,7 +129,7 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> } } syn::FnArg::Typed(ref captured) => { - if let Some(field_arg) = parse_argument_attrs(&captured) { + if let Some(field_arg) = util::parse_argument_attrs(&captured) { attrs .arguments .insert(field_arg.name.to_string(), field_arg); @@ -242,45 +233,3 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> let juniper_crate_name = if is_internal { "crate" } else { "juniper" }; definition.into_tokens(juniper_crate_name).into() } - -fn parse_argument_attrs(pat: &syn::PatType) -> Option { - let graphql_attrs = pat - .attrs - .iter() - .filter(|attr| { - let name = attr.path.get_ident().map(|i| i.to_string()); - name == Some("graphql".to_string()) - }) - .collect::>(); - - let graphql_attr = match graphql_attrs.len() { - 0 => return None, - 1 => &graphql_attrs[0], - _ => { - let last_attr = graphql_attrs.last().unwrap(); - abort!( - last_attr.span(), - "You cannot have multiple #[graphql] attributes on the same arg" - ); - } - }; - - let name = match &*pat.pat { - syn::Pat::Ident(i) => &i.ident, - other => abort!(other.span(), "Invalid token for function argument"), - }; - - let mut arg = util::FieldAttributeArgument { - name: name.to_owned(), - default: None, - description: None, - }; - - graphql_attr - .parse_args_with(|content: syn::parse::ParseStream| { - util::parse_field_attr_arg_contents(&content, &mut arg) - }) - .unwrap_or_else(|err| abort!(err.span(), "{}", err)); - - Some(arg) -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index cff9f1bf3..edcb1a244 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -360,6 +360,7 @@ pub fn object(args: TokenStream, input: TokenStream) -> TokenStream { /// A proc macro for defining a GraphQL object. #[doc(hidden)] #[proc_macro_attribute] +#[proc_macro_error] pub fn object_internal(args: TokenStream, input: TokenStream) -> TokenStream { let gen = impl_object::build_object(args, input, true); gen.into() diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index 68bae2335..cbdabaed6 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -1,8 +1,9 @@ +use proc_macro_error::abort; use quote::quote; use std::collections::HashMap; use syn::{ - parse, parse_quote, punctuated::Punctuated, Attribute, Lit, Meta, MetaList, MetaNameValue, - NestedMeta, Token, + parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Lit, Meta, MetaList, + MetaNameValue, NestedMeta, Token, }; /// Compares a path to a one-segment string value, @@ -390,25 +391,49 @@ pub struct FieldAttributeArgument { pub description: Option, } -impl parse::Parse for FieldAttributeArgument { - fn parse(input: parse::ParseStream) -> parse::Result { - let name = input.parse::()?; +pub fn parse_argument_attrs(pat: &syn::PatType) -> Option { + let graphql_attrs = pat + .attrs + .iter() + .filter(|attr| { + let name = attr.path.get_ident().map(|i| i.to_string()); + name == Some("graphql".to_string()) + }) + .collect::>(); - let mut arg = Self { - name, - default: None, - description: None, - }; + let graphql_attr = match graphql_attrs.len() { + 0 => return None, + 1 => &graphql_attrs[0], + _ => { + let last_attr = graphql_attrs.last().unwrap(); + abort!( + last_attr.span(), + "You cannot have multiple #[graphql] attributes on the same arg" + ); + } + }; - let content; - syn::parenthesized!(content in input); - parse_field_attr_arg_contents(&content, &mut arg)?; + let name = match &*pat.pat { + syn::Pat::Ident(i) => &i.ident, + other => abort!(other.span(), "Invalid token for function argument"), + }; - Ok(arg) - } + let mut arg = FieldAttributeArgument { + name: name.to_owned(), + default: None, + description: None, + }; + + graphql_attr + .parse_args_with(|content: syn::parse::ParseStream| { + parse_field_attr_arg_contents(&content, &mut arg) + }) + .unwrap_or_else(|err| abort!(err.span(), "{}", err)); + + Some(arg) } -pub fn parse_field_attr_arg_contents( +fn parse_field_attr_arg_contents( content: syn::parse::ParseStream, arg: &mut FieldAttributeArgument, ) -> parse::Result<()> { @@ -446,7 +471,6 @@ enum FieldAttribute { Description(syn::LitStr), Deprecation(DeprecationAttr), Skip(syn::Ident), - Arguments(HashMap), } impl parse::Parse for FieldAttribute { @@ -485,18 +509,6 @@ impl parse::Parse for FieldAttribute { })) } "skip" => Ok(FieldAttribute::Skip(ident)), - "arguments" => { - let arg_content; - syn::parenthesized!(arg_content in input); - let args = Punctuated::::parse_terminated( - &arg_content, - )?; - let map = args - .into_iter() - .map(|arg| (arg.name.to_string(), arg)) - .collect(); - Ok(FieldAttribute::Arguments(map)) - } other => Err(input.error(format!("Unknown attribute: {}", other))), } } @@ -522,6 +534,8 @@ impl parse::Parse for FieldAttributes { description: None, deprecation: None, skip: false, + // The arguments get set later via attrs on the argument items themselves in + // `parse_argument_attrs` arguments: Default::default(), }; @@ -539,9 +553,6 @@ impl parse::Parse for FieldAttributes { FieldAttribute::Skip(_) => { output.skip = true; } - FieldAttribute::Arguments(args) => { - output.arguments = args; - } } } From a368f7736d531eb3d4a5f0a08d82bdd8f62f1deb Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 26 Oct 2019 12:06:34 +0200 Subject: [PATCH 07/12] Add crate "assert-json-diff" for more friend test failures --- juniper/Cargo.toml | 1 + juniper/src/macros/tests/impl_object.rs | 49 ++++++++++++++----------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml index b8176c465..53d656892 100644 --- a/juniper/Cargo.toml +++ b/juniper/Cargo.toml @@ -47,3 +47,4 @@ uuid = { version = "0.7", optional = true } [dev-dependencies] bencher = "0.1.2" serde_json = { version = "1.0.2" } +assert-json-diff = "1.0.1" diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 7fe2b9f3b..749c955db 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -118,12 +118,19 @@ impl Mutation { } } +fn juniper_value_to_serde_json_value( + value: &crate::Value, +) -> serde_json::Value { + serde_json::from_str(&serde_json::to_string(value).unwrap()).unwrap() +} + #[test] fn object_introspect() { let res = util::run_info_query::("Query"); - assert_eq!( - res, - crate::graphql_value!({ + + assert_json_diff::assert_json_include!( + actual: juniper_value_to_serde_json_value(&res), + expected: serde_json::json!({ "name": "Query", "description": "Query Description.", "fields": [ @@ -134,32 +141,32 @@ fn object_introspect() { }, { "name": "independent", - "description": None, + "description": None::, "args": [], }, { "name": "withExecutor", - "description": None, + "description": None::, "args": [], }, { "name": "withExecutorAndSelf", - "description": None, + "description": None::, "args": [], }, { "name": "withContext", - "description": None, + "description": None::, "args": [], }, { "name": "withContextAndSelf", - "description": None, + "description": None::, "args": [], }, { "name": "renamed", - "description": None, + "description": None::, "args": [], }, { @@ -174,24 +181,24 @@ fn object_introspect() { }, { "name": "hasArgument", - "description": None, + "description": None::, "args": [ { "name": "arg1", - "description": None, + "description": None::, "type": { - "name": None, + "name": None::, }, } ], }, { "name": "defaultArgument", - "description": None, + "description": None::, "args": [ { "name": "defaultArg", - "description": None, + "description": None::, "type": { "name": "Boolean", }, @@ -200,36 +207,36 @@ fn object_introspect() { }, { "name": "argWithDescription", - "description": None, + "description": None::, "args": [ { "name": "arg", "description": "my argument description", "type": { - "name": None + "name": None:: }, } ], }, { "name": "withContextChild", - "description": None, + "description": None::, "args": [], }, { "name": "withLifetimeChild", - "description": None, + "description": None::, "args": [], }, { "name": "withMutArg", - "description": None, + "description": None::, "args": [ { "name": "arg", - "description": None, + "description": None::, "type": { - "name": None, + "name": None::, }, } ], From 1028adb8dd852a2610761194c114e99abc1e4014 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 26 Oct 2019 12:25:26 +0200 Subject: [PATCH 08/12] Support renaming arguments through param attr --- juniper/src/macros/tests/impl_object.rs | 17 +++++++++++++++ juniper_codegen/src/impl_object.rs | 28 +++++++++++++++---------- juniper_codegen/src/util.rs | 7 +++++-- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 749c955db..b0c12aba7 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -92,6 +92,10 @@ impl<'a> Query { arg } + fn renamed_argument(#[graphql(name = new_name)] old_name: bool) -> bool { + old_name + } + fn with_context_child(&self) -> WithContext { WithContext } @@ -218,6 +222,19 @@ fn object_introspect() { } ], }, + { + "name": "renamedArgument", + "description": None::, + "args": [ + { + "name": "newName", + "description": None::, + "type": { + "name": None:: + }, + } + ], + }, { "name": "withContextChild", "description": None::, diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 87aaccae2..839bd830a 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -129,12 +129,6 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> } } syn::FnArg::Typed(ref captured) => { - if let Some(field_arg) = util::parse_argument_attrs(&captured) { - attrs - .arguments - .insert(field_arg.name.to_string(), field_arg); - } - let (arg_ident, is_mut) = match &*captured.pat { syn::Pat::Ident(ref pat_ident) => { (&pat_ident.ident, pat_ident.mutability.is_some()) @@ -143,7 +137,14 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> panic!("Invalid token for function argument"); } }; - let arg_name = arg_ident.to_string(); + let arg_ident_name = arg_ident.to_string(); + + if let Some(field_arg) = util::parse_argument_attrs(&captured) { + // We insert with `arg_ident_name` as the key because the argument + // might have been renamed in the param attribute and we need to + // look it up for making `final_name` further down. + attrs.arguments.insert(arg_ident_name.clone(), field_arg); + } let context_type = definition.context.as_ref(); @@ -178,8 +179,13 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> // Regular argument. let ty = &captured.ty; - // TODO: respect graphql attribute overwrite. - let final_name = util::to_camel_case(&arg_name); + + let final_name = attrs + .argument(&arg_ident_name) + .and_then(|arg| arg.name.as_ref()) + .map(|name| util::to_camel_case(&name.to_string())) + .unwrap_or_else(|| util::to_camel_case(&arg_ident_name)); + let expect_text = format!("Internal error: missing argument {} - validation must have failed", &final_name); let mut_modifier = if is_mut { quote!(mut) } else { quote!() }; resolve_parts.push(quote!( @@ -188,11 +194,11 @@ pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> .expect(#expect_text); )); args.push(util::GraphQLTypeDefinitionFieldArg { - description: attrs.argument(&arg_name).and_then(|arg| { + description: attrs.argument(&arg_ident_name).and_then(|arg| { arg.description.as_ref().map(|d| d.value()) }), default: attrs - .argument(&arg_name) + .argument(&arg_ident_name) .and_then(|arg| arg.default.clone()), _type: ty.clone(), name: final_name, diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index cbdabaed6..1359def82 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -386,7 +386,7 @@ impl ObjectAttributes { #[derive(Debug)] pub struct FieldAttributeArgument { - pub name: syn::Ident, + pub name: Option, pub default: Option, pub description: Option, } @@ -419,7 +419,7 @@ pub fn parse_argument_attrs(pat: &syn::PatType) -> Option { arg.default = Some(content.parse()?); } + "name" => { + arg.name = content.parse()?; + } other => { return Err(content.error(format!("Invalid attribute argument key `{}`", other))); } From 79c98c2e98a8015e0928a59d4a8abfa1a42246dc Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 26 Oct 2019 12:35:30 +0200 Subject: [PATCH 09/12] Update changelog --- juniper/CHANGELOG.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 3af518981..6d2ee1e91 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -1,6 +1,30 @@ # master -- No changes yet +### New way to customize arguments + +See [#441](https://github.com/graphql-rust/juniper/pull/441). + +You can now customize arguments by annotating them with `#[graphql(...)]` directly. Example: + +```rust +#[juniper::object] +impl Query { + fn some_field_with_a_description( + #[graphql( + name = newNameForArg, + description = "My argument description", + default = false, + )] + arg: bool + ) -> bool { + // ... + } +} +``` + +The old style `#[graphql(arguments(...))]` is no longer supported. + +Note that this requires Rust 1.39. # [[0.14.0] 2019-09-29](https://github.com/graphql-rust/juniper/releases/tag/juniper-0.14.0) From 99a99fc5af768d543c2d566378a2f6b0d6881096 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 26 Oct 2019 12:37:40 +0200 Subject: [PATCH 10/12] Demonstrate naming args in docs --- juniper_codegen/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index edcb1a244..e042b86f2 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -291,18 +291,21 @@ impl InternalQuery { // Customizing field arguments can be done like so: - // Note that attributes on arguments requires Rust 1.39 fn args( #[graphql( + description = "Argument description....", + // You can specify default values. // A default can be any valid expression that yields the right type. default = true, - description = "Argument description....", + + // You can also give the argument a different name in your GraphQL schema + name = newName )] arg1: bool, #[graphql( - default = false, description = "arg2 description...", + default = false, )] arg2: bool, ) -> bool { arg1 && arg2 From 285876412cc45b886fbe6fb3a4664b8d71c7b79c Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 1 Nov 2019 16:37:54 +0100 Subject: [PATCH 11/12] Format --- .../src/executor_tests/introspection/mod.rs | 5 +- juniper/src/executor_tests/variables.rs | 4 +- juniper/src/macros/tests/args.rs | 50 ++++--------------- juniper/src/macros/tests/impl_object.rs | 6 +-- juniper/src/schema/schema.rs | 10 +--- juniper/src/tests/schema.rs | 18 ++----- 6 files changed, 20 insertions(+), 73 deletions(-) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index d7accb100..ea0e488ce 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -64,10 +64,7 @@ impl Root { } /// A sample scalar field on the object - fn sample_scalar( - #[graphql(description = "The first number")] first: i32, - #[graphql(description = "The second number", default = 123)] second: i32, - ) -> Scalar { + fn sample_scalar(first: i32, second: i32) -> Scalar { Scalar(first + second) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 8689587e9..1273ff168 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -78,9 +78,7 @@ impl TestType { format!("{:?}", input) } - fn field_with_default_argument_value( - #[graphql(default = "Hello World".to_string())] input: String, - ) -> String { + fn field_with_default_argument_value(input: String) -> String { format!("{:?}", input) } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 3286b5bd2..68a485d8b 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -52,73 +52,43 @@ impl Root { 0 } - fn single_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { + fn single_arg_descr(arg: i32) -> i32 { 0 } - fn multi_args_descr( - #[graphql(description = "The first arg")] arg1: i32, - #[graphql(description = "The second arg")] arg2: i32, - ) -> i32 { + fn multi_args_descr(arg1: i32, arg2: i32) -> i32 { 0 } - fn multi_args_descr_trailing_comma( - #[graphql(description = "The first arg")] arg1: i32, - #[graphql(description = "The second arg")] arg2: i32, - ) -> i32 { + fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 { 0 } - fn arg_with_default(#[graphql(default = 123)] arg: i32) -> i32 { + fn arg_with_default(arg: i32) -> i32 { 0 } - fn multi_args_with_default( - #[graphql(default = 123)] arg1: i32, - #[graphql(default = 456)] arg2: i32, - ) -> i32 { + fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 { 0 } - fn multi_args_with_default_trailing_comma( - #[graphql(default = 123)] arg1: i32, - #[graphql(default = 456)] arg2: i32, - ) -> i32 { + fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 { 0 } - fn arg_with_default_descr(#[graphql(default = 123, description = "The arg")] arg: i32) -> i32 { + fn arg_with_default_descr(arg: i32) -> i32 { 0 } - fn multi_args_with_default_descr( - #[graphql(default = 123, description = "The first arg")] arg1: i32, - #[graphql(default = 456, description = "The second arg")] arg2: i32, - ) -> i32 { + fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 { 0 } - fn multi_args_with_default_trailing_comma_descr( - #[graphql(default = 123, description = "The first arg")] arg1: i32, - #[graphql(default = 456, description = "The second arg")] arg2: i32, - ) -> i32 { + fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 { 0 } - fn args_with_complex_default( - #[graphql( - default = "test".to_string(), - description = "A string default argument", - )] - arg1: String, - - #[graphql( - default = Point{ x: 1 }, - description = "An input object default argument", - )] - arg2: Point, - ) -> i32 { + fn args_with_complex_default(arg1: String, arg2: Point) -> i32 { 0 } } diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index b0c12aba7..458662633 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -84,15 +84,15 @@ impl<'a> Query { arg1 } - fn default_argument(#[graphql(default = true)] default_arg: bool) -> bool { + fn default_argument(default_arg: bool) -> bool { default_arg } - fn arg_with_description(#[graphql(description = "my argument description")] arg: bool) -> bool { + fn arg_with_description(arg: bool) -> bool { arg } - fn renamed_argument(#[graphql(name = new_name)] old_name: bool) -> bool { + fn renamed_argument(old_name: bool) -> bool { old_name } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 72dc93fe0..8462f2ab6 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -144,10 +144,7 @@ where } } - fn fields( - &self, - #[graphql(default = false)] include_deprecated: bool, - ) -> Option>> { + fn fields(&self, include_deprecated: bool) -> Option>> { match *self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -232,10 +229,7 @@ where } } - fn enum_values( - &self, - #[graphql(default = false)] include_deprecated: bool, - ) -> Option> { + fn enum_values(&self, include_deprecated: bool) -> Option> { match *self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index 731369821..f8d9b58e0 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -107,27 +107,15 @@ pub struct Query; )] /// The root query object of the schema impl Query { - fn human( - database: &Database, - #[graphql(description = "id of the human")] id: String, - ) -> Option<&dyn Human> { + fn human(database: &Database, id: String) -> Option<&dyn Human> { database.get_human(&id) } - fn droid( - database: &Database, - #[graphql(description = "id of the droid")] id: String, - ) -> Option<&dyn Droid> { + fn droid(database: &Database, id: String) -> Option<&dyn Droid> { database.get_droid(&id) } - fn hero( - database: &Database, - #[graphql( - description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" - )] - episode: Option, - ) -> Option<&dyn Character> { + fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { Some(database.get_hero(episode).as_character()) } } From 5f3187509c2f682a9c43311537348df27c315703 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Thu, 7 Nov 2019 20:46:31 +0100 Subject: [PATCH 12/12] Revert "Format" This reverts commit 285876412cc45b886fbe6fb3a4664b8d71c7b79c. Apparently rustfmt removes parameter attributes --- .../src/executor_tests/introspection/mod.rs | 5 +- juniper/src/executor_tests/variables.rs | 4 +- juniper/src/macros/tests/args.rs | 50 +++++++++++++++---- juniper/src/macros/tests/impl_object.rs | 6 +-- juniper/src/schema/schema.rs | 10 +++- juniper/src/tests/schema.rs | 18 +++++-- 6 files changed, 73 insertions(+), 20 deletions(-) diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index ea0e488ce..d7accb100 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -64,7 +64,10 @@ impl Root { } /// A sample scalar field on the object - fn sample_scalar(first: i32, second: i32) -> Scalar { + fn sample_scalar( + #[graphql(description = "The first number")] first: i32, + #[graphql(description = "The second number", default = 123)] second: i32, + ) -> Scalar { Scalar(first + second) } } diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index 1273ff168..8689587e9 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -78,7 +78,9 @@ impl TestType { format!("{:?}", input) } - fn field_with_default_argument_value(input: String) -> String { + fn field_with_default_argument_value( + #[graphql(default = "Hello World".to_string())] input: String, + ) -> String { format!("{:?}", input) } diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index 68a485d8b..3286b5bd2 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -52,43 +52,73 @@ impl Root { 0 } - fn single_arg_descr(arg: i32) -> i32 { + fn single_arg_descr(#[graphql(description = "The arg")] arg: i32) -> i32 { 0 } - fn multi_args_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_descr( + #[graphql(description = "The first arg")] arg1: i32, + #[graphql(description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 { + fn multi_args_descr_trailing_comma( + #[graphql(description = "The first arg")] arg1: i32, + #[graphql(description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - fn arg_with_default(arg: i32) -> i32 { + fn arg_with_default(#[graphql(default = 123)] arg: i32) -> i32 { 0 } - fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default( + #[graphql(default = 123)] arg1: i32, + #[graphql(default = 456)] arg2: i32, + ) -> i32 { 0 } - fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_trailing_comma( + #[graphql(default = 123)] arg1: i32, + #[graphql(default = 456)] arg2: i32, + ) -> i32 { 0 } - fn arg_with_default_descr(arg: i32) -> i32 { + fn arg_with_default_descr(#[graphql(default = 123, description = "The arg")] arg: i32) -> i32 { 0 } - fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_descr( + #[graphql(default = 123, description = "The first arg")] arg1: i32, + #[graphql(default = 456, description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 { + fn multi_args_with_default_trailing_comma_descr( + #[graphql(default = 123, description = "The first arg")] arg1: i32, + #[graphql(default = 456, description = "The second arg")] arg2: i32, + ) -> i32 { 0 } - fn args_with_complex_default(arg1: String, arg2: Point) -> i32 { + fn args_with_complex_default( + #[graphql( + default = "test".to_string(), + description = "A string default argument", + )] + arg1: String, + + #[graphql( + default = Point{ x: 1 }, + description = "An input object default argument", + )] + arg2: Point, + ) -> i32 { 0 } } diff --git a/juniper/src/macros/tests/impl_object.rs b/juniper/src/macros/tests/impl_object.rs index 458662633..b0c12aba7 100644 --- a/juniper/src/macros/tests/impl_object.rs +++ b/juniper/src/macros/tests/impl_object.rs @@ -84,15 +84,15 @@ impl<'a> Query { arg1 } - fn default_argument(default_arg: bool) -> bool { + fn default_argument(#[graphql(default = true)] default_arg: bool) -> bool { default_arg } - fn arg_with_description(arg: bool) -> bool { + fn arg_with_description(#[graphql(description = "my argument description")] arg: bool) -> bool { arg } - fn renamed_argument(old_name: bool) -> bool { + fn renamed_argument(#[graphql(name = new_name)] old_name: bool) -> bool { old_name } diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 8462f2ab6..72dc93fe0 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -144,7 +144,10 @@ where } } - fn fields(&self, include_deprecated: bool) -> Option>> { + fn fields( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option>> { match *self { TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) | TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some( @@ -229,7 +232,10 @@ where } } - fn enum_values(&self, include_deprecated: bool) -> Option> { + fn enum_values( + &self, + #[graphql(default = false)] include_deprecated: bool, + ) -> Option> { match *self { TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some( values diff --git a/juniper/src/tests/schema.rs b/juniper/src/tests/schema.rs index f8d9b58e0..731369821 100644 --- a/juniper/src/tests/schema.rs +++ b/juniper/src/tests/schema.rs @@ -107,15 +107,27 @@ pub struct Query; )] /// The root query object of the schema impl Query { - fn human(database: &Database, id: String) -> Option<&dyn Human> { + fn human( + database: &Database, + #[graphql(description = "id of the human")] id: String, + ) -> Option<&dyn Human> { database.get_human(&id) } - fn droid(database: &Database, id: String) -> Option<&dyn Droid> { + fn droid( + database: &Database, + #[graphql(description = "id of the droid")] id: String, + ) -> Option<&dyn Droid> { database.get_droid(&id) } - fn hero(database: &Database, episode: Option) -> Option<&dyn Character> { + fn hero( + database: &Database, + #[graphql( + description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode" + )] + episode: Option, + ) -> Option<&dyn Character> { Some(database.get_hero(episode).as_character()) } }