Impl input objects, vol.2 [skip ci]
This commit is contained in:
parent
464ff10c14
commit
fea722b196
7 changed files with 386 additions and 25 deletions
|
@ -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>,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue