Impl macro for scalars, vol.4 [skip ci]

This commit is contained in:
tyranron 2022-06-19 22:18:50 +02:00
parent 679c1c1162
commit 1aafd3da5d
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
4 changed files with 157 additions and 63 deletions

View file

@ -464,12 +464,12 @@ impl<S> InputValue<S> {
/// Maps the [`ScalarValue`] type of this [`InputValue`] into the specified /// Maps the [`ScalarValue`] type of this [`InputValue`] into the specified
/// one. /// one.
pub fn map_scalar_value<To>(self) -> InputValue<To> pub fn map_scalar_value<Into>(self) -> InputValue<Into>
where where
To: From<S> + 'static, S: ScalarValue,
S: 'static, Into: ScalarValue,
{ {
if TypeId::of::<To>() == TypeId::of::<S>() { if TypeId::of::<Into>() == TypeId::of::<S>() {
// SAFETY: This is safe, because we're transmuting the value into // SAFETY: This is safe, because we're transmuting the value into
// itself, so no invariants may change and we're just // itself, so no invariants may change and we're just
// satisfying the type checker. // satisfying the type checker.
@ -481,7 +481,7 @@ impl<S> InputValue<S> {
} else { } else {
match self { match self {
Self::Null => InputValue::Null, Self::Null => InputValue::Null,
Self::Scalar(s) => InputValue::Scalar(s.into()), Self::Scalar(s) => InputValue::Scalar(s.into_another()),
Self::Enum(v) => InputValue::Enum(v), Self::Enum(v) => InputValue::Enum(v),
Self::Variable(n) => InputValue::Variable(n), Self::Variable(n) => InputValue::Variable(n),
Self::List(l) => InputValue::List( Self::List(l) => InputValue::List(

View file

@ -6,6 +6,7 @@ pub use crate::{
macros::{input_value, value, vars}, macros::{input_value, value, vars},
resolve::Type, resolve::Type,
value::Value, value::Value,
GraphQLScalar as Scalar,
}; };
/* /*

View file

@ -1,5 +1,7 @@
//! Code generation for `#[derive(GraphQLScalar)]` macro. //! Code generation for `#[derive(GraphQLScalar)]` macro.
use std::convert::TryFrom;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::{parse_quote, spanned::Spanned}; use syn::{parse_quote, spanned::Spanned};
@ -84,11 +86,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
.first() .first()
.filter(|_| fields.unnamed.len() == 1) .filter(|_| fields.unnamed.len() == 1)
.cloned() .cloned()
.map(|f| Field { .map(Field::try_from)
itself: f, .transpose()?
is_named: false,
behavior: None.unwrap_or_default(), // TODO: Parse attribute!
})
.ok_or_else(|| { .ok_or_else(|| {
ERR.custom_error( ERR.custom_error(
ast.span(), ast.span(),
@ -101,11 +100,8 @@ pub(super) fn parse_derived_methods(ast: &syn::DeriveInput, attr: &Attr) -> syn:
.first() .first()
.filter(|_| fields.named.len() == 1) .filter(|_| fields.named.len() == 1)
.cloned() .cloned()
.map(|f| Field { .map(Field::try_from)
itself: f, .transpose()?
is_named: true,
behavior: None.unwrap_or_default(), // TODO: Parse attribute!
})
.ok_or_else(|| { .ok_or_else(|| {
ERR.custom_error( ERR.custom_error(
ast.span(), ast.span(),

View file

@ -2,6 +2,8 @@
//! //!
//! [1]: https://spec.graphql.org/October2021#sec-Scalars //! [1]: https://spec.graphql.org/October2021#sec-Scalars
use std::convert::TryFrom;
use proc_macro2::{Literal, TokenStream}; use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
@ -536,10 +538,10 @@ impl Definition {
let predicates = &mut generics.make_where_clause().predicates; let predicates = &mut generics.make_where_clause().predicates;
predicates.push(parse_quote! { #sv: Clone }); predicates.push(parse_quote! { #sv: Clone });
predicates.push(parse_quote! { predicates.push(parse_quote! {
::juniper::behavior::Coerce<Self, #bh>: ::juniper::behavior::Coerce<Self>:
::juniper::resolve::TypeName<#inf, #bh> ::juniper::resolve::TypeName<#inf>
+ ::juniper::resolve::ScalarToken<#sv, #bh> + ::juniper::resolve::ScalarToken<#sv>
+ ::juniper::resolve::InputValueOwned<#sv, #bh> + ::juniper::resolve::InputValueOwned<#sv>
}); });
let (impl_gens, _, where_clause) = generics.split_for_impl(); let (impl_gens, _, where_clause) = generics.split_for_impl();
@ -566,7 +568,7 @@ impl Definition {
#sv: '__r, #sv: '__r,
{ {
registry.register_scalar_with::< registry.register_scalar_with::<
::juniper::behavior::Coerce<Self, #bh>, _, _, ::juniper::behavior::Coerce<Self>, _, _,
>(type_info, |meta| { >(type_info, |meta| {
meta#description meta#description
#specified_by_url #specified_by_url
@ -808,27 +810,25 @@ impl Definition {
let (sv, mut generics) = self.mix_scalar_value(generics); let (sv, mut generics) = self.mix_scalar_value(generics);
let lt: syn::GenericParam = parse_quote! { '__inp }; let lt: syn::GenericParam = parse_quote! { '__inp };
generics.params.push(lt.clone()); generics.params.push(lt.clone());
generics let predicates = &mut generics.make_where_clause().predicates;
.make_where_clause() predicates.push(parse_quote! { #sv: #lt });
.predicates predicates.extend(self.methods.bound_try_from_input_value(&lt, sv, bh));
.extend(self.methods.bound_try_from_input_value(&lt, sv, bh));
let (impl_gens, _, where_clause) = generics.split_for_impl(); let (impl_gens, _, where_clause) = generics.split_for_impl();
let conversion = self.methods.expand_try_from_input_value(sv, bh); let error_ty = self.methods.expand_try_from_input_value_error(&lt, sv, bh);
let body = self.methods.expand_try_from_input_value(sv, bh);
quote! { quote! {
#[automatically_derived] #[automatically_derived]
impl#impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh> for #ty impl#impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh> for #ty
#where_clause #where_clause
{ {
type Error = ::juniper::FieldError<#sv>; type Error = #error_ty;
fn try_from_input_value( fn try_from_input_value(
input: &#lt ::juniper::graphql::InputValue<#sv>, input: &#lt ::juniper::graphql::InputValue<#sv>,
) -> ::std::result::Result<Self, Self::Error> { ) -> ::std::result::Result<Self, Self::Error> {
#conversion.map_err( #body
::juniper::IntoFieldError::<#sv>::into_field_error,
)
} }
} }
} }
@ -1363,13 +1363,14 @@ impl Methods {
from_input: Some(from_input), from_input: Some(from_input),
.. ..
} => { } => {
let map_sv = sv.custom.as_ref().map(|custom_ty| { let map_sv = sv.custom.is_some().then(|| {
quote! { quote! { .map_scalar_value() }
.map_scalar_value()
}
}); });
quote! { quote! {
#from_input(input#map_sv) #from_input(input#map_sv)
.map_err(
::juniper::IntoFieldError::<#sv>::into_field_error,
)
} }
} }
@ -1382,13 +1383,45 @@ impl Methods {
<::juniper::behavior::Coerce<#field_ty, #bh> as <::juniper::behavior::Coerce<#field_ty, #bh> as
::juniper::resolve::InputValue<'_, #sv, #field_bh>> ::juniper::resolve::InputValue<'_, #sv, #field_bh>>
::try_from_input_value(input) ::try_from_input_value(input)
.into_inner() .map(::juniper::behavior::Coerce::into_inner)
.map(#self_constructor) .map(#self_constructor)
} }
} }
} }
} }
/// Expands error type of [`resolve::InputValue`] trait.
///
/// [`resolve::InputValue`]: juniper::resolve::InputValue
fn expand_try_from_input_value_error(
&self,
lt: &syn::GenericParam,
sv: &ScalarValue,
bh: &behavior::Type,
) -> syn::Type {
match self {
Self::Custom { .. }
| Self::Delegated {
from_input: Some(_),
..
} => {
parse_quote! {
::juniper::FieldError<#sv>
}
}
Self::Delegated { field, .. } => {
let field_ty = field.ty();
let field_bh = &field.behavior;
parse_quote! {
<::juniper::behavior::Coerce<#field_ty, #bh> as
::juniper::resolve::InputValue<#lt, #sv, #field_bh>>::Error
}
}
}
}
/// Generates additional trait bounds for [`resolve::InputValue`] /// Generates additional trait bounds for [`resolve::InputValue`]
/// implementation allowing to execute /// implementation allowing to execute
/// [`resolve::InputValue::try_from_input_value()`][0] method. /// [`resolve::InputValue::try_from_input_value()`][0] method.
@ -1407,15 +1440,15 @@ impl Methods {
from_input: Some(_), from_input: Some(_),
.. ..
} => { } => {
let mut bounds = vec![parse_quote! {
#sv: ::juniper::ScalarValue
}];
if let Some(custom_sv) = &sv.custom { if let Some(custom_sv) = &sv.custom {
vec![ bounds.push(parse_quote! {
parse_quote! { #custom_sv: ::std::convert::From<#sv> }, #custom_sv: ::juniper::ScalarValue
parse_quote! { #custom_sv: 'static }, });
parse_quote! { #sv: 'static },
]
} else {
vec![parse_quote! { #sv: ::juniper::ScalarValue }]
} }
bounds
} }
Self::Delegated { field, .. } => { Self::Delegated { field, .. } => {
@ -1557,10 +1590,8 @@ impl ParseToken {
fn expand_parse_scalar_token(&self, sv: &ScalarValue, bh: &behavior::Type) -> TokenStream { fn expand_parse_scalar_token(&self, sv: &ScalarValue, bh: &behavior::Type) -> TokenStream {
match self { match self {
Self::Custom(parse_token) => { Self::Custom(parse_token) => {
let into = sv.custom.as_ref().map(|custom_ty| { let into = sv.custom.is_some().then(|| {
quote! { quote! { .map(::juniper::ScalarValue::into_another) }
.map(<#sv as ::std::convert::From<#custom_ty>>::from)
}
}); });
quote! { quote! {
#parse_token(token)#into #parse_token(token)#into
@ -1606,15 +1637,15 @@ impl ParseToken {
) -> Vec<syn::WherePredicate> { ) -> Vec<syn::WherePredicate> {
match self { match self {
Self::Custom(_) => { Self::Custom(_) => {
vec![if let Some(custom_sv) = &sv.custom { let mut bounds = vec![parse_quote! {
parse_quote! {
#sv: ::std::convert::From<#custom_sv>
}
} else {
parse_quote! {
#sv: ::juniper::ScalarValue #sv: ::juniper::ScalarValue
}];
if let Some(custom_sv) = &sv.custom {
bounds.push(parse_quote! {
#custom_sv: ::juniper::ScalarValue
});
} }
}] bounds
} }
Self::Delegated(delegated) => delegated Self::Delegated(delegated) => delegated
@ -1630,6 +1661,65 @@ impl ParseToken {
} }
} }
/// Available arguments behind `#[graphql]` attribute on a [`Field`] when
/// generating code for a [GraphQL scalar][0] implementation.
///
/// [0]: https://spec.graphql.org/October2021#sec-Scalars
#[derive(Debug, Default)]
struct FieldAttr {
/// Explicitly specified type of the custom [`Behavior`] used for
/// [GraphQL scalar][0] implementation by the [`Field`].
///
/// 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-Scalars
behavior: Option<SpanContainer<behavior::Type>>,
}
impl Parse for FieldAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut out = Self::default();
while !input.is_empty() {
let ident = input.parse_any_ident()?;
match ident.to_string().as_str() {
"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))?
}
name => {
return Err(err::unknown_arg(&ident, name));
}
}
input.try_parse::<token::Comma>()?;
}
Ok(out)
}
}
impl FieldAttr {
/// Tries to merge two [`FieldAttr`]s into a single one, reporting about
/// duplicates, if any.
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
Ok(Self {
behavior: try_merge_opt!(behavior: self, another),
})
}
/// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s
/// placed on a field definition.
fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
filter_attrs(name, attrs)
.map(|attr| attr.parse_args())
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))
}
}
/// Inner field of a type implementing [GraphQL scalar][0], that the /// Inner field of a type implementing [GraphQL scalar][0], that the
/// implementation delegates calls to. /// implementation delegates calls to.
/// ///
@ -1638,19 +1728,28 @@ struct Field {
/// This [`Field`] itself. /// This [`Field`] itself.
itself: syn::Field, itself: syn::Field,
/// Indicator whether this [`Field`] is named.
is_named: bool,
/// [`Behavior`] parametrization of this [`Field`]. /// [`Behavior`] parametrization of this [`Field`].
/// ///
/// [`Behavior`]: juniper::behavior /// [`Behavior`]: juniper::behavior
behavior: behavior::Type, behavior: behavior::Type,
} }
impl TryFrom<syn::Field> for Field {
type Error = syn::Error;
fn try_from(field: syn::Field) -> syn::Result<Self> {
let attr = FieldAttr::from_attrs("graphql", &field.attrs)?;
Ok(Self {
itself: field,
behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
})
}
}
impl ToTokens for Field { impl ToTokens for Field {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
if self.is_named { if let Some(name) = &self.itself.ident {
self.itself.ident.to_tokens(tokens) name.to_tokens(tokens)
} else { } else {
tokens.append(Literal::u8_unsuffixed(0)) tokens.append(Literal::u8_unsuffixed(0))
} }
@ -1668,10 +1767,8 @@ impl Field {
/// ///
/// [0]: https://spec.graphql.org/October2021#sec-Scalars /// [0]: https://spec.graphql.org/October2021#sec-Scalars
fn closure_constructor(&self) -> TokenStream { fn closure_constructor(&self) -> TokenStream {
if self.is_named { if let Some(name) = &self.itself.ident {
let ident = &self.itself.ident; quote! { |v| Self { #name: v } }
quote! { |v| Self { #ident: v } }
} else { } else {
quote! { Self } quote! { Self }
} }