diff --git a/async-graphql-derive/src/object.rs b/async-graphql-derive/src/object.rs index 327e277f..f0cf9596 100644 --- a/async-graphql-derive/src/object.rs +++ b/async-graphql-derive/src/object.rs @@ -235,12 +235,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< resolvers.push(quote! { if field.name.as_str() == #field_name { #(#get_params)* - let obj = #resolve_obj; let ctx_obj = ctx_field.with_item(&field.selection_set); - let value = obj.resolve(&ctx_obj).await. - map_err(|err| err.with_position(field.position))?; - let name = field.alias.clone().unwrap_or_else(|| field.name.clone()); - result.insert(name, value.into()); + let value = #resolve_obj.resolve(&ctx_obj).await.map_err(|err| err.with_position(field.position))?; + result.insert(field.alias.clone().unwrap_or_else(|| field.name.clone()), value.into()); continue; } }); @@ -279,29 +276,25 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< } let mut result = #crate_name::serde_json::Map::::new(); - for selection in &ctx.items { - match selection { - #crate_name::graphql_parser::query::Selection::Field(field) => { - let ctx_field = ctx.with_item(field); - if ctx_field.is_skip_this()? { - continue; - } - if field.name.as_str() == "__typename" { - let name = field.alias.clone().unwrap_or_else(|| field.name.clone()); - result.insert(name, #gql_typename.into()); - continue; - } - if field.name.as_str() == "__schema" { - continue; - } - #(#resolvers)* - #crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound { - field_name: field.name.clone(), - object: #gql_typename, - }.with_position(field.position)); - } - _ => {} + for field in ctx.fields(&*ctx) { + let field = field?; + let ctx_field = ctx.with_item(field); + if ctx_field.is_skip_this()? { + continue; } + if field.name.as_str() == "__typename" { + let name = field.alias.clone().unwrap_or_else(|| field.name.clone()); + result.insert(name, #gql_typename.into()); + continue; + } + if field.name.as_str() == "__schema" { + continue; + } + #(#resolvers)* + #crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound { + field_name: field.name.clone(), + object: #gql_typename, + }.with_position(field.position)); } Ok(#crate_name::serde_json::Value::Object(result)) diff --git a/src/context.rs b/src/context.rs index 1a1b6731..7b78ec00 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,9 @@ use crate::registry::Registry; use crate::{ErrorWithPosition, GQLInputValue, GQLType, QueryError, Result}; use fnv::FnvHasher; -use graphql_parser::query::{Field, SelectionSet, Value, VariableDefinition}; +use graphql_parser::query::{ + Field, FragmentDefinition, Selection, SelectionSet, Value, VariableDefinition, +}; use std::any::{Any, TypeId}; use std::collections::{BTreeMap, HashMap}; use std::hash::BuildHasherDefault; @@ -73,6 +75,7 @@ pub struct ContextBase<'a, T> { pub(crate) variable_definitions: Option<&'a [VariableDefinition]>, pub(crate) registry: &'a Registry, pub(crate) data: &'a Data, + pub(crate) fragments: &'a HashMap, } impl<'a, T> Deref for ContextBase<'a, T> { @@ -83,6 +86,41 @@ impl<'a, T> Deref for ContextBase<'a, T> { } } +pub struct FieldIter<'a> { + fragments: &'a HashMap, + stack: Vec>, +} + +impl<'a> Iterator for FieldIter<'a> { + type Item = Result<&'a Field>; + + fn next(&mut self) -> Option { + while let Some(it) = self.stack.last_mut() { + if let Some(selection) = it.next() { + match selection { + Selection::Field(field) => { + return Some(Ok(field)); + } + Selection::FragmentSpread(fragment_spread) => { + if let Some(fragment) = self.fragments.get(&fragment_spread.fragment_name) { + self.stack.push(fragment.selection_set.items.iter()); + } else { + return Some(Err(QueryError::UnknownFragment { + name: fragment_spread.fragment_name.clone(), + } + .into())); + } + } + Selection::InlineFragment(_) => {} + } + } else { + self.stack.pop(); + } + } + None + } +} + impl<'a, T> ContextBase<'a, T> { #[doc(hidden)] pub fn with_item(&self, item: R) -> ContextBase<'a, R> { @@ -92,6 +130,7 @@ impl<'a, T> ContextBase<'a, T> { variable_definitions: self.variable_definitions, registry: self.registry.clone(), data: self.data, + fragments: self.fragments, } } @@ -101,6 +140,14 @@ impl<'a, T> ContextBase<'a, T> { .get(&TypeId::of::()) .and_then(|d| d.downcast_ref::()) } + + #[doc(hidden)] + pub fn fields(&self, selection_set: &'a SelectionSet) -> FieldIter { + FieldIter { + fragments: self.fragments, + stack: vec![selection_set.items.iter()], + } + } } impl<'a> ContextBase<'a, &'a Field> { diff --git a/src/error.rs b/src/error.rs index c34039c4..a7fbb202 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,9 @@ pub enum QueryError { #[error("Unknown directive \"{name}\".")] UnknownDirective { name: String }, + + #[error("Unknown fragment \"{name}\".")] + UnknownFragment { name: String }, } pub trait ErrorWithPosition { diff --git a/src/model/type.rs b/src/model/type.rs index 7701ac44..0f7b5579 100644 --- a/src/model/type.rs +++ b/src/model/type.rs @@ -133,7 +133,7 @@ impl<'a> __Type<'a> { None } - #[field] + #[field(name = "possibleTypes")] async fn possible_types(&self) -> Option>> { None } diff --git a/src/schema.rs b/src/schema.rs index b6c807bd..28b9457b 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -3,12 +3,12 @@ use crate::model::__DirectiveLocation; use crate::registry::{Directive, InputValue, Registry}; use crate::types::QueryRoot; use crate::{ - ContextBase, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType, QueryError, - QueryParseError, Result, Variables, + ContextBase, GQLObject, GQLOutputValue, GQLType, QueryError, QueryParseError, Result, Variables, }; use graphql_parser::parse_query; use graphql_parser::query::{Definition, OperationDefinition}; use std::any::Any; +use std::collections::HashMap; pub struct Schema { query: QueryRoot, @@ -116,6 +116,13 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> { { let document = parse_query(self.query_source).map_err(|err| QueryParseError(err.to_string()))?; + let mut fragments = HashMap::new(); + + for definition in &document.definitions { + if let Definition::Fragment(fragment) = definition { + fragments.insert(fragment.name.clone(), fragment); + } + } for definition in &document.definitions { match definition { @@ -127,6 +134,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> { variable_definitions: None, registry: &self.registry, data: self.data, + fragments: &fragments, }; return self.query.resolve(&ctx).await; } @@ -141,6 +149,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> { variable_definitions: Some(&query.variable_definitions), registry: self.registry.clone(), data: self.data, + fragments: &fragments, }; return self.query.resolve(&ctx).await; } @@ -155,16 +164,12 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> { variable_definitions: Some(&mutation.variable_definitions), registry: self.registry.clone(), data: self.data, + fragments: &fragments, }; return self.mutation.resolve(&ctx).await; } } - Definition::Operation(OperationDefinition::Subscription(subscription)) => { - anyhow::bail!(QueryError::NotSupported.with_position(subscription.position)); - } - Definition::Fragment(fragment) => { - anyhow::bail!(QueryError::NotSupported.with_position(fragment.position)); - } + _ => {} } }