Impl input objects, vol.1 [skip ci]

This commit is contained in:
tyranron 2022-08-10 19:10:38 +03:00
parent d8813aff13
commit 464ff10c14
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
3 changed files with 233 additions and 22 deletions

View file

@ -64,6 +64,17 @@ where
}
}
impl<T, SV, B1, B2> resolve::ToInputValue<SV, B1> for Coerce<T, B2>
where
T: resolve::ToInputValue<SV, B2> + ?Sized,
B1: ?Sized,
B2: ?Sized,
{
fn to_input_value(&self) -> graphql::InputValue<SV> {
self.1.to_input_value()
}
}
impl<'i, T, SV, B1, B2> resolve::InputValue<'i, SV, B1> for Coerce<T, B2>
where
T: resolve::InputValue<'i, SV, B2>,

View file

@ -80,6 +80,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
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<FieldDefinition> {
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(),
})
}

View file

@ -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<SpanContainer<scalar::AttrValue>>,
/// 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<SpanContainer<behavior::Type>>,
/// 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::<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))?
}
"rename_all" => {
input.parse::<token::Eq>()?;
let val = input.parse::<syn::LitStr>()?;
@ -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<SpanContainer<String>>,
/// 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<SpanContainer<Description>>,
/// 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<SpanContainer<default::Value>>,
/// 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<SpanContainer<Description>>,
/// [coerce]: juniper::behavior::Coerce
behavior: Option<SpanContainer<behavior::Type>>,
/// 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::<token::Eq>()?;
let desc = input.parse::<Description>()?;
out.description
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
.none_or_else(|_| err::dup_arg(&ident))?
}
"default" => {
let val = input.parse::<default::Value>()?;
out.default
.replace(SpanContainer::new(ident.span(), Some(val.span()), val))
.none_or_else(|_| err::dup_arg(&ident))?
}
"desc" | "description" => {
"behave" | "behavior" => {
input.parse::<token::Eq>()?;
let desc = input.parse::<Description>()?;
out.description
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
let bh = input.parse::<behavior::Type>()?;
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<Self> {
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<default::Value>,
/// [`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 {
<Self as ::juniper::reflect::BaseType<#bh>>::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 =
&[<Self 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;
}
}
}
/// 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)
}
}