Impl reflect traits for objects and interfaces [skip ci]

This commit is contained in:
tyranron 2022-06-23 17:42:55 +02:00
parent 42e2780142
commit 23e01956b3
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
8 changed files with 241 additions and 7 deletions

View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}
}

View file

@ -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 }
};