Impl input objects, vol.2 [skip ci]

This commit is contained in:
tyranron 2022-08-11 18:06:43 +03:00
parent 464ff10c14
commit fea722b196
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
7 changed files with 386 additions and 25 deletions

View file

@ -64,17 +64,6 @@ 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

@ -1263,6 +1263,16 @@ impl<'r, S: 'r> Registry<'r, S> {
Argument::new(name, self.get_type::<T>(info)).default_value(value.to_input_value())
}
/// Creates an [`Argument`] with the provided `name`.
pub fn arg_reworked<'ti, T, TI>(&mut self, name: &str, type_info: &'ti TI) -> Argument<'r, S>
where
T: resolve::Type<TI, S> + resolve::InputValueOwned<S>,
TI: ?Sized,
'ti: 'r,
{
Argument::new(name, T::meta(self, type_info).as_type())
}
fn insert_placeholder(&mut self, name: Name, of_type: Type<'r>) {
self.types
.entry(name)
@ -1531,4 +1541,39 @@ impl<'r, S: 'r> Registry<'r, S> {
InputObjectMeta::new::<T>(Cow::Owned(name.into()), args)
}
/// Builds an [`InputObjectMeta`] information for the specified
/// [`graphql::Type`], allowing to `customize` the created [`ScalarMeta`],
/// and stores it in this [`Registry`].
///
/// # Idempotent
///
/// If this [`Registry`] contains a [`MetaType`] with such [`TypeName`]
/// already, then just returns it without doing anything.
///
/// [`graphql::Type`]: resolve::Type
/// [`TypeName`]: resolve::TypeName
pub fn register_input_object_with<'ti, T, TI, F>(
&mut self,
fields: &[Argument<'r, S>],
type_info: &'ti TI,
customize: F,
) -> MetaType<'r, S>
where
T: resolve::TypeName<TI> + resolve::InputValueOwned<S>,
TI: ?Sized,
'ti: 'r,
F: FnOnce(InputObjectMeta<'r, S>) -> InputObjectMeta<'r, S>,
S: Clone,
{
self.entry_type::<T, _>(type_info)
.or_insert_with(move || {
customize(InputObjectMeta::new_reworked::<T, _>(
T::type_name(type_info),
fields,
))
.into_meta()
})
.clone()
}
}

View file

@ -47,6 +47,16 @@ pub trait Object<S>: OutputType<S>
fn assert_object();
}*/
pub trait InputObject<
'inp,
TypeInfo: ?Sized,
ScalarValue: 'inp,
Behavior: ?Sized = behavior::Standard,
>: InputType<'inp, TypeInfo, ScalarValue, Behavior>
{
fn assert_input_object();
}
pub trait Scalar<
'inp,
TypeInfo: ?Sized,

View file

@ -769,6 +769,25 @@ impl<'a, S> InputObjectMeta<'a, S> {
}
}
/// Builds a new [`InputObjectMeta`] information with the specified `name`
/// and its `fields`.
// TODO: Use `impl Into<Cow<'a, str>>` argument once feature
// `explicit_generic_args_with_impl_trait` hits stable:
// https://github.com/rust-lang/rust/issues/83701
pub fn new_reworked<T, N>(name: N, fields: &[Argument<'a, S>]) -> Self
where
T: resolve::InputValueOwned<S>,
Cow<'a, str>: From<N>,
S: Clone,
{
Self {
name: name.into(),
description: None,
input_fields: fields.to_vec(),
try_parse_fn: try_parse_fn_reworked::<T, S>,
}
}
/// Set the `description` of this [`InputObjectMeta`] type.
///
/// Overwrites any previously set description.

View file

@ -533,9 +533,14 @@ impl Definition {
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::graphql::Enum<#lt, #inf, #cx, #sv, #bh>
for #ty #where_clause
for #ty #where_clause
{
fn assert_enum() {}
fn assert_enum() {
<Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
::assert_input_type();
<Self as ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>>
::assert_output_type();
}
}
}
}

View file

@ -386,6 +386,14 @@ struct FieldDefinition {
ignored: bool,
}
impl FieldDefinition {
/// Indicates whether this [`FieldDefinition`] uses [`Default::default()`]
/// ans its [`FieldDefinition::default`] value.
fn needs_default_trait_bound(&self) -> bool {
matches!(self.default, Some(default::Value::Default))
}
}
/// Representation of [GraphQL input object][0] for code generation.
///
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
@ -455,12 +463,12 @@ impl ToTokens for Definition {
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().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_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);
}
}
@ -503,6 +511,88 @@ impl Definition {
}
}
/// Returns generated code implementing [`graphql::InputType`] trait for
/// [GraphQL input object][0].
///
/// [`graphql::InputType`]: juniper::graphql::InputType
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
#[must_use]
fn impl_graphql_input_type(&self) -> TokenStream {
let bh = &self.behavior;
let (ty, generics) = self.ty_and_generics();
let (inf, generics) = self.mix_type_info(generics);
let (sv, generics) = self.mix_scalar_value(generics);
let (lt, mut generics) = self.mix_input_lifetime(generics, &sv);
generics.make_where_clause().predicates.push(parse_quote! {
Self: ::juniper::resolve::Type<#inf, #sv, #bh>
+ ::juniper::resolve::ToInputValue<#sv, #bh>
+ ::juniper::resolve::InputValue<#lt, #sv, #bh>
});
for f in self.fields.iter().filter(|f| !f.ignored) {
let field_ty = &f.ty;
let field_bh = &f.behavior;
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty:
::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh>
});
}
let (impl_gens, _, where_clause) = generics.split_for_impl();
let fields_assertions = self.fields.iter().filter_map(|f| {
(!f.ignored).then(|| {
let field_ty = &f.ty;
let field_bh = &f.behavior;
quote! {
<#field_ty as
::juniper::graphql::InputType<#lt, #inf, #sv, #field_bh>>
::assert_input_type();
}
})
});
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
for #ty #where_clause
{
fn assert_input_type() {
#( #fields_assertions )*
}
}
}
}
/// Returns generated code implementing [`graphql::InputObject`] trait for
/// this [GraphQL input object][0].
///
/// [`graphql::InputObject`]: juniper::graphql::InputObject
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
#[must_use]
fn impl_graphql_input_object(&self) -> TokenStream {
let bh = &self.behavior;
let (ty, generics) = self.ty_and_generics();
let (inf, generics) = self.mix_type_info(generics);
let (sv, generics) = self.mix_scalar_value(generics);
let (lt, mut generics) = self.mix_input_lifetime(generics, &sv);
generics.make_where_clause().predicates.push(parse_quote! {
Self: ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>
});
let (impl_gens, _, where_clause) = generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::graphql::InputObject<#lt, #inf, #sv, #bh>
for #ty #where_clause
{
fn assert_input_object() {
<Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
::assert_input_type();
}
}
}
}
/// Returns generated code implementing [`GraphQLType`] trait for this
/// [GraphQL input object][0].
///
@ -563,6 +653,96 @@ impl Definition {
}
}
/// Returns generated code implementing [`resolve::Type`] trait for this
/// [GraphQL input object][0].
///
/// [`resolve::Type`]: juniper::resolve::Type
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
fn impl_resolve_type(&self) -> TokenStream {
let bh = &self.behavior;
let (ty, generics) = self.ty_and_generics();
let (inf, generics) = self.mix_type_info(generics);
let (sv, mut generics) = self.mix_scalar_value(generics);
let preds = &mut generics.make_where_clause().predicates;
preds.push(parse_quote! { #sv: Clone });
preds.push(parse_quote! {
::juniper::behavior::Coerce<Self>:
::juniper::resolve::TypeName<#inf>
+ ::juniper::resolve::InputValueOwned<#sv>
});
for f in self.fields.iter().filter(|f| !f.ignored) {
let field_ty = &f.ty;
let field_bh = &f.behavior;
preds.push(parse_quote! {
::juniper::behavior::Coerce<#field_ty>:
::juniper::resolve::Type<#inf, #sv>
+ ::juniper::resolve::InputValueOwned<#sv>
});
if f.default.is_some() {
preds.push(parse_quote! {
#field_ty: ::juniper::resolve::ToInputValue<#sv, #field_bh>
});
}
if f.needs_default_trait_bound() {
preds.push(parse_quote! {
#field_ty: ::std::default::Default
});
}
}
let (impl_gens, _, where_clause) = generics.split_for_impl();
let description = &self.description;
let fields_meta = self.fields.iter().filter_map(|f| {
(!f.ignored).then(|| {
let f_ty = &f.ty;
let f_bh = &f.behavior;
let f_name = &f.name;
let f_description = &f.description;
let f_default = f.default.as_ref().map(|expr| {
quote! {
.default_value(
<#f_ty as
::juniper::resolve::ToInputValue<#sv, #f_bh>>
::to_input_value(&{ #expr }),
)
}
});
quote! {
registry.arg_reworked::<
::juniper::behavior::Coerce<#f_ty>, _,
>(#f_name, type_info)
#f_description
#f_default
}
})
});
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::resolve::Type<#inf, #sv, #bh>
for #ty #where_clause
{
fn meta<'__r, '__ti: '__r>(
registry: &mut ::juniper::Registry<'__r, #sv>,
type_info: &'__ti #inf,
) -> ::juniper::meta::MetaType<'__r, #sv>
where
#sv: '__r,
{
let fields = [#( #fields_meta ),*];
registry.register_input_object_with::<
::juniper::behavior::Coerce<Self>, _, _,
>(&fields, type_info, |meta| {
meta #description
})
}
}
}
}
/// Returns generated code implementing [`resolve::TypeName`] trait for this
/// [GraphQL input object][0].
///
@ -717,6 +897,96 @@ impl Definition {
}
}
/// Returns generated code implementing [`resolve::InputValue`] trait for
/// this [GraphQL input object][0].
///
/// [`resolve::InputValue`]: juniper::resolve::InputValue
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
fn impl_resolve_input_value(&self) -> TokenStream {
let bh = &self.behavior;
let (ty, generics) = self.ty_and_generics();
let (sv, generics) = self.mix_scalar_value(generics);
let (lt, mut generics) = self.mix_input_lifetime(generics, &sv);
generics.make_where_clause().predicates.push(parse_quote! {
#sv: ::juniper::ScalarValue
});
for f in self.fields.iter().filter(|f| !f.ignored) {
let field_ty = &f.ty;
let field_bh = &f.behavior;
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: ::juniper::resolve::InputValue<#lt, #sv, #field_bh>
});
}
for f in self.fields.iter().filter(|f| f.needs_default_trait_bound()) {
let field_ty = &f.ty;
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: ::std::default::Default,
});
}
let (impl_gens, _, where_clause) = generics.split_for_impl();
let fields = self.fields.iter().map(|f| {
let field = &f.ident;
let field_ty = &f.ty;
let field_bh = &f.behavior;
let constructor = if f.ignored {
let expr = f.default.clone().unwrap_or_default();
quote! { #expr }
} else {
let name = &f.name;
let fallback = f.default.as_ref().map_or_else(
|| {
quote! {
<#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>
::try_from_implicit_null()
.map_err(::juniper::IntoFieldError::<#sv>::into_field_error)?
}
},
|expr| quote! { #expr },
);
quote! {
match obj.get(#name) {
::std::option::Option::Some(v) => {
<#field_ty as ::juniper::resolve::InputValue<#lt, #sv, #field_bh>>
::try_from_input_value(v)
.map_err(::juniper::IntoFieldError::<#sv>::into_field_error)?
}
::std::option::Option::None => { #fallback }
}
}
};
quote! { #field: { #constructor }, }
});
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::resolve::InputValue<#lt, #sv, #bh>
for #ty #where_clause
{
type Error = ::juniper::FieldError<#sv>;
fn try_from_input_value(
input: &#lt ::juniper::graphql::InputValue<#sv>,
) -> ::std::result::Result<Self, Self::Error> {
let obj = input
.to_object_value()
.ok_or_else(|| ::std::format!(
"Expected input object, found: {}", input,
))?;
::std::result::Result::Ok(Self {
#( #fields )*
})
}
}
}
}
/// Returns generated code implementing [`ToInputValue`] trait for this
/// [GraphQL input object][0].
///
@ -764,7 +1034,7 @@ impl Definition {
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 {
for f in self.fields.iter().filter(|f| !f.ignored) {
let field_ty = &f.ty;
let field_bh = &f.behavior;
generics.make_where_clause().predicates.push(parse_quote! {
@ -774,12 +1044,12 @@ impl Definition {
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(|| {
let field = &f.ident;
let field_ty = &f.ty;
let field_bh = &f.behavior;
let name = &f.name;
quote! {
(#name, <#field_ty as
::juniper::resolve::ToInputValue<#sv, #field_bh>>
@ -973,4 +1243,22 @@ impl Definition {
generics.params.push(parse_quote! { #sv });
(sv, generics)
}
/// Mixes an [`InputValue`]'s lifetime [`syn::GenericParam`] into the
/// provided [`syn::Generics`] and returns it.
///
/// [`InputValue`]: juniper::resolve::InputValue
fn mix_input_lifetime(
&self,
mut generics: syn::Generics,
sv: &syn::Ident,
) -> (syn::GenericParam, syn::Generics) {
let lt: syn::GenericParam = parse_quote! { '__inp };
generics.params.push(lt.clone());
generics
.make_where_clause()
.predicates
.push(parse_quote! { #sv: #lt });
(lt, generics)
}
}

View file

@ -467,9 +467,14 @@ impl Definition {
quote! {
#[automatically_derived]
impl #impl_gens ::juniper::graphql::Scalar<#lt, #inf, #cx, #sv, #bh>
for #ty #where_clause
for #ty #where_clause
{
fn assert_scalar() {}
fn assert_scalar() {
<Self as ::juniper::graphql::InputType<#lt, #inf, #sv, #bh>>
::assert_input_type();
<Self as ::juniper::graphql::OutputType<#inf, #cx, #sv, #bh>>
::assert_output_type();
}
}
}
}