From 23e01956b348cf2820c1366dcda330fb98412ec2 Mon Sep 17 00:00:00 2001 From: tyranron <tyranron@gmail.com> Date: Thu, 23 Jun 2022 17:42:55 +0200 Subject: [PATCH] Impl `reflect` traits for objects and interfaces [skip ci] --- juniper/src/behavior.rs | 38 ++++++- juniper_codegen/src/graphql_interface/attr.rs | 2 + .../src/graphql_interface/derive.rs | 1 + juniper_codegen/src/graphql_interface/mod.rs | 97 ++++++++++++++++- juniper_codegen/src/graphql_object/attr.rs | 1 + juniper_codegen/src/graphql_object/derive.rs | 1 + juniper_codegen/src/graphql_object/mod.rs | 100 +++++++++++++++++- juniper_codegen/src/graphql_scalar/mod.rs | 8 +- 8 files changed, 241 insertions(+), 7 deletions(-) diff --git a/juniper/src/behavior.rs b/juniper/src/behavior.rs index c98c959f..506cc384 100644 --- a/juniper/src/behavior.rs +++ b/juniper/src/behavior.rs @@ -6,7 +6,7 @@ use crate::{ graphql, meta::MetaType, parser::{ParseError, ScalarToken}, - resolve, Registry, + reflect, resolve, Registry, }; /// Default standard behavior of GraphQL types implementation. @@ -92,3 +92,39 @@ where T::parse_scalar_token(token) } } + +impl<T, B1, B2> reflect::BaseType<B1> for Coerce<T, B2> +where + T: reflect::BaseType<B2> + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAME: reflect::Type = T::NAME; +} + +impl<T, B1, B2> reflect::BaseSubTypes<B1> for Coerce<T, B2> +where + T: reflect::BaseSubTypes<B2> + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} + +impl<T, B1, B2> reflect::WrappedType<B1> for Coerce<T, B2> +where + T: reflect::WrappedType<B2> + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const VALUE: reflect::WrappedValue = T::VALUE; +} + +impl<T, B1, B2> reflect::Implements<B1> for Coerce<T, B2> +where + T: reflect::Implements<B2> + ?Sized, + B1: ?Sized, + B2: ?Sized, +{ + const NAMES: reflect::Types = T::NAMES; +} diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 746b7af7..ba7a456d 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -123,6 +123,7 @@ fn expand_on_trait( description: attr.description.as_deref().cloned(), context, scalar, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), fields, implemented_for: attr .implemented_for @@ -304,6 +305,7 @@ fn expand_on_derive_input( description: attr.description.as_deref().cloned(), context, scalar, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), fields, implemented_for: attr .implemented_for diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs index e113bdce..bf60e5cf 100644 --- a/juniper_codegen/src/graphql_interface/derive.rs +++ b/juniper_codegen/src/graphql_interface/derive.rs @@ -96,6 +96,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> { description: attr.description.as_deref().cloned(), context, scalar, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), fields, implemented_for: attr .implemented_for diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 2114e2cf..fae35157 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -21,7 +21,7 @@ use syn::{ use crate::{ common::{ - field, gen, + behavior, field, gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, @@ -111,6 +111,17 @@ struct Attr { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces scalar: Option<SpanContainer<scalar::AttrValue>>, + /// Explicitly specified type of the custom [`Behavior`] to parametrize this + /// [GraphQL interface][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-Interfaces + behavior: Option<SpanContainer<behavior::Type>>, + /// Explicitly specified marker indicating that the Rust trait should be /// transformed into [`async_trait`]. /// @@ -173,6 +184,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::<token::Eq>()?; + let bh = input.parse::<behavior::Type>()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "for" | "implementers" => { input.parse::<token::Eq>()?; for impler in input.parse_maybe_wrapped_and_punctuated::< @@ -231,6 +249,7 @@ impl Attr { 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), implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), @@ -309,6 +328,13 @@ struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL interface][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + behavior: behavior::Type, + /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -347,6 +373,8 @@ impl ToTokens for Definition { self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_reflect().to_tokens(into); } } @@ -847,6 +875,59 @@ impl Definition { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`], [`reflect::WrappedType`] and + /// [`reflect::Fields`] traits for this [GraphQL interface][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::Fields`]: juniper::reflect::Fields + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Interfaces + 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; + let implers = &self.implemented_for; + let fields = self.fields.iter().map(|f| &f.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 = &[ + <Self as ::juniper::reflect::BaseType<#bh>>::NAME, + #( <#implers as ::juniper::reflect::BaseType<#bh>>::NAME ),* + ]; + } + + #[automatically_derived] + impl#impl_gens ::juniper::reflect::WrappedType<#bh> for #ty + #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + + #[automatically_derived] + impl#impl_gens ::juniper::reflect::Fields<#bh> for #ty + #where_clause + { + const NAMES: ::juniper::reflect::Names = &[#( #fields ),*]; + } + } + } + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// @@ -1267,6 +1348,20 @@ 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.enum_alias_ident; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident#ty_gen } + }; + + (ty, generics) + } + /// Indicates whether this enum has non-exhaustive phantom variant to hold /// type parameters. #[must_use] diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs index 34a70c21..5461bf84 100644 --- a/juniper_codegen/src/graphql_object/attr.rs +++ b/juniper_codegen/src/graphql_object/attr.rs @@ -121,6 +121,7 @@ where description: attr.description.map(SpanContainer::into_inner), context, scalar, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), fields, interfaces: attr .interfaces diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs index 7cbe961b..e20f533c 100644 --- a/juniper_codegen/src/graphql_object/derive.rs +++ b/juniper_codegen/src/graphql_object/derive.rs @@ -98,6 +98,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar, + behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(), fields, interfaces: attr .interfaces diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 0f3b05ac..c51cab09 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -18,7 +18,7 @@ use syn::{ use crate::{ common::{ - field, gen, + behavior, field, gen, parse::{ attr::{err, OptionExt as _}, GenericsExt as _, ParseBufferExt as _, TypeExt, @@ -73,6 +73,17 @@ pub(crate) struct Attr { /// [1]: https://spec.graphql.org/June2018/#sec-Objects pub(crate) scalar: Option<SpanContainer<scalar::AttrValue>>, + /// Explicitly specified type of the custom [`Behavior`] to parametrize this + /// [GraphQL object][0] type 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-Objects + pub(crate) behavior: Option<SpanContainer<behavior::Type>>, + /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1] /// type implements. /// @@ -135,6 +146,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::<token::Eq>()?; + let bh = input.parse::<behavior::Type>()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "impl" | "implements" | "interfaces" => { input.parse::<token::Eq>()?; for iface in input.parse_maybe_wrapped_and_punctuated::< @@ -180,6 +198,7 @@ impl Attr { 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), interfaces: try_merge_hashset!(interfaces: self, another => span_joined), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, @@ -245,6 +264,13 @@ pub(crate) struct Definition<Operation: ?Sized> { /// [1]: https://spec.graphql.org/June2018/#sec-Objects pub(crate) scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL object][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Objects + pub(crate) behavior: behavior::Type, + /// Defined [GraphQL fields][2] of this [GraphQL object][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Objects @@ -329,6 +355,13 @@ impl<Operation: ?Sized + 'static> Definition<Operation> { (quote! { #impl_generics }, where_clause.cloned()) } + /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait + /// implementation. + #[must_use] + fn ty_and_generics(&self) -> (&syn::Type, syn::Generics) { + (&self.ty, self.generics.clone()) + } + /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL object][1]. /// @@ -423,6 +456,69 @@ impl<Operation: ?Sized + 'static> Definition<Operation> { } } + /// Returns generated code implementing [`reflect::BaseType`], + /// [`reflect::BaseSubTypes`], [`reflect::Implements`], + /// [`reflect::WrappedType`] and [`reflect::Fields`] traits for this + /// [GraphQL object][0]. + /// + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::Fields`]: juniper::reflect::Fields + /// [`reflect::Implements`]: juniper::reflect::Implements + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType + /// [0]: https://spec.graphql.org/October2021#sec-Objects + #[must_use] + pub(crate) 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; + let interfaces = self.interfaces.iter(); + let fields = self.fields.iter().map(|f| &f.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 = + &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME]; + } + + #[automatically_derived] + impl#impl_gens ::juniper::reflect::Implements<#bh> for #ty + #where_clause + { + const NAMES: ::juniper::reflect::Types = &[#( + <#interfaces as ::juniper::reflect::BaseType<#bh>>::NAME + ),*]; + } + + #[automatically_derived] + impl#impl_gens ::juniper::reflect::WrappedType<#bh> for #ty + #where_clause + { + const VALUE: ::juniper::reflect::WrappedValue = + ::juniper::reflect::wrap::SINGULAR; + } + + #[automatically_derived] + impl#impl_gens ::juniper::reflect::Fields<#bh> for #ty + #where_clause + { + const NAMES: ::juniper::reflect::Names = &[#( #fields ),*]; + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -504,6 +600,8 @@ impl ToTokens for Definition<Query> { self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); + //////////////////////////////////////////////////////////////////////// + self.impl_reflect().to_tokens(into); } } diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 876ec9fa..64370796 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -971,9 +971,9 @@ impl Definition { /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this /// [GraphQL scalar][0]. /// - /// [`reflect::BaseSubTypes`]: juniper::reflection::BaseSubTypes - /// [`reflect::BaseType`]: juniper::reflection::BaseType - /// [`reflect::WrappedType`]: juniper::reflection::WrappedType + /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes + /// [`reflect::BaseType`]: juniper::reflect::BaseType + /// [`reflect::WrappedType`]: juniper::reflect::WrappedType /// [0]: https://spec.graphql.org/October2021#sec-Scalars fn impl_reflect(&self) -> TokenStream { let bh = &self.behavior; @@ -1087,7 +1087,7 @@ impl Definition { let ty = { let ident = &self.ident; - let (_, ty_gen, _) = self.generics.split_for_impl(); + let (_, ty_gen, _) = generics.split_for_impl(); parse_quote! { #ident#ty_gen } };