From fa34a8ae683110eb81d7bbc22a2d277dfb59f6af Mon Sep 17 00:00:00 2001 From: Miaxos Date: Wed, 27 Oct 2021 13:37:13 +0000 Subject: [PATCH 1/5] feat: add basic coercion for SimpleObject derived arg --- derive/Cargo.toml | 2 + derive/src/args.rs | 2 + derive/src/simple_object.rs | 99 ++++++++++++++++++++++++++++++----- derive/src/utils.rs | 60 +++++++++++++++++++++ docs/en/src/derived_fields.md | 44 ++++++++++++++++ src/lib.rs | 2 + tests/derived_field.rs | 62 ++++++++++++++++++++++ 7 files changed, 259 insertions(+), 12 deletions(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 9e3de277..6fb64876 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -23,3 +23,5 @@ Inflector = "0.11.4" proc-macro-crate = "1.0.0" darling = "0.12.2" thiserror = "1.0.24" +regex = "1.5" +once_cell = "1.8.0" diff --git a/derive/src/args.rs b/derive/src/args.rs index 6b753c22..5bc5123b 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -255,6 +255,8 @@ pub struct ObjectField { pub struct DerivedField { pub name: Option, pub into: Option, + #[darling(default)] + pub owned: Option, } #[derive(FromDeriveInput)] diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 784f46d9..8a072a29 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -7,12 +7,27 @@ use syn::{Error, Ident, Type}; use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField}; use crate::utils::{ - gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult, + derive_type_coercion, gen_deprecation, generate_guards, get_crate_name, get_rustdoc, + visible_fn, DerivedIntoCoercion, GeneratorResult, }; +#[derive(Debug)] struct DerivedFieldMetadata { ident: Ident, into: Type, + owned: Option, + // The into argument for a derive field won't be able to transform everythings: + // Without the specialization from Rust, we can't implement things like From between Vec -> + // Vec or Option -> Option. + // But there are cases which you want to have this coercion derived, so to have it working + // until the specialization feature comes, we manually check coercion for the most usual cases + // which are: + // + // - Vec -> Vec + // - Option -> Option + // - Option> -> Option> + // - Vec> -> Vec> + coercion: DerivedIntoCoercion, } struct SimpleObjectFieldGenerator<'a> { @@ -60,10 +75,30 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult( proc_macro2::TokenStream::from_str(&derived.into.clone().unwrap()).unwrap(), - ); - let derived = DerivedFieldMetadata { ident: name, into }; + ) { + Ok(e) => e, + _ => { + return Err(Error::new_spanned( + &name, + "derived into must be a valid type.", + ) + .into()); + } + }; + + let ty = &field.ty; + + let derived_type = quote! { #into }.to_string(); + let base_type = quote! { #ty }.to_string(); + + let derived = DerivedFieldMetadata { + ident: name, + into, + owned: derived.owned, + coercion: derive_type_coercion(&base_type, &derived_type), + }; processed_fields.push(SimpleObjectFieldGenerator { field, @@ -83,7 +118,6 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult return Err(Error::new_spanned(&ident, "All fields must be named.").into()), }; - let is_derived = derived.is_some(); let ident = if let Some(derived) = derived { &derived.ident } else { @@ -109,12 +143,24 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { ::std::option::Option::None }, }; let vis = &field.vis; + let derived_coercion = if let Some(derived) = derived { + Some(&derived.coercion) + } else { + None + }; + let ty = if let Some(derived) = derived { &derived.into } else { &field.ty }; + let owned = if let Some(derived) = derived { + derived.owned.unwrap_or(field.owned) + } else { + field.owned + }; + let cache_control = { let public = field.cache_control.is_public(); let max_age = field.cache_control.max_age; @@ -152,7 +198,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { &self.#base_ident }, @@ -161,16 +207,45 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { - #block - }, - true => quote! { + // If the field is derived, it means it has a derived_coercion, depending on the coercion + // we have to implement several way to coerce it. + block = match derived_coercion { + Some(DerivedIntoCoercion::Unknown) => quote! { ::std::convert::Into::into(#block) }, + Some(DerivedIntoCoercion::OptionToOption) => quote! { + ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some(::std::convert::Into::into(value))) + }, + Some(DerivedIntoCoercion::VecToVec) => quote! { + { + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); + result + } + }, + Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { + ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); + result + })) + }, + Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { + { + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( + ::std::convert::Into::into(value) + )))); + result + } + }, + // If the field is not derived, we follow the normal process. + _ => quote! { + #block + }, }; - let ty = match !field.owned { + let ty = match !owned { true => quote! { &#ty }, false => quote! { #ty }, }; diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 394a30d4..6843c056 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -1,9 +1,11 @@ use std::collections::HashSet; use darling::FromMeta; +use once_cell::sync::Lazy; use proc_macro2::{Span, TokenStream, TokenTree}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; +use regex::Regex; use syn::visit::Visit; use syn::{ Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, NestedMeta, @@ -533,3 +535,61 @@ pub fn extract_input_args( Ok(args) } + +#[derive(Debug)] +pub enum DerivedIntoCoercion { + Unknown = 1, + VecToVec = 2, + OptionToOption = 3, + OptionVecToOptionVec = 4, + VecOptionToVecOption = 5, +} + +static CHECK_OPTION: Lazy = Lazy::new(|| Regex::new("^Option <(.*?) >$").unwrap()); +static CHECK_VEC: Lazy = Lazy::new(|| Regex::new("^Vec <(.*?) >$").unwrap()); +static CHECK_VEC_OPTION: Lazy = + Lazy::new(|| Regex::new("^Vec < Option <(.*?)> >$").unwrap()); +static CHECK_OPTION_VEC: Lazy = + Lazy::new(|| Regex::new("^Option < Vec <(.*?)> >$").unwrap()); + +/// The into argument for a derive field won't be able to transform everythings: +/// Without the specialization from Rust, we can't implement things like From between Vec -> +/// Vec or Option -> Option. +/// But there are cases which you want to have this coercion derived, so to have it working +/// until the specialization feature comes, we manually check coercion for the most usual cases +/// which are: +/// +/// - Vec -> Vec +/// - Option -> Option +/// - Option> -> Option> +/// - Vec> -> Vec> +pub fn derive_type_coercion, S2: AsRef>( + base_type: S1, + target_type: S2, +) -> DerivedIntoCoercion { + if CHECK_OPTION_VEC.find(base_type.as_ref()).is_some() + && CHECK_OPTION_VEC.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::OptionVecToOptionVec; + } + + if CHECK_VEC_OPTION.find(base_type.as_ref()).is_some() + && CHECK_VEC_OPTION.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::VecOptionToVecOption; + } + + if CHECK_VEC.find(base_type.as_ref()).is_some() + && CHECK_VEC.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::VecToVec; + } + + if CHECK_OPTION.find(base_type.as_ref()).is_some() + && CHECK_OPTION.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::OptionToOption; + } + + DerivedIntoCoercion::Unknown +} diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md index 23c1fa63..6ec4c016 100644 --- a/docs/en/src/derived_fields.md +++ b/docs/en/src/derived_fields.md @@ -59,3 +59,47 @@ type Query { duration_rfc3339(arg: String): DateRFC3339! } ``` + +## Wrapper types + +A derived field won't be able to manage everythings easily: without the specialization from the Rust language, you won't be able to implement specialized trait like: +``` +impl From> for Vec { + ... +} +``` + +So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`, so a coercion mecanism has been included so you'll be able to use the derived macro argument with `Vec` and `Option`. + +This coercion mecanism impose these derived to be `owned`. + +### Example + +```rust +#[derive(Serialize, Deserialize, Clone)] +struct ValueDerived(String); + +#[derive(Serialize, Deserialize, Clone)] +struct ValueDerived2(String); + +scalar!(ValueDerived); +scalar!(ValueDerived2); + +impl From for ValueDerived2 { + fn from(value: ValueDerived) -> Self { + ValueDerived2(value.0) + } +} + +#[derive(SimpleObject)] +struct TestObj { + #[graphql(derived(owned, name = "value2", into = "Option"))] + pub value1: Option, + #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + pub value_vec_1: Vec, + #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + pub value_opt_vec_1: Option>, + #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + pub value_vec_opt_1: Vec>, +} +``` diff --git a/src/lib.rs b/src/lib.rs index 123672b0..20d422a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -467,6 +467,8 @@ pub use async_graphql_derive::Object; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | into | Type to derived an into | string | Y | +/// | owned | Field resolver return a ownedship value | bool | Y | /// /// /// # Examples diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 304802e1..03f718ed 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -102,6 +102,68 @@ pub async fn test_derived_field_simple_object() { ); } +#[tokio::test] +pub async fn test_derived_field_simple_object_option() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize, Clone)] + struct ValueDerived(String); + + #[derive(Serialize, Deserialize, Clone)] + struct ValueDerived2(String); + + scalar!(ValueDerived); + scalar!(ValueDerived2); + + impl From for ValueDerived2 { + fn from(value: ValueDerived) -> Self { + ValueDerived2(value.0) + } + } + + #[derive(SimpleObject)] + struct TestObj { + #[graphql(derived(owned, name = "value2", into = "Option"))] + pub value1: Option, + #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + pub value_vec_1: Vec, + #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + pub value_opt_vec_1: Option>, + #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + pub value_vec_opt_1: Vec>, + } + + #[Object] + impl Query { + async fn test(&self) -> TestObj { + TestObj { + value1: Some(ValueDerived("Test".to_string())), + value_vec_1: vec![ValueDerived("Test".to_string())], + value_opt_vec_1: Some(vec![ValueDerived("Test".to_string())]), + value_vec_opt_1: vec![Some(ValueDerived("Test".to_string()))], + } + } + } + + let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2} }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "test": { + "value1": "Test", + "value2": "Test", + "valueVec1": vec!["Test"], + "valueVec2": vec!["Test"], + "valueOptVec1": vec!["Test"], + "valueOptVec2": vec!["Test"], + } + }) + ); +} + #[tokio::test] pub async fn test_derived_field_complex_object() { use serde::{Deserialize, Serialize}; From cf407adce07e14fbb73536ba38ee65d50c76b86c Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 01:42:06 +0000 Subject: [PATCH 2/5] fix: do not use extend, useless --- derive/src/simple_object.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 8a072a29..46340afb 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -218,25 +218,19 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { { - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); - result + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) } }, Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); - result + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) })) }, Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { { - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( ::std::convert::Into::into(value) - )))); - result + )))) } }, // If the field is not derived, we follow the normal process. From f3ef60033f0c90c07607394a26337f25ce8b8c0a Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 10:22:39 +0000 Subject: [PATCH 3/5] feat: use with for simple object --- derive/Cargo.toml | 2 -- derive/src/args.rs | 1 + derive/src/simple_object.rs | 66 +++++++------------------------------ derive/src/utils.rs | 60 --------------------------------- tests/derived_field.rs | 46 +++++++++++++++++++++++--- 5 files changed, 54 insertions(+), 121 deletions(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 6fb64876..9e3de277 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -23,5 +23,3 @@ Inflector = "0.11.4" proc-macro-crate = "1.0.0" darling = "0.12.2" thiserror = "1.0.24" -regex = "1.5" -once_cell = "1.8.0" diff --git a/derive/src/args.rs b/derive/src/args.rs index 5bc5123b..e2ba5e86 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -255,6 +255,7 @@ pub struct ObjectField { pub struct DerivedField { pub name: Option, pub into: Option, + pub with: Option, #[darling(default)] pub owned: Option, } diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 46340afb..a170749d 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -3,12 +3,11 @@ use proc_macro::TokenStream; use quote::quote; use std::str::FromStr; use syn::ext::IdentExt; -use syn::{Error, Ident, Type}; +use syn::{Error, Ident, Path, Type}; use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField}; use crate::utils::{ - derive_type_coercion, gen_deprecation, generate_guards, get_crate_name, get_rustdoc, - visible_fn, DerivedIntoCoercion, GeneratorResult, + gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult, }; #[derive(Debug)] @@ -16,18 +15,7 @@ struct DerivedFieldMetadata { ident: Ident, into: Type, owned: Option, - // The into argument for a derive field won't be able to transform everythings: - // Without the specialization from Rust, we can't implement things like From between Vec -> - // Vec or Option -> Option. - // But there are cases which you want to have this coercion derived, so to have it working - // until the specialization feature comes, we manually check coercion for the most usual cases - // which are: - // - // - Vec -> Vec - // - Option -> Option - // - Option> -> Option> - // - Vec> -> Vec> - coercion: DerivedIntoCoercion, + with: Option, } struct SimpleObjectFieldGenerator<'a> { @@ -88,16 +76,11 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult quote! { ::std::option::Option::None }, }; let vis = &field.vis; - let derived_coercion = if let Some(derived) = derived { - Some(&derived.coercion) - } else { - None - }; let ty = if let Some(derived) = derived { &derived.into @@ -198,6 +176,8 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { &self.#base_ident @@ -207,36 +187,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { + block = match (derived, with_function) { + (Some(_), Some(with)) => quote! { + #with(#block) + }, + (Some(_), None) => quote! { ::std::convert::Into::into(#block) }, - Some(DerivedIntoCoercion::OptionToOption) => quote! { - ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some(::std::convert::Into::into(value))) - }, - Some(DerivedIntoCoercion::VecToVec) => quote! { - { - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) - } - }, - Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { - ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) - })) - }, - Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { - { - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( - ::std::convert::Into::into(value) - )))) - } - }, - // If the field is not derived, we follow the normal process. - _ => quote! { - #block - }, + (_, _) => block, }; let ty = match !owned { diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 6843c056..394a30d4 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -1,11 +1,9 @@ use std::collections::HashSet; use darling::FromMeta; -use once_cell::sync::Lazy; use proc_macro2::{Span, TokenStream, TokenTree}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; -use regex::Regex; use syn::visit::Visit; use syn::{ Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, NestedMeta, @@ -535,61 +533,3 @@ pub fn extract_input_args( Ok(args) } - -#[derive(Debug)] -pub enum DerivedIntoCoercion { - Unknown = 1, - VecToVec = 2, - OptionToOption = 3, - OptionVecToOptionVec = 4, - VecOptionToVecOption = 5, -} - -static CHECK_OPTION: Lazy = Lazy::new(|| Regex::new("^Option <(.*?) >$").unwrap()); -static CHECK_VEC: Lazy = Lazy::new(|| Regex::new("^Vec <(.*?) >$").unwrap()); -static CHECK_VEC_OPTION: Lazy = - Lazy::new(|| Regex::new("^Vec < Option <(.*?)> >$").unwrap()); -static CHECK_OPTION_VEC: Lazy = - Lazy::new(|| Regex::new("^Option < Vec <(.*?)> >$").unwrap()); - -/// The into argument for a derive field won't be able to transform everythings: -/// Without the specialization from Rust, we can't implement things like From between Vec -> -/// Vec or Option -> Option. -/// But there are cases which you want to have this coercion derived, so to have it working -/// until the specialization feature comes, we manually check coercion for the most usual cases -/// which are: -/// -/// - Vec -> Vec -/// - Option -> Option -/// - Option> -> Option> -/// - Vec> -> Vec> -pub fn derive_type_coercion, S2: AsRef>( - base_type: S1, - target_type: S2, -) -> DerivedIntoCoercion { - if CHECK_OPTION_VEC.find(base_type.as_ref()).is_some() - && CHECK_OPTION_VEC.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::OptionVecToOptionVec; - } - - if CHECK_VEC_OPTION.find(base_type.as_ref()).is_some() - && CHECK_VEC_OPTION.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::VecOptionToVecOption; - } - - if CHECK_VEC.find(base_type.as_ref()).is_some() - && CHECK_VEC.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::VecToVec; - } - - if CHECK_OPTION.find(base_type.as_ref()).is_some() - && CHECK_OPTION.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::OptionToOption; - } - - DerivedIntoCoercion::Unknown -} diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 03f718ed..1d6abbe7 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -123,15 +123,51 @@ pub async fn test_derived_field_simple_object_option() { } } + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + fn vec_to_vec>(value: Vec) -> Vec { + value.into_iter().map(|x| x.into()).collect() + } + + fn vecopt_to_vecopt>(value: Vec>) -> Vec> { + value.into_iter().map(|x| x.map(|opt| opt.into())).collect() + } + + fn optvec_to_optvec>(value: Option>) -> Option> { + value.map(|x| x.into_iter().map(|y| y.into()).collect()) + } + #[derive(SimpleObject)] struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option"))] + #[graphql(derived( + owned, + name = "value2", + into = "Option", + with = "option_to_option" + ))] pub value1: Option, - #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + #[graphql(derived( + owned, + name = "value_vec_2", + into = "Vec", + with = "vec_to_vec" + ))] pub value_vec_1: Vec, - #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + #[graphql(derived( + owned, + name = "value_opt_vec_2", + into = "Option>", + with = "optvec_to_optvec" + ))] pub value_opt_vec_1: Option>, - #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + #[graphql(derived( + owned, + name = "value_vec_opt_2", + into = "Vec>", + with = "vecopt_to_vecopt" + ))] pub value_vec_opt_1: Vec>, } @@ -147,7 +183,7 @@ pub async fn test_derived_field_simple_object_option() { } } - let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2} }"; + let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2 } }"; let schema = Schema::new(Query, EmptyMutation, EmptySubscription); assert_eq!( schema.execute(query).await.data, From b361119ca01a147aee6b7ac76edeaaa2dc92d693 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 12:42:13 +0000 Subject: [PATCH 4/5] feat: add with for object & complex object & update documentation --- derive/src/complex_object.rs | 16 +++-- derive/src/object.rs | 16 +++-- docs/en/src/derived_fields.md | 16 ++--- src/lib.rs | 4 +- tests/derived_field.rs | 120 ++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 20 deletions(-) diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 8d282690..2926518a 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -40,6 +40,7 @@ pub fn generate( if derived.name.is_some() && derived.into.is_some() { let base_function_name = &method.sig.ident; let name = derived.name.unwrap(); + let with = derived.with; let into = Type::Verbatim( proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), ); @@ -93,11 +94,16 @@ pub fn generate( .into_iter(), ); - let new_block = quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }); + let new_block = match with { + Some(with) => quote!({ + ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) + }), + None => quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }), + }; new_impl.block = syn::parse2::(new_block).expect("invalid block"); diff --git a/derive/src/object.rs b/derive/src/object.rs index 6cfe0b79..665a9976 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -55,6 +55,7 @@ pub fn generate( if derived.name.is_some() && derived.into.is_some() { let base_function_name = &method.sig.ident; let name = derived.name.unwrap(); + let with = derived.with; let into = Type::Verbatim( proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), ); @@ -108,11 +109,16 @@ pub fn generate( .into_iter(), ); - let new_block = quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }); + let new_block = match with { + Some(with) => quote!({ + ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) + }), + None => quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }), + }; new_impl.block = syn::parse2::(new_block).expect("invalid block"); diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md index 6ec4c016..6214a58a 100644 --- a/docs/en/src/derived_fields.md +++ b/docs/en/src/derived_fields.md @@ -69,9 +69,9 @@ impl From> for Vec { } ``` -So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`, so a coercion mecanism has been included so you'll be able to use the derived macro argument with `Vec` and `Option`. +So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`. +We included a `with` parameter to help you define a function to call instead of using the `Into` trait implementation between wrapper structures. -This coercion mecanism impose these derived to be `owned`. ### Example @@ -91,15 +91,13 @@ impl From for ValueDerived2 { } } +fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) +} + #[derive(SimpleObject)] struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option"))] + #[graphql(derived(owned, name = "value2", into = "Option", with = "option_to_option"))] pub value1: Option, - #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] - pub value_vec_1: Vec, - #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] - pub value_opt_vec_1: Option>, - #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] - pub value_vec_opt_1: Vec>, } ``` diff --git a/src/lib.rs b/src/lib.rs index 20d422a5..1d80fb24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -295,6 +295,7 @@ pub type FieldResult = Result; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// # Valid field return types /// @@ -467,8 +468,8 @@ pub use async_graphql_derive::Object; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | -/// | into | Type to derived an into | string | Y | /// | owned | Field resolver return a ownedship value | bool | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// /// # Examples @@ -533,6 +534,7 @@ pub use async_graphql_derive::SimpleObject; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// # Examples /// diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 1d6abbe7..142d23d2 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -46,6 +46,60 @@ pub async fn test_derived_field_object() { ); } +#[tokio::test] +pub async fn test_derived_field_object_with() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + #[Object] + impl Query { + #[graphql(derived( + name = "value2", + into = "Option", + with = "option_to_option" + ))] + async fn value1(&self, #[graphql(default = 100)] input: i32) -> Option { + Some(input) + } + } + + let query = "{ value1 value2 }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 100, + "value2": "100", + }) + ); + + let query = "{ value1(input: 1) value2(input: 2) }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 1, + "value2": "2", + }) + ); +} + #[tokio::test] pub async fn test_derived_field_simple_object() { use serde::{Deserialize, Serialize}; @@ -261,3 +315,69 @@ pub async fn test_derived_field_complex_object() { }) ); } + +#[tokio::test] +pub async fn test_derived_field_complex_object_derived() { + use serde::{Deserialize, Serialize}; + + #[derive(SimpleObject)] + #[graphql(complex)] + struct MyObj { + a: i32, + #[graphql(owned, derived(name = "f", into = "ValueDerived"))] + b: i32, + } + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + #[ComplexObject] + impl MyObj { + async fn c(&self) -> i32 { + self.a + self.b + } + + #[graphql(derived(name = "e", into = "Option", with = "option_to_option"))] + async fn d(&self, v: i32) -> Option { + Some(self.a + self.b + v) + } + } + + struct Query; + + #[Object] + impl Query { + async fn obj(&self) -> MyObj { + MyObj { a: 10, b: 20 } + } + } + + let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + dbg!(schema.execute(query).await); + assert_eq!( + schema.execute(query).await.data, + value!({ + "obj": { + "a": 10, + "b": 20, + "c": 30, + "d": 130, + "e": "230", + "f": "20", + }, + }) + ); +} From 717a0da19228d78910fd0b955fa95175e6e8d3d1 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 15:55:00 +0000 Subject: [PATCH 5/5] misc: dbg missed --- tests/derived_field.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 142d23d2..36724c81 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -366,7 +366,6 @@ pub async fn test_derived_field_complex_object_derived() { let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - dbg!(schema.execute(query).await); assert_eq!( schema.execute(query).await.data, value!({