From ca1f9045cc9c3e6e2e49201ad02a6dde7cb8a2b4 Mon Sep 17 00:00:00 2001 From: Douman Date: Thu, 10 Feb 2022 13:38:28 +0900 Subject: [PATCH 1/4] Introduce process_with for input object --- derive/src/args.rs | 2 + derive/src/input_object.rs | 27 +++++++++--- src/docs/input_object.md | 2 + tests/input_object.rs | 85 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) diff --git a/derive/src/args.rs b/derive/src/args.rs index eb0c7d85..0fe983f6 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -372,6 +372,8 @@ pub struct InputObjectField { pub skip: bool, #[darling(default)] pub skip_input: bool, + #[darling(default)] + pub process_with: Option, // for SimpleObject #[darling(default)] pub skip_output: bool, diff --git a/derive/src/input_object.rs b/derive/src/input_object.rs index 4d62853d..c39bb2c6 100644 --- a/derive/src/input_object.rs +++ b/derive/src/input_object.rs @@ -75,6 +75,16 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult federation_fields.push((ty, name.clone())); + let process_with = match field.process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(&fn_path)?; + quote! { + #fn_path(&mut #ident); + } + } + None => Default::default(), + }; + let validators = field .validator .clone() @@ -99,9 +109,11 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult }); get_fields.push(quote! { - let #ident: #ty = #crate_name::InputType::parse( + #[allow(unused_mut)] + let mut #ident: #ty = #crate_name::InputType::parse( ::std::option::Option::Some(#crate_name::Value::Object(::std::clone::Clone::clone(&obj))) ).map_err(#crate_name::InputValueError::propagate)?; + #process_with #validators }); @@ -137,8 +149,12 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult let #ident: #ty = { match obj.get(#name) { ::std::option::Option::Some(value) => { - #crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value))) - .map_err(#crate_name::InputValueError::propagate)? + #[allow(unused_mut)] + let mut #ident = #crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value))) + .map_err(#crate_name::InputValueError::propagate)?; + #process_with + #ident + }, ::std::option::Option::None => #default, } @@ -147,9 +163,10 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult }); } else { get_fields.push(quote! { - #[allow(non_snake_case)] - let #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned()) + #[allow(non_snake_case, unused_mut)] + let mut #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned()) .map_err(#crate_name::InputValueError::propagate)?; + #process_with #validators }); } diff --git a/src/docs/input_object.md b/src/docs/input_object.md index 5b3cb82f..e02fc03f 100644 --- a/src/docs/input_object.md +++ b/src/docs/input_object.md @@ -24,6 +24,8 @@ Define a GraphQL input object | flatten | Similar to serde (flatten) | boolean | Y | | skip | Skip this field, use `Default::default` to get a default value for this field. | bool | Y | | skip_input | Skip this field, similar to `skip`, but avoids conflicts when this macro is used with `SimpleObject`. | bool | Y | +| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. + | code path | Y | | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | diff --git a/tests/input_object.rs b/tests/input_object.rs index d9c86ee9..acc3eb44 100644 --- a/tests/input_object.rs +++ b/tests/input_object.rs @@ -546,3 +546,88 @@ pub async fn test_complex_output() { }) ); } + +#[tokio::test] +pub async fn test_input_object_process_with() { + mod processor { + pub fn string(input: &mut String) { + while let Some(ch) = input.pop() { + if !ch.is_whitespace() { + input.push(ch); + break; + } + } + } + } + #[derive(InputObject)] + struct MyInput { + //processor does nothing on default value + #[graphql(default = " ", process_with = "processor::string")] + a: String, + + #[graphql(process_with = "processor::string")] + b: String, + } + + struct MyOutput { + a: String, + b: String, + } + + #[Object] + impl MyOutput { + async fn a(&self) -> &String { + &self.a + } + + async fn b(&self) -> &String { + &self.b + } + } + + struct Root; + + #[Object] + impl Root { + async fn a(&self, input: MyInput) -> MyOutput { + MyOutput { + a: input.a, + b: input.b, + } + } + } + + let schema = Schema::new(Root, EmptyMutation, EmptySubscription); + let query = r#"{ + a(input:{b: "test b "}) { + a b + } + }"# + .to_owned(); + assert_eq!( + schema.execute(&query).await.data, + value!({ + "a": { + "a": " ", + "b": "test b", + } + }) + ); + + let schema = Schema::new(Root, EmptyMutation, EmptySubscription); + let query = r#"{ + a(input:{a: "test a ", b: "test"}) { + a b + } + }"# + .to_owned(); + assert_eq!( + schema.execute(&query).await.data, + value!({ + "a": { + "a": "test a", + "b": "test", + } + }) + ); +} From b0933b34755b6f20ee3947142b205badcd749f4f Mon Sep 17 00:00:00 2001 From: Douman Date: Wed, 6 Apr 2022 16:10:56 +0900 Subject: [PATCH 2/4] Add process_with to methods --- derive/src/args.rs | 6 ++++++ derive/src/complex_object.rs | 41 +++++++++++++++++++++++++++++++---- derive/src/input_object.rs | 2 +- derive/src/object.rs | 42 +++++++++++++++++++++++++++++++----- derive/src/subscription.rs | 42 ++++++++++++++++++++++++++++++++---- src/docs/complex_object.md | 3 ++- src/docs/input_object.md | 3 +-- src/docs/object.md | 1 + src/docs/subscription.md | 1 + tests/complex_object.rs | 39 +++++++++++++++++++++++++++++++++ tests/object.rs | 24 +++++++++++++++++++++ 11 files changed, 187 insertions(+), 17 deletions(-) diff --git a/derive/src/args.rs b/derive/src/args.rs index 0fe983f6..a6d88db5 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -154,6 +154,8 @@ pub struct SimpleObjectField { pub visible: Option, #[darling(default, multiple)] pub derived: Vec, + #[darling(default)] + pub process_with: Option, // for InputObject #[darling(default)] pub default: Option, @@ -212,6 +214,8 @@ pub struct Argument { pub default: Option, pub default_with: Option, pub validator: Option, + #[darling(default)] + pub process_with: Option, pub key: bool, // for entity pub visible: Option, pub secret: bool, @@ -549,6 +553,8 @@ pub struct SubscriptionFieldArgument { pub default: Option, pub default_with: Option, pub validator: Option, + #[darling(default)] + pub process_with: Option, pub visible: Option, pub secret: bool, } diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 33322525..51b980be 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -215,6 +215,17 @@ pub fn generate( }); use_params.push(quote! { #ident }); + let param_ident = &ident.ident; + let process_with = match argument.process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = argument .validator .clone() @@ -225,10 +236,15 @@ pub fn generate( quote!(#ty), Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case, unused_variables)] - let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?; + #[allow(non_snake_case, unused_variables, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?; + #process_with #validators + #[allow(non_snake_case, unused_variables)] + let #ident = #non_mut_ident; }); } else { for ( @@ -240,6 +256,7 @@ pub fn generate( default, default_with, validator, + process_with, visible, secret, .. @@ -289,6 +306,17 @@ pub fn generate( None => quote! { ::std::option::Option::None }, }; + let param_ident = &ident.ident; + let process_with = match process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = validator.clone().unwrap_or_default().create_validators( &crate_name, quote!(&#ident), @@ -296,10 +324,15 @@ pub fn generate( Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case)] - let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?; + #[allow(non_snake_case, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; + #process_with #validators + #[allow(non_snake_case)] + let #ident = #non_mut_ident; }); } } diff --git a/derive/src/input_object.rs b/derive/src/input_object.rs index c39bb2c6..a02aecda 100644 --- a/derive/src/input_object.rs +++ b/derive/src/input_object.rs @@ -77,7 +77,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult let process_with = match field.process_with.as_ref() { Some(fn_path) => { - let fn_path: syn::ExprPath = syn::parse_str(&fn_path)?; + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; quote! { #fn_path(&mut #ident); } diff --git a/derive/src/object.rs b/derive/src/object.rs index d97c7632..5326f1d8 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -189,7 +189,7 @@ pub fn generate( if is_key { get_federation_key.push(quote! { if let Some(fields) = <#ty as #crate_name::InputType>::federation_fields() { - key_str.push(format!("{} {}", #name, fields)); + key_str.push(format!("{} {}", #name, fields)); } else { key_str.push(#name.to_string()); } @@ -360,6 +360,17 @@ pub fn generate( }); use_params.push(quote! { #ident }); + let param_ident = &ident.ident; + let process_with = match argument.process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = argument .validator .clone() @@ -370,10 +381,15 @@ pub fn generate( quote!(#ty), Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case, unused_variables)] - let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?; + #[allow(non_snake_case, unused_variables, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?; + #process_with #validators + #[allow(non_snake_case, unused_variables)] + let #ident = #non_mut_ident; }); } else { for ( @@ -384,6 +400,7 @@ pub fn generate( desc, default, default_with, + process_with, validator, visible, secret, @@ -434,6 +451,16 @@ pub fn generate( None => quote! { ::std::option::Option::None }, }; + let process_with = match process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = validator.clone().unwrap_or_default().create_validators( &crate_name, quote!(&#ident), @@ -441,10 +468,15 @@ pub fn generate( Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case, unused_variables)] - let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?; + #[allow(non_snake_case, unused_variables, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; + #process_with #validators + #[allow(non_snake_case, unused_variables)] + let #ident = #non_mut_ident; }); } } diff --git a/derive/src/subscription.rs b/derive/src/subscription.rs index ee5b7efe..77120f36 100644 --- a/derive/src/subscription.rs +++ b/derive/src/subscription.rs @@ -92,6 +92,17 @@ pub fn generate( }); use_params.push(quote! { #ident }); + let param_ident = &ident.ident; + let process_with = match argument.process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = argument .validator .clone() @@ -102,10 +113,15 @@ pub fn generate( quote!(#ty), Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case, unused_variables)] - let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?; + #[allow(non_snake_case, unused_variables, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?; + #process_with #validators + #[allow(non_snake_case, unused_variables)] + let #ident = #non_mut_ident; }); } else { for ( @@ -117,6 +133,7 @@ pub fn generate( default, default_with, validator, + process_with, visible: arg_visible, secret, }, @@ -164,6 +181,18 @@ pub fn generate( } None => quote! { ::std::option::Option::None }, }; + + let param_ident = &ident.ident; + let process_with = match process_with.as_ref() { + Some(fn_path) => { + let fn_path: syn::ExprPath = syn::parse_str(fn_path)?; + quote! { + #fn_path(&mut #param_ident); + } + } + None => Default::default(), + }; + let validators = validator.clone().unwrap_or_default().create_validators( &crate_name, quote!(&#ident), @@ -171,10 +200,15 @@ pub fn generate( Some(quote!(.map_err(|err| err.into_server_error(__pos)))), )?; + let mut non_mut_ident = ident.clone(); + non_mut_ident.mutability = None; get_params.push(quote! { - #[allow(non_snake_case)] - let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?; + #[allow(non_snake_case, unused_mut)] + let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?; + #process_with #validators + #[allow(non_snake_case)] + let #ident = #non_mut_ident; }); } } diff --git a/src/docs/complex_object.md b/src/docs/complex_object.md index 8cd71b32..25dd991a 100644 --- a/src/docs/complex_object.md +++ b/src/docs/complex_object.md @@ -52,6 +52,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | +| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | # Examples @@ -68,7 +69,7 @@ struct MyObj { #[ComplexObject] impl MyObj { async fn c(&self) -> i32 { - self.a + self.b + self.a + self.b } } diff --git a/src/docs/input_object.md b/src/docs/input_object.md index e02fc03f..c93f1d88 100644 --- a/src/docs/input_object.md +++ b/src/docs/input_object.md @@ -24,8 +24,7 @@ Define a GraphQL input object | flatten | Similar to serde (flatten) | boolean | Y | | skip | Skip this field, use `Default::default` to get a default value for this field. | bool | Y | | skip_input | Skip this field, similar to `skip`, but avoids conflicts when this macro is used with `SimpleObject`. | bool | Y | -| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. - | code path | Y | +| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | diff --git a/src/docs/object.md b/src/docs/object.md index fcea0da9..58134b92 100644 --- a/src/docs/object.md +++ b/src/docs/object.md @@ -56,6 +56,7 @@ All methods are converted to camelCase. | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | | key | Is entity key(for Federation) | bool | Y | +| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | # Derived argument attributes diff --git a/src/docs/subscription.md b/src/docs/subscription.md index a030846e..bae85730 100644 --- a/src/docs/subscription.md +++ b/src/docs/subscription.md @@ -47,6 +47,7 @@ The filter function should be synchronous. | validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y | | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | +| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y | # Examples diff --git a/tests/complex_object.rs b/tests/complex_object.rs index f9e3d90f..6337979b 100644 --- a/tests/complex_object.rs +++ b/tests/complex_object.rs @@ -1,6 +1,45 @@ use async_graphql::*; use core::marker::PhantomData; +#[tokio::test] +async fn test_complex_object_process_with_method_field() { + #[derive(SimpleObject)] + #[graphql(complex)] + struct MyObj { + a: i32, + } + + #[ComplexObject] + impl MyObj { + async fn test( + &self, + #[graphql(process_with = "str::make_ascii_uppercase")] processed_complex_arg: String, + ) -> String { + processed_complex_arg + } + } + + struct Query; + + #[Object] + impl Query { + async fn obj(&self) -> MyObj { + MyObj { a: 10 } + } + } + + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + let query = "{ obj { test(processedComplexArg: \"smol\") } }"; + assert_eq!( + schema.execute(query).await.into_result().unwrap().data, + value!({ + "obj": { + "test": "SMOL" + } + }) + ); +} + #[tokio::test] pub async fn test_complex_object() { #[derive(SimpleObject)] diff --git a/tests/object.rs b/tests/object.rs index 71377b15..70394d3a 100644 --- a/tests/object.rs +++ b/tests/object.rs @@ -118,6 +118,30 @@ async fn test_flatten_with_context() { ); } +#[tokio::test] +async fn test_object_process_with_field() { + struct Query; + + #[Object] + impl Query { + async fn test( + &self, + #[graphql(process_with = "str::make_ascii_uppercase")] processed_arg: String, + ) -> String { + processed_arg + } + } + + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + let query = "{ test(processedArg: \"smol\") }"; + assert_eq!( + schema.execute(query).await.into_result().unwrap().data, + value!({ + "test": "SMOL" + }) + ); +} + #[tokio::test] async fn test_oneof_field() { #[derive(OneofObject)] From fe77b329a0a3c0200141a3f2116bbc18a37dd26a Mon Sep 17 00:00:00 2001 From: Paul Nguyen Date: Wed, 6 Apr 2022 15:50:07 +0200 Subject: [PATCH 3/4] Support macro type in enum variant --- derive/src/union.rs | 31 ++++++++++++++++++------------- tests/union.rs | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/derive/src/union.rs b/derive/src/union.rs index d557995e..62c8d074 100644 --- a/derive/src/union.rs +++ b/derive/src/union.rs @@ -60,9 +60,14 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { } }; - if let Type::Path(p) = &ty { + let mut ty = ty; + while let Type::Group(group) = ty { + ty = &*group.elem; + } + + if matches!(ty, Type::Path(_) | Type::Macro(_)) { // This validates that the field type wasn't already used - if !enum_items.insert(p) { + if !enum_items.insert(ty) { return Err( Error::new_spanned(&ty, "This type already used in another variant").into(), ); @@ -70,16 +75,16 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { enum_names.push(enum_name); - let mut assert_ty = p.clone(); - RemoveLifetime.visit_type_path_mut(&mut assert_ty); + let mut assert_ty = ty.clone(); + RemoveLifetime.visit_type_mut(&mut assert_ty); if !variant.flatten { type_into_impls.push(quote! { #crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::ObjectType); #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause { - fn from(obj: #p) -> Self { + impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause { + fn from(obj: #ty) -> Self { #ident::#enum_name(obj) } } @@ -89,8 +94,8 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { #crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::UnionType); #[allow(clippy::all, clippy::pedantic)] - impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause { - fn from(obj: #p) -> Self { + impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause { + fn from(obj: #ty) -> Self { #ident::#enum_name(obj) } } @@ -99,15 +104,15 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { if !variant.flatten { registry_types.push(quote! { - <#p as #crate_name::OutputType>::create_type_info(registry); + <#ty as #crate_name::OutputType>::create_type_info(registry); }); possible_types.push(quote! { - possible_types.insert(<#p as #crate_name::OutputType>::type_name().into_owned()); + possible_types.insert(<#ty as #crate_name::OutputType>::type_name().into_owned()); }); } else { possible_types.push(quote! { if let #crate_name::registry::MetaType::Union { possible_types: possible_types2, .. } = - registry.create_fake_output_type::<#p>() { + registry.create_fake_output_type::<#ty>() { possible_types.extend(possible_types2); } }); @@ -115,11 +120,11 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult { if !variant.flatten { get_introspection_typename.push(quote! { - #ident::#enum_name(obj) => <#p as #crate_name::OutputType>::type_name() + #ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::type_name() }); } else { get_introspection_typename.push(quote! { - #ident::#enum_name(obj) => <#p as #crate_name::OutputType>::introspection_type_name(obj) + #ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::introspection_type_name(obj) }); } diff --git a/tests/union.rs b/tests/union.rs index 4de78dee..06db6ae0 100644 --- a/tests/union.rs +++ b/tests/union.rs @@ -419,3 +419,24 @@ pub async fn test_trait_object_in_union() { }) ); } + +macro_rules! generate_union { + ($name:ident, $variant_ty:ty) => { + #[derive(Union)] + pub enum $name { + Val($variant_ty), + } + }; +} + +#[test] +pub fn test_macro_generated_union() { + #[derive(SimpleObject)] + pub struct IntObj { + pub val: i32, + } + + generate_union!(MyEnum, IntObj); + + let _ = MyEnum::Val(IntObj { val: 1 }); +} From 5efb65c448e4cf21286bb230a1fe4dc7e8d3e4da Mon Sep 17 00:00:00 2001 From: Stephen Wicklund Date: Wed, 6 Apr 2022 20:21:02 -0700 Subject: [PATCH 4/4] Added instructions for returning errors from subscription resolvers ^Title. The example code in my comment is fairly long, so I just linked to it rather than filling the page with it (also because my solution for changing the error-type within the resolver is probably more verbose than needed). --- docs/en/src/error_handling.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/en/src/error_handling.md b/docs/en/src/error_handling.md index 29804042..6cd4080d 100644 --- a/docs/en/src/error_handling.md +++ b/docs/en/src/error_handling.md @@ -25,3 +25,12 @@ impl Query { } } ``` + +#### Errors in subscriptions + +Errors can be returned from subscription resolvers as well, using a return type of the form: +```rust +async fn my_subscription_resolver(&self) -> impl Stream> { ... } +``` + +Note however that the `MyError` struct must have `Clone` implemented, due to the restrictions placed by the `Subscription` macro. One way to accomplish this is by creating a custom error type, with `#[derive(Clone)]`, as [seen here](https://github.com/async-graphql/async-graphql/issues/845#issuecomment-1090933464).