From c75d33ae0aea4011d760d8fe994c5bb2f7577e1f Mon Sep 17 00:00:00 2001 From: tyranron Date: Mon, 29 Aug 2022 12:55:44 +0300 Subject: [PATCH] Impl field resolving, vol.1 --- juniper/src/executor/mod.rs | 37 +++-- juniper/src/executor_tests/executor.rs | 4 +- .../src/executor_tests/interfaces_unions.rs | 36 ++--- juniper/src/extract.rs | 9 ++ juniper/src/lib.rs | 2 + juniper/src/resolve/mod.rs | 80 ++++++++++- juniper/src/types/base.rs | 35 +++++ juniper_codegen/src/graphql_object/mod.rs | 132 +++++++++++++++++- juniper_codegen/src/graphql_union/attr.rs | 2 + juniper_codegen/src/graphql_union/derive.rs | 3 + juniper_codegen/src/graphql_union/mod.rs | 91 +++++++++++- 11 files changed, 384 insertions(+), 47 deletions(-) create mode 100644 juniper/src/extract.rs diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 36076455..cd550d7b 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -89,6 +89,35 @@ impl<'r, 'a, CX: ?Sized, SV> Executor<'r, 'a, CX, SV> { pub(crate) fn current_type_reworked(&self) -> &TypeType<'a, SV> { &self.current_type } + + /// Resolves the specified single arbitrary `Type` `value` as + /// [`graphql::Value`]. + /// + /// # Errors + /// + /// Whenever [`Type::resolve_value()`] errors. + /// + /// [`graphql::Value`]: crate::graphql::Value + /// [`Type::resolve_value()`]: resolve::Value::resolve_value + pub fn resolve_value(&self, value: &Type, type_info: &TI) -> ExecutionResult + where + Type: resolve::Value + ?Sized, + TI: ?Sized, + BH: ?Sized, + { + value.resolve_value(self.current_selection_set, type_info, self) + } + + /// Returns the current context of this [`Executor`]. + /// + /// Context is usually provided when the top-level [`execute()`] function is + /// called. + /// + /// [`execute()`]: crate::execute + #[must_use] + pub fn context(&self) -> &'r CX { + self.context + } } /// Error type for errors that occur during query execution @@ -635,14 +664,6 @@ where self.current_selection_set } - /// Access the current context - /// - /// You usually provide the context when calling the top-level `execute` - /// function, or using the context factory in the Iron integration. - pub fn context(&self) -> &'r CtxT { - self.context - } - /// The currently executing schema pub fn schema(&self) -> &'a SchemaType { self.schema diff --git a/juniper/src/executor_tests/executor.rs b/juniper/src/executor_tests/executor.rs index 06d93fc0..08897418 100644 --- a/juniper/src/executor_tests/executor.rs +++ b/juniper/src/executor_tests/executor.rs @@ -369,6 +369,8 @@ mod threads_context_correctly { } } +// TODO: Remove as should be unnecessary with generic context. +/* mod dynamic_context_switching { use indexmap::IndexMap; @@ -672,7 +674,7 @@ mod dynamic_context_switching { assert_eq!(result, graphql_value!({"first": {"value": "First value"}})); } } - +*/ mod propagates_errors_to_nullable_fields { use crate::{ executor::{ExecutionError, FieldError, FieldResult, IntoFieldError}, diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 3e9fccf4..9b8ac746 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -96,19 +96,15 @@ mod interface { mod union { use crate::{ - graphql_object, graphql_union, graphql_value, + graphql_object, GraphQLUnion, graphql_value, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, }; - #[graphql_union] - trait Pet { - fn as_dog(&self) -> Option<&Dog> { - None - } - fn as_cat(&self) -> Option<&Cat> { - None - } + #[derive(GraphQLUnion)] + enum Pet { + Dog(Dog), + Cat(Cat), } struct Dog { @@ -116,12 +112,6 @@ mod union { woofs: bool, } - impl Pet for Dog { - fn as_dog(&self) -> Option<&Dog> { - Some(self) - } - } - #[graphql_object] impl Dog { fn name(&self) -> &str { @@ -137,12 +127,6 @@ mod union { meows: bool, } - impl Pet for Cat { - fn as_cat(&self) -> Option<&Cat> { - Some(self) - } - } - #[graphql_object] impl Cat { fn name(&self) -> &str { @@ -154,13 +138,13 @@ mod union { } struct Schema { - pets: Vec>, + pets: Vec, } #[graphql_object] impl Schema { - fn pets(&self) -> Vec<&(dyn Pet + Send + Sync)> { - self.pets.iter().map(|p| p.as_ref()).collect() + fn pets(&self) -> &[Pet] { + &self.pets } } @@ -169,11 +153,11 @@ mod union { let schema = RootNode::new( Schema { pets: vec![ - Box::new(Dog { + Pet::Dog(Dog { name: "Odie".into(), woofs: true, }), - Box::new(Cat { + Pet::Cat(Cat { name: "Garfield".into(), meows: false, }), diff --git a/juniper/src/extract.rs b/juniper/src/extract.rs new file mode 100644 index 00000000..3b02845b --- /dev/null +++ b/juniper/src/extract.rs @@ -0,0 +1,9 @@ +pub trait Extract { + fn extract(&self) -> &T; +} + +impl Extract for T { + fn extract(&self) -> &Self { + self + } +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index a40e1bf8..9271f8d4 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -33,6 +33,7 @@ pub mod macros; mod ast; pub mod behavior; pub mod executor; +pub mod extract; pub mod graphql; pub mod http; pub mod integrations; @@ -94,6 +95,7 @@ pub use crate::{ }, validation::RuleError, value::{DefaultScalarValue, Object, ParseScalarResult, ParseScalarValue, ScalarValue, Value}, + extract::Extract, }; /// An error that prevented query execution diff --git a/juniper/src/resolve/mod.rs b/juniper/src/resolve/mod.rs index 7c7e5c19..cdab181c 100644 --- a/juniper/src/resolve/mod.rs +++ b/juniper/src/resolve/mod.rs @@ -2,8 +2,10 @@ use crate::{ behavior, graphql, meta::MetaType, parser::{self, ParseError}, - reflect, Arguments, BoxFuture, ExecutionResult, Executor, IntoFieldError, Registry, Selection, + reflect, Arguments, BoxFuture, ExecutionResult, Executor, FieldResult, IntoFieldError, + Registry, Selection, }; +use juniper::resolve; pub trait Type { fn meta<'r, 'ti: 'r>( @@ -110,9 +112,9 @@ pub trait StaticField< { fn resolve_static_field( &self, - arguments: &Arguments, + arguments: &Arguments<'_, ScalarValue>, type_info: &TypeInfo, - executor: &Executor, + executor: &Executor<'_, '_, Context, ScalarValue>, ) -> ExecutionResult; } @@ -202,3 +204,75 @@ pub trait InputValueAsRef { pub trait ScalarToken { fn parse_scalar_token(token: parser::ScalarToken<'_>) -> Result; } + +/* +pub trait IntoResolvable< + ScalarValue, +> +{ + type Type; + + fn into_resolvable(self) -> FieldResult; +} + +impl IntoResolvable for T +where + T: crate::GraphQLValue, + SV: crate::ScalarValue, +{ + type Type = Self; + + fn into_resolvable(self) -> FieldResult { + Ok(self) + } +} + +impl IntoResolvable for Result +where + T: crate::GraphQLValue, + SV: crate::ScalarValue, + E: IntoFieldError, +{ + type Type = T; + + fn into_resolvable(self) -> FieldResult { + self.map_err(IntoFieldError::into_field_error) + } +} +*/ + +#[doc(hidden)] +pub trait IntoResolvable +where + T: crate::GraphQLValue, + S: crate::ScalarValue, +{ + type Type; + + #[doc(hidden)] + fn into_resolvable(self) -> FieldResult; +} + +impl<'a, S, T> IntoResolvable for T +where + T: crate::GraphQLValue, + S: crate::ScalarValue, +{ + type Type = T; + + fn into_resolvable(self) -> FieldResult { + Ok(self) + } +} + +impl<'a, S, T, E: IntoFieldError> IntoResolvable for Result +where + S: crate::ScalarValue, + T: crate::GraphQLValue, +{ + type Type = T; + + fn into_resolvable(self) -> FieldResult { + self.map_err(IntoFieldError::into_field_error) + } +} diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 9d54fee0..aab0c6df 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -4,6 +4,7 @@ use crate::{ ast::{Directive, FromInputValue, InputValue, Selection}, executor::{ExecutionResult, Executor, Registry, Variables}, parser::Spanning, + resolve, schema::meta::{Argument, MetaType}, value::{DefaultScalarValue, Object, ScalarValue, Value}, FieldResult, GraphQLEnum, IntoFieldError, @@ -98,6 +99,8 @@ impl<'a, S> Arguments<'a, S> { Self { args } } + /// TODO: DEPRECATED! + /// /// Gets an argument by the given `name` and converts it into the desired /// type. /// @@ -121,6 +124,38 @@ impl<'a, S> Arguments<'a, S> { .transpose() .map_err(IntoFieldError::into_field_error) } + + /// Resolves an argument with the provided `name` as the specified type `T`. + /// + /// If [`None`] argument is found, then `T` is + /// [tried to be resolved from implicit `null`][0]. + /// + /// # Errors + /// + /// If the [`resolve::InputValue`] conversion fails. + /// + /// [0]: resolve::InputValue::try_from_implicit_null + pub fn resolve<'s, T, BH>(&'s self, name: &str) -> FieldResult + where + T: resolve::InputValue<'s, S, BH>, + BH: ?Sized, + { + self.args + .as_ref() + .and_then(|args| args.get(name)) + .map(>::try_from_input_value) + .transpose() + .map_err(IntoFieldError::into_field_error)? + .map_or_else( + || { + >::try_from_implicit_null().map_err(|e| { + IntoFieldError::into_field_error(e) + .map_message(|m| format!("Missing argument `{name}`: {m}")) + }) + }, + Ok, + ) + } } /// Primary trait used to resolve GraphQL values. diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 125bc5e2..5e01d322 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -352,11 +352,36 @@ impl Definition { /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait /// implementation. - #[must_use] fn ty_and_generics(&self) -> (&syn::Type, syn::Generics) { (&self.ty, self.generics.clone()) } + /// 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 context [`syn::GenericParam`] into the provided + /// [`syn::Generics`] and returns its [`syn::Ident`]. + fn mix_context(&self, mut generics: syn::Generics) -> (syn::Ident, syn::Generics) { + let ty = parse_quote! { __Context }; + 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) + } + /// Returns generated code implementing [`marker::IsOutputType`] trait for /// this [GraphQL object][1]. /// @@ -574,7 +599,108 @@ impl Definition { }) .collect() } +/* + /// Returns generated code implementing [`resolve::StaticField`] trait for + /// each [field][1] of this [GraphQL object][0]. + /// + /// [`resolve::StaticField`]: juniper::resolve::StaticField + /// [0]: https://spec.graphql.org/October2021#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields + #[must_use] + pub(crate) fn impl_resolve_static_field(&self) -> TokenStream { + let bh = &self.behavior; + let (ty, generics) = self.ty_and_generics(); + let (inf, generics) = self.mix_type_info(generics); + let (cx, generics) = self.mix_context(generics); + let (sv, generics) = self.mix_scalar_value(generics); + self.fields + .iter() + .map(|field| { + let mut generics = generics.clone(); + let (f_name, f_bh) = (&field.name, &field.behavior); + let (f_ident, f_ty) = (&field.ident, &field.ty); + + let body = if !field.is_async { + generics.make_where_clause().predicates.push(parse_quote! { + #f_ty: ::juniper::resolve::Value<#inf, #cx, #sv, #f_bh> + }); + + let res = if field.is_method() { + let args = field.arguments.as_ref().unwrap().iter().map(|arg| { + match arg { + field::MethodArgument::Regular(arg) => { + let (a_ty, a_bh) = (&arg.ty, &arg.behavior); + generics.make_where_clause().predicates.push(parse_quote! { + #a_ty: ::juniper::resolve::InputValueOwned<#sv, #a_bh> + }); + quote! { + args.resolve::<#a_ty, #a_bh>(#name)? + } + } + field::MethodArgument::Context(cx_ty) => { + generics.make_where_clause().predicates.push(parse_quote! { + #cx: ::juniper::Extract<#cx_ty> + }); + quote! { + <#cx as ::juniper::Extract<#cx_ty>> + ::extract(executor.context()) + } + } + field::MethodArgument::Executor => { + quote! { + executor + } + } + } + }); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + quote! { + &self.#f_ident + } + }; + + quote! { + executor.resolve_value::<#f_bh, _, _>(#res, type_info) + } + } else { + quote! { + ::std::panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + #f_name, + >::NAME, + ); + } + }; + + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_gens ::juniper::resolve::StaticField< + { ::juniper::reflect::fnv1a128(#f_name) }, + #inf, #cx, #sv, #bh, + > for #ty #where_clause { + fn resolve_static_field( + &self, + args: &::juniper::Arguments<'_, #sv>, + type_info: &#inf, + executor: &::juniper::Executor<'_, '_, #cx, #sv>, + ) -> ::juniper::ExecutionResult<#sv> { + #body + } + } + } + }) + .collect() + } +*/ /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -655,8 +781,8 @@ impl ToTokens for Definition { self.impl_async_field_tokens().to_tokens(into); //////////////////////////////////////////////////////////////////////// self.impl_reflect().to_tokens(into); - //self.impl_reflect_field().to_tokens(into); - //self.impl_resolve_field_static().to_tokens(into); + self.impl_reflect_field().to_tokens(into); + //self.impl_resolve_static_field().to_tokens(into); } } diff --git a/juniper_codegen/src/graphql_union/attr.rs b/juniper_codegen/src/graphql_union/attr.rs index 006ad226..a07ac78e 100644 --- a/juniper_codegen/src/graphql_union/attr.rs +++ b/juniper_codegen/src/graphql_union/attr.rs @@ -93,6 +93,7 @@ fn expand_on_trait( description: attr.description.map(SpanContainer::into_inner), context, scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics.clone(), variants, }; @@ -210,6 +211,7 @@ fn parse_variant_from_trait_method( ty, resolver_code, resolver_check, + behavior: attr.behavior.into(), context: method_context_ty, }) } diff --git a/juniper_codegen/src/graphql_union/derive.rs b/juniper_codegen/src/graphql_union/derive.rs index a10be60c..0a2818c2 100644 --- a/juniper_codegen/src/graphql_union/derive.rs +++ b/juniper_codegen/src/graphql_union/derive.rs @@ -84,6 +84,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics, variants, }) @@ -163,6 +164,7 @@ fn parse_variant_from_enum_variant( ty, resolver_code, resolver_check, + behavior: attr.behavior.into(), context: None, }) } @@ -214,6 +216,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result { .map(SpanContainer::into_inner) .unwrap_or_else(|| parse_quote! { () }), scalar: scalar::Type::parse(attr.scalar.as_deref(), &ast.generics), + behavior: attr.behavior.into(), generics: ast.generics, variants, }) diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index df162fdd..ff21e3c6 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -18,7 +18,7 @@ use syn::{ }; use crate::common::{ - filter_attrs, gen, + filter_attrs, gen, behavior, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -74,6 +74,17 @@ struct Attr { /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: Option>, + /// Explicitly specified type of the custom [`Behavior`] to parametrize this + /// [GraphQL union][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-Unions + behavior: Option>, + /// Explicitly specified external resolver functions for [GraphQL union][1] /// variants. /// @@ -128,6 +139,13 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) .none_or_else(|_| err::dup_arg(&ident))? } + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "on" => { let ty = input.parse::()?; input.parse::()?; @@ -160,6 +178,7 @@ impl Attr { 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), external_resolvers: try_merge_hashmap!( external_resolvers: self, another => span_joined ), @@ -188,6 +207,19 @@ impl Attr { /// [1]: https://spec.graphql.org/October2021#sec-Unions #[derive(Debug, Default)] struct VariantAttr { + /// Explicitly specified type of the custom [`Behavior`] this + /// [GraphQL union][0] member implementation is parametrized with, to + /// [coerce] in the generated code from. + /// + /// 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-Unions + /// [coerce]: juniper::behavior::Coerce + behavior: Option>, + /// Explicitly specified marker for the variant/field being ignored and not /// included into [GraphQL union][1]. /// @@ -210,6 +242,13 @@ impl Parse for VariantAttr { while !input.is_empty() { let ident = input.parse::()?; match ident.to_string().as_str() { + "behave" | "behavior" => { + input.parse::()?; + let bh = input.parse::()?; + out.behavior + .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh)) + .none_or_else(|_| err::dup_arg(&ident))? + } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) @@ -236,6 +275,7 @@ impl VariantAttr { /// duplicates, if any. fn try_merge(self, mut another: Self) -> syn::Result { Ok(Self { + behavior: try_merge_opt!(behavior: self, another), ignore: try_merge_opt!(ignore: self, another), external_resolver: try_merge_opt!(external_resolver: self, another), }) @@ -301,6 +341,13 @@ struct Definition { /// [1]: https://spec.graphql.org/October2021#sec-Unions scalar: scalar::Type, + /// [`Behavior`] parametrization to generate code with for this + /// [GraphQL union][0]. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Unions + behavior: behavior::Type, + /// Variants definitions of this [GraphQL union][1]. /// /// [1]: https://spec.graphql.org/October2021#sec-Unions @@ -316,7 +363,7 @@ impl ToTokens for Definition { self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); //////////////////////////////////////////////////////////////////////// - //self.impl_reflect().to_tokens(into); + self.impl_reflect().to_tokens(into); } } @@ -647,7 +694,7 @@ impl Definition { } } } - /* + /// Returns generated code implementing [`reflect::BaseType`], /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this /// [GraphQL union][0]. @@ -663,6 +710,15 @@ impl Definition { let name = &self.name; + let member_names = self.variants.iter().map(|m| { + let m_ty = &m.ty; + let m_bh = &m.behavior; + + quote! { + <#m_ty as ::juniper::reflect::BaseType<#m_bh>>::NAME + } + }); + quote! { #[automatically_derived] impl #impl_gens ::juniper::reflect::BaseType<#bh> @@ -675,8 +731,10 @@ impl Definition { impl #impl_gens ::juniper::reflect::BaseSubTypes<#bh> for #ty #where_clause { - const NAMES: ::juniper::reflect::Types = - &[>::NAME]; + const NAMES: ::juniper::reflect::Types = &[ + >::NAME, + #( #member_names ),* + ]; } #[automatically_derived] @@ -687,7 +745,19 @@ impl Definition { ::juniper::reflect::wrap::SINGULAR; } } - }*/ + } + + /// 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.ty; + let (_, ty_gen, _) = generics.split_for_impl(); + parse_quote! { #ident #ty_gen } + }; + (ty, generics) + } } /// Definition of [GraphQL union][1] variant for code generation. @@ -710,6 +780,14 @@ struct VariantDefinition { /// [1]: https://spec.graphql.org/October2021#sec-Unions resolver_check: syn::Expr, + /// [`Behavior`] parametrization of this [GraphQL union][0] member + /// implementation to [coerce] from in the generated code. + /// + /// [`Behavior`]: juniper::behavior + /// [0]: https://spec.graphql.org/October2021#sec-Unions + /// [coerce]: juniper::behavior::Coerce + behavior: behavior::Type, + /// Rust type of [`Context`] that this [GraphQL union][1] variant requires /// for resolution. /// @@ -826,6 +904,7 @@ fn emerge_union_variants_from_attr( ty, resolver_code, resolver_check, + behavior: behavior::Type::default(), // TODO: remove at all context: None, }) }