Impl field resolving, vol.1

This commit is contained in:
tyranron 2022-08-29 12:55:44 +03:00
parent e6861bc516
commit c75d33ae0a
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
11 changed files with 384 additions and 47 deletions

View file

@ -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<BH, Type, TI>(&self, value: &Type, type_info: &TI) -> ExecutionResult<SV>
where
Type: resolve::Value<TI, CX, SV, BH> + ?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<S> {
self.schema

View file

@ -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},

View file

@ -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<Box<dyn Pet + Send + Sync>>,
pets: Vec<Pet>,
}
#[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,
}),

9
juniper/src/extract.rs Normal file
View file

@ -0,0 +1,9 @@
pub trait Extract<T: ?Sized> {
fn extract(&self) -> &T;
}
impl<T: ?Sized> Extract<T> for T {
fn extract(&self) -> &Self {
self
}
}

View file

@ -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

View file

@ -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<TypeInfo: ?Sized, ScalarValue, Behavior: ?Sized = behavior::Standard> {
fn meta<'r, 'ti: 'r>(
@ -110,9 +112,9 @@ pub trait StaticField<
{
fn resolve_static_field(
&self,
arguments: &Arguments<ScalarValue>,
arguments: &Arguments<'_, ScalarValue>,
type_info: &TypeInfo,
executor: &Executor<Context, ScalarValue>,
executor: &Executor<'_, '_, Context, ScalarValue>,
) -> ExecutionResult<ScalarValue>;
}
@ -202,3 +204,75 @@ pub trait InputValueAsRef<ScalarValue, Behavior: ?Sized = behavior::Standard> {
pub trait ScalarToken<ScalarValue, Behavior: ?Sized = behavior::Standard> {
fn parse_scalar_token(token: parser::ScalarToken<'_>) -> Result<ScalarValue, ParseError>;
}
/*
pub trait IntoResolvable<
ScalarValue,
>
{
type Type;
fn into_resolvable(self) -> FieldResult<Self::Type, ScalarValue>;
}
impl<T, SV> IntoResolvable<SV> for T
where
T: crate::GraphQLValue<SV>,
SV: crate::ScalarValue,
{
type Type = Self;
fn into_resolvable(self) -> FieldResult<Self, SV> {
Ok(self)
}
}
impl<T, E, SV> IntoResolvable<SV> for Result<T, E>
where
T: crate::GraphQLValue<SV>,
SV: crate::ScalarValue,
E: IntoFieldError<SV>,
{
type Type = T;
fn into_resolvable(self) -> FieldResult<Self::Type, SV> {
self.map_err(IntoFieldError::into_field_error)
}
}
*/
#[doc(hidden)]
pub trait IntoResolvable<S, T>
where
T: crate::GraphQLValue<S>,
S: crate::ScalarValue,
{
type Type;
#[doc(hidden)]
fn into_resolvable(self) -> FieldResult<T, S>;
}
impl<'a, S, T> IntoResolvable<S, T> for T
where
T: crate::GraphQLValue<S>,
S: crate::ScalarValue,
{
type Type = T;
fn into_resolvable(self) -> FieldResult<T, S> {
Ok(self)
}
}
impl<'a, S, T, E: IntoFieldError<S>> IntoResolvable<S, T> for Result<T, E>
where
S: crate::ScalarValue,
T: crate::GraphQLValue<S>,
{
type Type = T;
fn into_resolvable(self) -> FieldResult<T, S> {
self.map_err(IntoFieldError::into_field_error)
}
}

View file

@ -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<T, S>
where
T: resolve::InputValue<'s, S, BH>,
BH: ?Sized,
{
self.args
.as_ref()
.and_then(|args| args.get(name))
.map(<T as resolve::InputValue<'s, S, BH>>::try_from_input_value)
.transpose()
.map_err(IntoFieldError::into_field_error)?
.map_or_else(
|| {
<T as resolve::InputValue<'s, S, BH>>::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.

View file

@ -352,11 +352,36 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
/// 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<Operation: ?Sized + 'static> Definition<Operation> {
})
.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,
<Self as ::juniper::reflect::BaseType<#bh>>::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<Query> {
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);
}
}

View file

@ -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,
})
}

View file

@ -84,6 +84,7 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<Definition> {
.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<Definition> {
.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,
})

View file

@ -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<SpanContainer<scalar::AttrValue>>,
/// 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<SpanContainer<behavior::Type>>,
/// 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::<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))?
}
"on" => {
let ty = input.parse::<syn::Type>()?;
input.parse::<token::Eq>()?;
@ -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<SpanContainer<behavior::Type>>,
/// 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::<syn::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))?
}
"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<Self> {
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 =
&[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
const NAMES: ::juniper::reflect::Types = &[
<Self as ::juniper::reflect::BaseType<#bh>>::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,
})
}