From 97d2da581a2a7ff535f5b837775a74496a12ff35 Mon Sep 17 00:00:00 2001 From: tyranron <tyranron@gmail.com> Date: Wed, 1 Jun 2022 17:31:02 +0200 Subject: [PATCH] Impl codegen for scalars (`resolve::ToInputValue` trait), vol.5 --- juniper/src/ast.rs | 35 ++++++--- juniper/src/macros/reflect.rs | 4 +- juniper/src/resolve/mod.rs | 4 + juniper/src/types/arc.rs | 17 ++++- juniper/src/types/array.rs | 9 +++ juniper/src/types/box.rs | 17 ++++- juniper/src/types/containers.rs | 6 +- juniper/src/types/nullable.rs | 12 +++ juniper/src/types/option.rs | 12 +++ juniper/src/types/rc.rs | 17 ++++- juniper/src/types/ref.rs | 17 ++++- juniper/src/types/ref_mut.rs | 9 +++ juniper/src/types/slice.rs | 9 +++ juniper/src/types/str.rs | 17 ++++- juniper/src/types/vec.rs | 9 +++ juniper/src/value/mod.rs | 22 ++++++ juniper_codegen/src/graphql_scalar/mod.rs | 89 ++++++++++++++++++++++- 17 files changed, 265 insertions(+), 40 deletions(-) diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index ea2c0fec..b535f921 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -256,13 +256,17 @@ impl<S> InputValue<S> { Self::Variable(v.as_ref().to_owned()) } - /// Construct a [`Spanning::unlocated`] list. + /// Constructs a [`Spanning::unlocated`] [`InputValue::List`]. /// - /// Convenience function to make each [`InputValue`] in the input vector - /// not contain any location information. Can be used from [`ToInputValue`] - /// implementations, where no source code position information is available. - pub fn list(l: Vec<Self>) -> Self { - Self::List(l.into_iter().map(Spanning::unlocated).collect()) + /// Convenience function to make each [`InputValue`] in the input `list` to + /// not contain any location information. + /// + /// Intended for [`resolve::ToInputValue`] implementations, where no source + /// code position information is available. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + pub fn list(list: impl IntoIterator<Item = Self>) -> Self { + Self::List(list.into_iter().map(Spanning::unlocated).collect()) } /// Construct a located list. @@ -270,16 +274,25 @@ impl<S> InputValue<S> { Self::List(l) } - /// Construct aa [`Spanning::unlocated`] object. + /// Construct a [`Spanning::unlocated`] [`InputValue::Onject`]. /// - /// Similarly to [`InputValue::list`] it makes each key and value in the - /// given hash map not contain any location information. - pub fn object<K>(o: IndexMap<K, Self>) -> Self + /// Similarly to [`InputValue::list()`] it makes each key and value in the + /// given `obj`ect to not contain any location information. + /// + /// Intended for [`resolve::ToInputValue`] implementations, where no source + /// code position information is available. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + // TODO: Use `impl IntoIterator<Item = (K, Self)>` argument once feature + // `explicit_generic_args_with_impl_trait` hits stable: + // https://github.com/rust-lang/rust/issues/83701 + pub fn object<K, O>(obj: O) -> Self where K: AsRef<str> + Eq + Hash, + O: IntoIterator<Item = (K, Self)>, { Self::Object( - o.into_iter() + obj.into_iter() .map(|(k, v)| { ( Spanning::unlocated(k.as_ref().to_owned()), diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index f40a8c7d..b967d958 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -2,9 +2,7 @@ use futures::future::BoxFuture; -use crate::{ - Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, -}; +use crate::{Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, ScalarValue}; /// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name /// in a GraphQL schema. diff --git a/juniper/src/resolve/mod.rs b/juniper/src/resolve/mod.rs index 39c693e3..87564555 100644 --- a/juniper/src/resolve/mod.rs +++ b/juniper/src/resolve/mod.rs @@ -101,3 +101,7 @@ pub trait InputValue<'input, S: 'input = DefaultScalarValue>: Sized { pub trait InputValueOwned<S = DefaultScalarValue>: for<'i> InputValue<'i, S> {} impl<T, S> InputValueOwned<S> for T where T: for<'i> InputValue<'i, S> {} + +pub trait ToInputValue<S> { + fn to_input_value(&self) -> graphql::InputValue<S>; +} diff --git a/juniper/src/types/arc.rs b/juniper/src/types/arc.rs index 33804c3f..cb5a44ee 100644 --- a/juniper/src/types/arc.rs +++ b/juniper/src/types/arc.rs @@ -143,12 +143,12 @@ where } } -impl<T, S> resolve::ScalarToken<S> for Arc<T> +impl<T, S> resolve::ToInputValue<S> for Arc<T> where - T: resolve::ScalarToken<S> + ?Sized, + T: resolve::ToInputValue<S> + ?Sized, { - fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { - T::parse_scalar_token(token) + fn to_input_value(&self) -> graphql::InputValue<S> { + (**self).to_input_value() } } @@ -194,6 +194,15 @@ where } } +impl<T, S> resolve::ScalarToken<S> for Arc<T> +where + T: resolve::ScalarToken<S> + ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { + T::parse_scalar_token(token) + } +} + impl<T, S> graphql::InputType<S> for Arc<T> where T: graphql::InputType<S> + ?Sized, diff --git a/juniper/src/types/array.rs b/juniper/src/types/array.rs index 4cd5856b..0f0197fa 100644 --- a/juniper/src/types/array.rs +++ b/juniper/src/types/array.rs @@ -64,6 +64,15 @@ where } } +impl<T, S, const N: usize> resolve::ToInputValue<S> for [T; N] +where + T: resolve::ToInputValue<S>, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + graphql::InputValue::list(self.iter().map(T::to_input_value)) + } +} + impl<T, S, const N: usize> graphql::InputType<S> for [T; N] where T: graphql::InputType<S>, diff --git a/juniper/src/types/box.rs b/juniper/src/types/box.rs index 1237ed6e..be725c5d 100644 --- a/juniper/src/types/box.rs +++ b/juniper/src/types/box.rs @@ -141,12 +141,12 @@ where } } -impl<T, S> resolve::ScalarToken<S> for Box<T> +impl<T, S> resolve::ToInputValue<S> for Box<T> where - T: resolve::ScalarToken<S> + ?Sized, + T: resolve::ToInputValue<S> + ?Sized, { - fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { - T::parse_scalar_token(token) + fn to_input_value(&self) -> graphql::InputValue<S> { + (**self).to_input_value() } } @@ -192,6 +192,15 @@ where } } +impl<T, S> resolve::ScalarToken<S> for Box<T> +where + T: resolve::ScalarToken<S> + ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { + T::parse_scalar_token(token) + } +} + impl<T, S> graphql::InputType<S> for Box<T> where T: graphql::InputType<S> + ?Sized, diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index ab5383a4..bda6d4db 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -183,7 +183,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue<S> { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } @@ -283,7 +283,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue<S> { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } @@ -481,7 +481,7 @@ where S: ScalarValue, { fn to_input_value(&self) -> InputValue<S> { - InputValue::list(self.iter().map(T::to_input_value).collect()) + InputValue::list(self.iter().map(T::to_input_value)) } } diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 4113ea81..a8736781 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -324,6 +324,18 @@ where } } +impl<T, S> resolve::ToInputValue<S> for Nullable<T> +where + T: resolve::ToInputValue<S>, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + match self { + Self::Some(v) => v.to_input_value(), + Self::ExplicitNull | Self::ImplicitNull => graphql::InputValue::Null, + } + } +} + impl<'inp, T, S: 'inp> resolve::InputValue<'inp, S> for Nullable<T> where T: resolve::InputValue<'inp, S>, diff --git a/juniper/src/types/option.rs b/juniper/src/types/option.rs index 75320f82..4b475182 100644 --- a/juniper/src/types/option.rs +++ b/juniper/src/types/option.rs @@ -61,6 +61,18 @@ where } } +impl<T, S> resolve::ToInputValue<S> for Option<T> +where + T: resolve::ToInputValue<S>, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + match self { + Some(v) => v.to_input_value(), + None => graphql::InputValue::Null, + } + } +} + impl<'inp, T, S: 'inp> resolve::InputValue<'inp, S> for Option<T> where T: resolve::InputValue<'inp, S>, diff --git a/juniper/src/types/rc.rs b/juniper/src/types/rc.rs index 515290b1..76d148d7 100644 --- a/juniper/src/types/rc.rs +++ b/juniper/src/types/rc.rs @@ -143,12 +143,12 @@ where } } -impl<T, S> resolve::ScalarToken<S> for Rc<T> +impl<T, S> resolve::ToInputValue<S> for Rc<T> where - T: resolve::ScalarToken<S> + ?Sized, + T: resolve::ToInputValue<S> + ?Sized, { - fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { - T::parse_scalar_token(token) + fn to_input_value(&self) -> graphql::InputValue<S> { + (**self).to_input_value() } } @@ -194,6 +194,15 @@ where } } +impl<T, S> resolve::ScalarToken<S> for Rc<T> +where + T: resolve::ScalarToken<S> + ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { + T::parse_scalar_token(token) + } +} + impl<T, S> graphql::InputType<S> for Rc<T> where T: graphql::InputType<S> + ?Sized, diff --git a/juniper/src/types/ref.rs b/juniper/src/types/ref.rs index 646b4566..55ed18be 100644 --- a/juniper/src/types/ref.rs +++ b/juniper/src/types/ref.rs @@ -143,12 +143,12 @@ where } } -impl<'me, T, S> resolve::ScalarToken<S> for &'me T +impl<'me, T, S> resolve::ToInputValue<S> for &'me T where - T: resolve::ScalarToken<S> + ?Sized, + T: resolve::ToInputValue<S> + ?Sized, { - fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { - T::parse_scalar_token(token) + fn to_input_value(&self) -> graphql::InputValue<S> { + (**self).to_input_value() } } @@ -180,6 +180,15 @@ pub trait TryFromInputValue<S = DefaultScalarValue> { } } +impl<'me, T, S> resolve::ScalarToken<S> for &'me T +where + T: resolve::ScalarToken<S> + ?Sized, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { + T::parse_scalar_token(token) + } +} + impl<'me, T, S> graphql::InputType<S> for &'me T where T: graphql::InputType<S> + ?Sized, diff --git a/juniper/src/types/ref_mut.rs b/juniper/src/types/ref_mut.rs index 36a576d7..607b4ba4 100644 --- a/juniper/src/types/ref_mut.rs +++ b/juniper/src/types/ref_mut.rs @@ -142,6 +142,15 @@ where } } +impl<'me, T, S> resolve::ToInputValue<S> for &'me mut T +where + T: resolve::ToInputValue<S> + ?Sized, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + (**self).to_input_value() + } +} + impl<'me, T, S> resolve::ScalarToken<S> for &'me mut T where T: resolve::ScalarToken<S> + ?Sized, diff --git a/juniper/src/types/slice.rs b/juniper/src/types/slice.rs index 5a5b6b52..7dcdfe35 100644 --- a/juniper/src/types/slice.rs +++ b/juniper/src/types/slice.rs @@ -62,6 +62,15 @@ where } } +impl<T, S> resolve::ToInputValue<S> for [T] +where + T: resolve::ToInputValue<S>, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + graphql::InputValue::list(self.iter().map(T::to_input_value)) + } +} + impl<T, S> graphql::InputType<S> for [T] where T: graphql::InputType<S>, diff --git a/juniper/src/types/str.rs b/juniper/src/types/str.rs index b8f26985..65ebcf61 100644 --- a/juniper/src/types/str.rs +++ b/juniper/src/types/str.rs @@ -66,12 +66,12 @@ where } } -impl<S> resolve::ScalarToken<S> for str +impl<S> resolve::ToInputValue<S> for str where - String: resolve::ScalarToken<S>, + S: From<String>, { - fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { - <String as resolve::ScalarToken<S>>::parse_scalar_token(token) + fn to_input_value(&self) -> graphql::InputValue<S> { + graphql::InputValue::scalar(self.to_owned()) } } @@ -108,6 +108,15 @@ impl<'inp, S: ScalarValue> resolve::InputValueAsRc<'inp, S> for str { } } +impl<S> resolve::ScalarToken<S> for str +where + String: resolve::ScalarToken<S>, +{ + fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> { + <String as resolve::ScalarToken<S>>::parse_scalar_token(token) + } +} + impl<S> graphql::InputType<S> for str { fn assert_input_type() {} } diff --git a/juniper/src/types/vec.rs b/juniper/src/types/vec.rs index 8a96f5ad..bf173512 100644 --- a/juniper/src/types/vec.rs +++ b/juniper/src/types/vec.rs @@ -60,6 +60,15 @@ where } } +impl<T, S> resolve::ToInputValue<S> for Vec<T> +where + T: resolve::ToInputValue<S>, +{ + fn to_input_value(&self) -> graphql::InputValue<S> { + graphql::InputValue::list(self.iter().map(T::to_input_value)) + } +} + impl<T, S> graphql::InputType<S> for Vec<T> where T: graphql::InputType<S>, diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs index e3457c8e..9d8ca2b3 100644 --- a/juniper/src/value/mod.rs +++ b/juniper/src/value/mod.rs @@ -6,6 +6,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem}; use crate::{ ast::{InputValue, ToInputValue}, parser::Spanning, + resolve, }; pub use self::{ @@ -190,6 +191,27 @@ impl<S: Clone> ToInputValue<S> for Value<S> { } } +impl<S: Clone> resolve::ToInputValue<S> for Value<S> { + fn to_input_value(&self) -> InputValue<S> { + // TODO: Simplify recursive calls syntax, once old `ToInputValue` trait + // is removed. + match self { + Self::Null => InputValue::Null, + Self::Scalar(s) => InputValue::Scalar(s.clone()), + Self::List(l) => InputValue::list( + l.iter() + .map(<Self as resolve::ToInputValue<S>>::to_input_value), + ), + Self::Object(o) => InputValue::object(o.iter().map(|(k, v)| { + ( + k.clone(), + <Self as resolve::ToInputValue<S>>::to_input_value(v), + ) + })), + } + } +} + impl<S: ScalarValue> fmt::Display for Value<S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs index 17508bc0..daecafe8 100644 --- a/juniper_codegen/src/graphql_scalar/mod.rs +++ b/juniper_codegen/src/graphql_scalar/mod.rs @@ -334,6 +334,7 @@ impl ToTokens for Definition { self.impl_resolve_type_name().to_tokens(into); self.impl_resolve_value().to_tokens(into); self.impl_resolve_value_async().to_tokens(into); + self.impl_resolve_to_input_value().to_tokens(into); self.impl_resolve_input_value().to_tokens(into); self.impl_resolve_scalar_token().to_tokens(into); self.impl_input_and_output_type().to_tokens(into); @@ -680,7 +681,7 @@ impl Definition { fn impl_to_input_value_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let to_input_value = self.methods.expand_to_input_value(scalar); + let to_input_value = self.methods.expand_old_to_input_value(scalar); let (ty, generics) = self.impl_self_and_generics(false); let (impl_gens, _, where_clause) = generics.split_for_impl(); @@ -697,6 +698,34 @@ impl Definition { } } + /// Returns generated code implementing [`resolve::ToInputValue`] trait for + /// this [GraphQL scalar][0]. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: https://spec.graphql.org/October2021#sec-Scalars + fn impl_resolve_to_input_value(&self) -> TokenStream { + let (ty, generics) = self.ty_and_generics(); + let (sv, mut generics) = self.mix_scalar_value(generics); + generics + .make_where_clause() + .predicates + .push(self.methods.bound_to_input_value(sv)); + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + let body = self.methods.expand_to_input_value(sv); + + quote! { + #[automatically_derived] + impl#impl_gens ::juniper::resolve::ToInputValue<#sv> for #ty + #where_clause + { + fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> { + #body + } + } + } + } + /// Returns generated code implementing [`FromInputValue`] trait for this /// [GraphQL scalar][1]. /// @@ -1084,7 +1113,7 @@ impl Methods { } } - /// Expands [`resolve::Value::resolve_value()`][0] method. + /// Expands body of [`resolve::Value::resolve_value()`][0] method. /// /// [0]: juniper::resolve::Value::resolve_value fn expand_resolve_value( @@ -1152,7 +1181,7 @@ impl Methods { /// Expands [`ToInputValue::to_input_value`] method. /// /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value - fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { + fn expand_old_to_input_value(&self, scalar: &scalar::Type) -> TokenStream { match self { Self::Custom { to_output, .. } | Self::Delegated { @@ -1172,6 +1201,60 @@ impl Methods { } } + /// Expands body of [`resolve::ToInputValue::to_input_value()`][0] method. + /// + /// [0]: juniper::resolve::ToInputValue::to_input_value + fn expand_to_input_value(&self, sv: &scalar::Type) -> TokenStream { + match self { + Self::Custom { to_output, .. } + | Self::Delegated { + to_output: Some(to_output), + .. + } => { + quote! { + let v = #to_output(self); + ::juniper::resolve::ToInputValue::<#sv>::to_input_value(&v) + } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + + quote! { + <#field_ty as ::juniper::resolve::ToInputValue<#sv>> + ::to_input_value(&self.#field) + } + } + } + } + + /// Generates additional trait bounds for [`resolve::ToInputValue`] + /// implementation allowing to execute + /// [`resolve::ToInputValue::to_input_value()`][0] method. + /// + /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue + /// [0]: juniper::resolve::ToInputValue::to_input_value + fn bound_to_input_value(&self, sv: &scalar::Type) -> syn::WherePredicate { + match self { + Self::Custom { .. } + | Self::Delegated { + to_output: Some(_), .. + } => { + parse_quote! { + #sv: ::juniper::ScalarValue + } + } + + Self::Delegated { field, .. } => { + let field_ty = field.ty(); + + parse_quote! { + #field_ty: ::juniper::resolve::ToInputValue<#sv>> + } + } + } + } + /// Expands [`FromInputValue::from_input_value`][1] method. /// /// [1]: juniper::FromInputValue::from_input_value