feat: use with for simple object

This commit is contained in:
Miaxos 2021-10-28 10:22:39 +00:00
parent cf407adce0
commit f3ef60033f
5 changed files with 54 additions and 121 deletions

View File

@ -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"

View File

@ -255,6 +255,7 @@ pub struct ObjectField {
pub struct DerivedField {
pub name: Option<Ident>,
pub into: Option<String>,
pub with: Option<Path>,
#[darling(default)]
pub owned: Option<bool>,
}

View File

@ -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<bool>,
// 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<T> ->
// Vec<U> or Option<T> -> Option<U>.
// 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<T> -> Vec<U>
// - Option<T> -> Option<U>
// - Option<Vec<T>> -> Option<Vec<U>>
// - Vec<Option<T>> -> Vec<Option<U>>
coercion: DerivedIntoCoercion,
with: Option<Path>,
}
struct SimpleObjectFieldGenerator<'a> {
@ -88,16 +76,11 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
}
};
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),
with: derived.with.clone(),
};
processed_fields.push(SimpleObjectFieldGenerator {
@ -143,11 +126,6 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
None => 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<TokenStream
|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?; },
);
let with_function = derived.as_ref().and_then(|x| x.with.as_ref());
let mut block = match !owned {
true => quote! {
&self.#base_ident
@ -207,36 +187,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
},
};
// 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! {
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 {

View File

@ -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<Regex> = Lazy::new(|| Regex::new("^Option <(.*?) >$").unwrap());
static CHECK_VEC: Lazy<Regex> = Lazy::new(|| Regex::new("^Vec <(.*?) >$").unwrap());
static CHECK_VEC_OPTION: Lazy<Regex> =
Lazy::new(|| Regex::new("^Vec < Option <(.*?)> >$").unwrap());
static CHECK_OPTION_VEC: Lazy<Regex> =
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<T> ->
/// Vec<U> or Option<T> -> Option<U>.
/// 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<T> -> Vec<U>
/// - Option<T> -> Option<U>
/// - Option<Vec<T>> -> Option<Vec<U>>
/// - Vec<Option<T>> -> Vec<Option<U>>
pub fn derive_type_coercion<S1: AsRef<str>, S2: AsRef<str>>(
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
}

View File

@ -123,15 +123,51 @@ pub async fn test_derived_field_simple_object_option() {
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
fn vec_to_vec<T, U: From<T>>(value: Vec<T>) -> Vec<U> {
value.into_iter().map(|x| x.into()).collect()
}
fn vecopt_to_vecopt<T, U: From<T>>(value: Vec<Option<T>>) -> Vec<Option<U>> {
value.into_iter().map(|x| x.map(|opt| opt.into())).collect()
}
fn optvec_to_optvec<T, U: From<T>>(value: Option<Vec<T>>) -> Option<Vec<U>> {
value.map(|x| x.into_iter().map(|y| y.into()).collect())
}
#[derive(SimpleObject)]
struct TestObj {
#[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>"))]
#[graphql(derived(
owned,
name = "value2",
into = "Option<ValueDerived2>",
with = "option_to_option"
))]
pub value1: Option<ValueDerived>,
#[graphql(derived(owned, name = "value_vec_2", into = "Vec<ValueDerived2>"))]
#[graphql(derived(
owned,
name = "value_vec_2",
into = "Vec<ValueDerived2>",
with = "vec_to_vec"
))]
pub value_vec_1: Vec<ValueDerived>,
#[graphql(derived(owned, name = "value_opt_vec_2", into = "Option<Vec<ValueDerived2>>"))]
#[graphql(derived(
owned,
name = "value_opt_vec_2",
into = "Option<Vec<ValueDerived2>>",
with = "optvec_to_optvec"
))]
pub value_opt_vec_1: Option<Vec<ValueDerived>>,
#[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec<Option<ValueDerived2>>"))]
#[graphql(derived(
owned,
name = "value_vec_opt_2",
into = "Vec<Option<ValueDerived2>>",
with = "vecopt_to_vecopt"
))]
pub value_vec_opt_1: Vec<Option<ValueDerived>>,
}
@ -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,