diff --git a/juniper/src/behavior.rs b/juniper/src/behavior.rs index cb1976cd..d8f8b4d2 100644 --- a/juniper/src/behavior.rs +++ b/juniper/src/behavior.rs @@ -64,6 +64,17 @@ where } } +impl resolve::ToInputValue for Coerce +where + T: resolve::ToInputValue + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue { + self.1.to_input_value() + } +} + impl<'i, T, SV, B1, B2> resolve::InputValue<'i, SV, B1> for Coerce where T: resolve::InputValue<'i, SV, B2>, diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs index 370fc200..562343ea 100644 --- a/juniper_codegen/src/graphql_input_object/derive.rs +++ b/juniper_codegen/src/graphql_input_object/derive.rs @@ -80,6 +80,7 @@ pub fn expand(input: TokenStream) -> syn::Result { description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.into(), fields, }; @@ -94,13 +95,13 @@ fn parse_field( renaming: rename::Policy, is_internal: bool, ) -> Option { - let field_attr = FieldAttr::from_attrs("graphql", &f.attrs) + let attr = FieldAttr::from_attrs("graphql", &f.attrs) .map_err(|e| proc_macro_error::emit_error!(e)) .ok()?; let ident = f.ident.as_ref().or_else(|| err_unnamed_field(f))?; - let name = field_attr + let name = attr .name .map_or_else( || renaming.apply(&ident.unraw().to_string()), @@ -114,10 +115,11 @@ fn parse_field( Some(FieldDefinition { ident: ident.clone(), ty: f.ty.clone(), - default: field_attr.default.map(SpanContainer::into_inner), + default: attr.default.map(SpanContainer::into_inner), + behavior: attr.behavior.into(), name, - description: field_attr.description.map(SpanContainer::into_inner), - ignored: field_attr.ignore.is_some(), + description: attr.description.map(SpanContainer::into_inner), + ignored: attr.ignore.is_some(), }) } diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs index 93c21d6f..9d37269f 100644 --- a/juniper_codegen/src/graphql_input_object/mod.rs +++ b/juniper_codegen/src/graphql_input_object/mod.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - default, filter_attrs, + behavior, default, filter_attrs, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -66,6 +66,17 @@ struct ContainerAttr { /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: Option>, + /// Explicitly specified type of the custom [`Behavior`] to parametrize this + /// [GraphQL input object][0] implementation with. + /// + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. + /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + behavior: Option>, + /// Explicitly specified [`rename::Policy`] for all fields of this /// [GraphQL input object][0]. /// @@ -118,6 +129,13 @@ impl Parse for ContainerAttr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -151,6 +169,7 @@ impl ContainerAttr { description: try_merge_opt!(description: self, another), context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), + behavior: try_merge_opt!(behavior: self, another), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -185,6 +204,16 @@ struct FieldAttr { /// [1]: https://spec.graphql.org/October2021#InputValueDefinition name: Option>, + /// Explicitly specified [description][2] of this + /// [GraphQL input object field][1]. + /// + /// If [`None`], then Rust doc comment will be used as the [description][2], + /// if any. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#sec-Descriptions + description: Option>, + /// Explicitly specified [default value][2] of this /// [GraphQL input object field][1] to be used used in case a field value is /// not provided. @@ -195,15 +224,18 @@ struct FieldAttr { /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option>, - /// Explicitly specified [description][2] of this - /// [GraphQL input object field][1]. + /// Explicitly specified type of the custom [`Behavior`] this + /// [GraphQL input object field][1] implementation is parametrized with, to + /// [coerce] in the generated code from. /// - /// If [`None`], then Rust doc comment will be used as the [description][2], - /// if any. + /// If [`None`], then [`behavior::Standard`] will be used for the generated + /// code. /// + /// [`Behavior`]: juniper::behavior + /// [`behavior::Standard`]: juniper::behavior::Standard /// [1]: https://spec.graphql.org/October2021#InputValueDefinition - /// [2]: https://spec.graphql.org/October2021#sec-Descriptions - description: Option>, + /// [coerce]: juniper::behavior::Coerce + behavior: Option>, /// Explicitly specified marker for the Rust struct field to be ignored and /// not included into the code generated for a [GraphQL input object][0] @@ -234,17 +266,24 @@ impl Parse for FieldAttr { )) .none_or_else(|_| err::dup_arg(&ident))? } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + .none_or_else(|_| err::dup_arg(&ident))? + } "default" => { let val = input.parse::()?; out.default .replace(SpanContainer::new(ident.span(), Some(val.span()), val)) .none_or_else(|_| err::dup_arg(&ident))? } - "desc" | "description" => { + "behave" | "behavior" => { input.parse::()?; - let desc = input.parse::()?; - out.description - .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) .none_or_else(|_| err::dup_arg(&ident))? } "ignore" | "skip" => out @@ -267,8 +306,9 @@ impl FieldAttr { fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { name: try_merge_opt!(name: self, another), - default: try_merge_opt!(default: self, another), description: try_merge_opt!(description: self, another), + default: try_merge_opt!(default: self, another), + behavior: try_merge_opt!(behavior: self, another), ignore: try_merge_opt!(ignore: self, another), }) } @@ -314,6 +354,14 @@ struct FieldDefinition { /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option, + /// [`Behavior`] parametrization of this [GraphQL input object field][1] + /// implementation to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [coerce]: juniper::behavior::Coerce + behavior: behavior::Type, + /// Name of this [GraphQL input object field][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition @@ -383,6 +431,13 @@ struct Definition { /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL input object][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + behavior: behavior::Type, + /// [Fields][1] of this [GraphQL input object][0]. /// /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects @@ -399,6 +454,14 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + //self.impl_resolve_type().to_tokens(into); + self.impl_resolve_type_name().to_tokens(into); + self.impl_resolve_to_input_value().to_tokens(into); + //self.impl_resolve_input_value().to_tokens(into); + //self.impl_graphql_input_type().to_tokens(into); + //self.impl_graphql_input_object().to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -500,6 +563,29 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::TypeName`] trait for this + /// [GraphQL input object][0]. + /// + /// [`resolve::TypeName`]: juniper::resolve::TypeName + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_resolve_type_name(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = self.mix_type_info(generics); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::TypeName<#inf, #bh> + for #ty #where_clause + { + fn type_name(_: &#inf) -> &'static str { + >::NAME + } + } + } + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL input object][0]. /// @@ -663,11 +749,52 @@ impl Definition { #where_clause { fn to_input_value(&self) -> ::juniper::InputValue<#scalar> { - ::juniper::InputValue::object( - #[allow(deprecated)] - ::std::array::IntoIter::new([#( #fields ),*]) - .collect() - ) + ::juniper::InputValue::object([#( #fields ),*]) + } + } + } + } + + /// Returns generated code implementing [`resolve::ToInputValue`] trait for + /// this [GraphQL input object][0]. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_resolve_to_input_value(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = self.mix_scalar_value(generics); + for f in &self.fields { + let field_ty = &f.ty; + let field_bh = &f.behavior; + generics.make_where_clause().predicates.push(parse_quote! { + #field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh> + }); + } + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let fields = self.fields.iter().filter_map(|f| { + let field = &f.ident; + let field_ty = &f.ty; + let field_bh = &f.behavior; + let name = &f.name; + + (!f.ignored).then(|| { + quote! { + (#name, <#field_ty as + ::juniper::resolve::ToInputValue<#sv, #field_bh>> + ::to_input_value(&self.#field)) + } + }) + }); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::ToInputValue<#sv, #bh> + for #ty #where_clause + { + fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> { + ::juniper::InputValue::object([#( #fields ),*]) } } } @@ -716,6 +843,47 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this + /// [GraphQL input object][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_reflect(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let name = &self.name; + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseType<#bh> + for #ty #where_clause + { + const NAME: ::juniper::reflect::Type = #name; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> + for #ty #where_clause + { + const NAMES: ::juniper::reflect::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl #impl_gens ::juniper::reflect::WrappedType<#bh> + for #ty #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + } + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this struct. /// @@ -775,4 +943,34 @@ impl Definition { generics } + + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + fn ty_and_generics(&self) -> (syn::Type, syn::Generics) { + let generics = self.generics.clone(); + let ty = { + let ident = &self.ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + (ty, generics) + } + + /// Mixes a type info [`syn::GenericParam`] into the provided + /// [`syn::Generics`] and returns its [`syn::Ident`]. + fn mix_type_info(&self, mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let ty = parse_quote! { __TypeInfo }; + generics.params.push(parse_quote! { #ty: ?Sized }); + (ty, generics) + } + + /// Mixes a [`ScalarValue`] [`syn::GenericParam`] into the provided + /// [`syn::Generics`] and returns it. + /// + /// [`ScalarValue`]: juniper::ScalarValue + fn mix_scalar_value(&self, mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let sv = parse_quote! { __ScalarValue }; + generics.params.push(parse_quote! { #sv }); + (sv, generics) + } }