diff --git a/book/src/types/interfaces.md b/book/src/types/interfaces.md index 8990a0946..1ae086d3a 100644 --- a/book/src/types/interfaces.md +++ b/book/src/types/interfaces.md @@ -391,7 +391,7 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`] ```rust # extern crate juniper; -use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; +use juniper::{graphql_interface, graphql_object, Executor, ScalarValue}; #[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter trait Character { diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 180c79562..47c92aedd 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -52,12 +52,22 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Removed `scalar-naivetime` [Cargo feature]. - Removed lifetime parameter from `ParseError`, `GraphlQLError`, `GraphQLBatchRequest` and `GraphQLRequest`. ([#1081], [#528]) - Upgraded [GraphiQL] to 3.1.0 version (requires new [`graphql-transport-ws` GraphQL over WebSocket Protocol] integration on server, see `juniper_warp/examples/subscription.rs`). ([#1188], [#1193], [#1235]) -- Made `LookAheadMethods::children()` method to return slice instead of `Vec`. ([#1200]) - Abstracted `Spanning::start` and `Spanning::end` fields into separate struct `Span`. ([#1207], [#1208]) -- Added `Span` to `Arguments` and `LookAheadArguments`. ([#1206], [#1209]) - Removed `graphql-parser-integration` and `graphql-parser` [Cargo feature]s by merging them into `schema-language` [Cargo feature]. ([#1237]) - Renamed `RootNode::as_schema_language()` method as `RootNode::as_sdl()`. ([#1237]) -- Renamed `RootNode::as_parser_document()` method as `RootNode::as_document()`. ([#1237]) +- Renamed `RootNode::as_parser_document()` method as `RootNode::as_document()`. ([#1237]) +- Reworked look-ahead machinery: ([#1212]) + - Turned from eagerly-evaluated into lazy-evaluated: + - Made `LookAheadValue::List` to contain new iterable `LookAheadList` type. + - Made `LookAheadValue::Object` to contain new iterable `LookAheadObject` type. + - Removed `LookAheadMethods` trait and redundant `ConcreteLookAheadSelection` type, making all APIs accessible as inherent methods on `LookAheadSelection` and `LookAheadChildren` decoupled types: + - Moved `LookAheadMethods::child_names()` to `LookAheadChildren::names()`. + - Moved `LookAheadMethods::has_children()` to `LookAheadChildren::is_empty()`. + - Moved `LookAheadMethods::select_child()` to `LookAheadChildren::select()`. + - Moved `LookAheadSelection::for_explicit_type()` to `LookAheadSelection::children_for_explicit_type()`. + - Made `LookAheadSelection::arguments()` returning iterator over `LookAheadArgument`. + - Made `LookAheadSelection::children()` returning `LookAheadChildren`. + - Added `Span` to `Arguments` and `LookAheadArguments`. ([#1206], [#1209]) ### Added @@ -145,11 +155,11 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1190]: /../../pull/1190 [#1193]: /../../pull/1193 [#1199]: /../../pull/1199 -[#1200]: /../../pull/1200 [#1206]: /../../pull/1206 [#1207]: /../../pull/1207 [#1208]: /../../pull/1208 [#1209]: /../../pull/1209 +[#1212]: /../../pull/1212 [#1215]: /../../pull/1215 [#1227]: /../../pull/1227 [#1228]: /../../pull/1228 diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 3fe73c35e..07d84188a 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -1,511 +1,824 @@ -use std::collections::HashMap; +use std::{collections::HashMap, vec}; use crate::{ - ast::{Directive, Fragment, InputValue, Selection}, + ast::{Directive, Field, Fragment, InputValue, Selection}, parser::{Span, Spanning}, value::ScalarValue, }; use super::Variables; -/// An enum that describes if a field is available in all types of the interface -/// or only in a certain subtype +/// Indication whether a field is available in all types of an interface or only in a certain +/// subtype. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Applies<'a> { - /// The field is available independent from the type + /// Field is always available, independently from the type. All, - /// The field is only available for a given typename + + /// Field is only available for the type with the specified typename. OnlyType(&'a str), } /// Shortcut for a [`Spanning`] containing a borrowed [`Span`]. type BorrowedSpanning<'a, T> = Spanning; -/// JSON-like value that can be used as an argument in the query execution. +/// JSON-like value performing [look-ahead][0] operations on an executed GraphQL query. +/// +/// In contrast to an [`InputValue`], these values do only contain constants, meaning that GraphQL +/// variables get automatically resolved. /// -/// In contrast to an [`InputValue`], these values do only contain constants, -/// meaning that variables are already resolved. +/// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) #[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] -pub enum LookAheadValue<'a, S: 'a> { +#[must_use] +pub enum LookAheadValue<'a, S: ScalarValue + 'a> { Null, Scalar(&'a S), Enum(&'a str), - List(Vec>>), - Object( - Vec<( - BorrowedSpanning<'a, &'a str>, - BorrowedSpanning<'a, LookAheadValue<'a, S>>, - )>, - ), + List(LookAheadList<'a, S>), + Object(LookAheadObject<'a, S>), } -impl<'a, S> LookAheadValue<'a, S> -where - S: ScalarValue, -{ +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S: ScalarValue + 'a> Copy for LookAheadValue<'a, S> where Self: Clone {} + +impl<'a, S: ScalarValue + 'a> LookAheadValue<'a, S> { fn from_input_value( input_value: BorrowedSpanning<'a, &'a InputValue>, - vars: &'a Variables, + vars: Option<&'a Variables>, ) -> BorrowedSpanning<'a, Self> { + let Spanning { + item: input_value, + span: input_span, + } = input_value; Spanning { - span: input_value.span, - item: match input_value.item { + item: match input_value { InputValue::Null => Self::Null, InputValue::Scalar(s) => Self::Scalar(s), InputValue::Enum(e) => Self::Enum(e), InputValue::Variable(name) => vars - .get(name) + .and_then(|vars| vars.get(name)) .map(|item| { - let input_value = Spanning { - span: input_value.span, - item, - }; - Self::from_input_value(input_value, vars).item + Self::from_input_value( + BorrowedSpanning { + item, + span: input_span, + }, + vars, + ) + .item }) .unwrap_or(Self::Null), - InputValue::List(l) => Self::List( - l.iter() - .map(|i| Self::from_input_value(i.as_ref(), vars)) - .collect(), - ), - InputValue::Object(o) => Self::Object( - o.iter() - .map(|(n, i)| { - ( - Spanning { - span: &n.span, - item: n.item.as_str(), - }, - Self::from_input_value(i.as_ref(), vars), - ) - }) - .collect(), - ), + InputValue::List(input_list) => Self::List(LookAheadList { input_list, vars }), + InputValue::Object(input_object) => Self::Object(LookAheadObject { + input_object: input_object.as_slice(), + vars, + }), }, + span: input_span, } } } -/// An argument passed into the query -#[derive(Debug, Clone, PartialEq)] -pub struct LookAheadArgument<'a, S: 'a> { - name: &'a str, - value: BorrowedSpanning<'a, LookAheadValue<'a, S>>, +/// [Lazy][2]-evaluated [list] used in [look-ahead][0] operations on an executed GraphQL query. +/// +/// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) +/// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation +/// [list]: https://spec.graphql.org/October2021#sec-List +#[derive(Debug)] +#[must_use] +pub struct LookAheadList<'a, S> { + input_list: &'a [Spanning>], + vars: Option<&'a Variables>, } -impl<'a, S> LookAheadArgument<'a, S> -where - S: ScalarValue, -{ - pub(super) fn new( - (name, value): &'a (Spanning<&'a str>, Spanning>), - vars: &'a Variables, - ) -> Self { - LookAheadArgument { - name: name.item, - value: LookAheadValue::from_input_value(value.as_ref(), vars), +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for LookAheadList<'a, S> { + fn clone(&self) -> Self { + *self + } +} + +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S> Copy for LookAheadList<'a, S> {} + +// Implemented manually to omit redundant `S: Default` trait bound, imposed by `#[derive(Default)]`. +impl<'a, S> Default for LookAheadList<'a, S> { + fn default() -> Self { + Self { + input_list: &[], + vars: None, + } + } +} + +// Implemented manually to omit redundant `S: PartialEq` trait bound, imposed by +// `#[derive(PartialEq)]`. +impl<'a, S: ScalarValue> PartialEq for LookAheadList<'a, S> { + fn eq(&self, other: &Self) -> bool { + self.iter().eq(other.iter()) + } +} + +impl<'a, S: ScalarValue> LookAheadList<'a, S> { + /// Returns an [`Iterator`] over the items of this [list]. + /// + /// [list]: https://spec.graphql.org/October2021#sec-List + pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { + self.into_iter() + } +} + +impl<'a, S: ScalarValue> IntoIterator for LookAheadList<'a, S> { + type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; + type IntoIter = look_ahead_list::Iter<'a, S>; + + fn into_iter(self) -> Self::IntoIter { + (&self).into_iter() + } +} + +impl<'a, S: ScalarValue> IntoIterator for &LookAheadList<'a, S> { + type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; + type IntoIter = look_ahead_list::Iter<'a, S>; + + fn into_iter(self) -> Self::IntoIter { + look_ahead_list::Iter { + slice_iter: self.input_list.iter(), + vars: self.vars, } } +} + +pub mod look_ahead_list { + //! [`LookAheadList`] helper definitions. + + use std::slice; + + #[cfg(doc)] + use super::LookAheadList; + use super::{BorrowedSpanning, InputValue, LookAheadValue, ScalarValue, Spanning, Variables}; - /// The argument's name - pub fn name(&'a self) -> &str { - self.name + /// [`Iterator`] over [`LookAheadList`] items ([`LookAheadValue`]s) by value. + /// + /// GraphQL variables are resolved lazily as this [`Iterator`] advances. + #[must_use] + pub struct Iter<'a, S> { + pub(super) slice_iter: slice::Iter<'a, Spanning>>, + pub(super) vars: Option<&'a Variables>, } - /// The value of the argument - pub fn value(&'a self) -> &LookAheadValue<'a, S> { - &self.value.item + impl<'a, S: ScalarValue> Iterator for Iter<'a, S> { + type Item = BorrowedSpanning<'a, LookAheadValue<'a, S>>; + + fn next(&mut self) -> Option { + let vars = self.vars; + self.slice_iter + .next() + .map(move |val| LookAheadValue::from_input_value(val.as_ref(), vars)) + } } - /// The input source span of the argument - pub fn span(&self) -> &Span { - self.value.span + impl<'a, S: ScalarValue> DoubleEndedIterator for Iter<'a, S> { + fn next_back(&mut self) -> Option { + let vars = self.vars; + self.slice_iter + .next_back() + .map(move |val| LookAheadValue::from_input_value(val.as_ref(), vars)) + } } } -/// A selection performed by a query -#[derive(Debug, Clone, PartialEq)] -pub struct LookAheadSelection<'a, S: 'a> { - pub(super) name: &'a str, - pub(super) alias: Option<&'a str>, - pub(super) arguments: Vec>, - pub(super) children: Vec>, - pub(super) applies_for: Applies<'a>, +/// [Lazy][2]-evaluated [input object] used in [look-ahead][0] operations on an executed GraphQL +/// query. +/// +/// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) +/// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation +/// [input object]: https://spec.graphql.org/October2021#sec-Input-Objects +#[derive(Debug)] +#[must_use] +pub struct LookAheadObject<'a, S> { + input_object: &'a [(Spanning, Spanning>)], + vars: Option<&'a Variables>, +} + +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for LookAheadObject<'a, S> { + fn clone(&self) -> Self { + *self + } } -// Implemented manually to omit redundant `S: Default` trait bound, imposed by -// `#[derive(Default)]`. -impl<'a, S: 'a> Default for LookAheadSelection<'a, S> { +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S> Copy for LookAheadObject<'a, S> {} + +impl<'a, S> Default for LookAheadObject<'a, S> { fn default() -> Self { Self { - name: "", - alias: None, - arguments: vec![], - children: vec![], - applies_for: Applies::All, + input_object: &[], + vars: None, } } } -impl<'a, S> LookAheadSelection<'a, S> -where - S: ScalarValue, -{ - fn should_include<'b, 'c>( - directives: Option<&'b Vec>>>, - vars: &'c Variables, - ) -> bool - where - 'b: 'a, - 'c: 'a, - { - directives - .map(|d| { - d.iter().all(|d| { - let d = &d.item; - let arguments = &d.arguments; - match (d.name.item, arguments) { - ("include", Some(a)) => a - .item - .items - .iter() - .find(|item| item.0.item == "if") - .map(|(_, v)| { - if let LookAheadValue::Scalar(s) = - LookAheadValue::from_input_value(v.as_ref(), vars).item - { - s.as_bool().unwrap_or(false) - } else { - false - } - }) - .unwrap_or(false), - ("skip", Some(a)) => a - .item - .items - .iter() - .find(|item| item.0.item == "if") - .map(|(_, v)| { - if let LookAheadValue::Scalar(b) = - LookAheadValue::from_input_value(v.as_ref(), vars).item - { - b.as_bool().map(::std::ops::Not::not).unwrap_or(false) - } else { - false - } - }) - .unwrap_or(false), - ("skip", &None) => false, - ("include", &None) => true, - (_, _) => unreachable!(), - } - }) - }) - .unwrap_or(true) +impl<'a, S: ScalarValue> PartialEq for LookAheadObject<'a, S> { + fn eq(&self, other: &Self) -> bool { + self.iter().eq(other.iter()) } +} - pub(super) fn build_from_selection( - s: &'a Selection<'a, S>, - vars: &'a Variables, - fragments: &'a HashMap<&'a str, Fragment<'a, S>>, - ) -> Option> { - Self::build_from_selection_with_parent(s, None, vars, fragments) +impl<'a, S: ScalarValue> LookAheadObject<'a, S> { + /// Returns an [`Iterator`] over this [input object]'s fields. + /// + /// [input object]: https://spec.graphql.org/October2021#sec-Input-Objects + pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { + self.into_iter() } +} - pub(super) fn build_from_selection_with_parent( - s: &'a Selection<'a, S>, - parent: Option<&mut Self>, - vars: &'a Variables, - fragments: &'a HashMap<&'a str, Fragment<'a, S>>, - ) -> Option> { - let empty: &[Selection] = &[]; - match *s { - Selection::Field(ref field) => { - let field = &field.item; - let include = Self::should_include(field.directives.as_ref(), vars); - if !include { - return None; - } - let name = field.name.item; - let alias = field.alias.as_ref().map(|a| a.item); - let arguments = field - .arguments - .as_ref() - .map(|a| &a.item) - .map(|a| { - a.items - .iter() - .map(|p| LookAheadArgument::new(p, vars)) - .collect() - }) - .unwrap_or_else(Vec::new); - let applies_for = match &parent { - Some(p) => p.applies_for, - None => Applies::All, - }; - - let mut ret = LookAheadSelection { - name, - alias, - arguments, - children: Vec::new(), - applies_for, - }; - for c in field - .selection_set - .as_ref() - .map(|s| s as &[_]) - .unwrap_or_else(|| empty) - .iter() - { - let s = LookAheadSelection::build_from_selection_with_parent( - c, - Some(&mut ret), - vars, - fragments, - ); - assert!(s.is_none()); - } - if let Some(p) = parent { - p.children.push(ret); - None - } else { - Some(ret) - } - } - Selection::FragmentSpread(ref fragment) => { - let include = Self::should_include(fragment.item.directives.as_ref(), vars); - if !include { - return None; - } - let f = fragments.get(&fragment.item.name.item).expect("a fragment"); - if let Some(parent) = parent { - for c in f.selection_set.iter() { - let s = LookAheadSelection::build_from_selection_with_parent( - c, - Some(parent), - vars, - fragments, - ); - assert!(s.is_none()); - } - } else { - for c in f.selection_set.iter() { - let s = LookAheadSelection::build_from_selection_with_parent( - c, None, vars, fragments, - ); - assert!(s.is_some()); - } - } - None - } - Selection::InlineFragment(ref inline) if parent.is_some() => { - let include = Self::should_include(inline.item.directives.as_ref(), vars); - if !include { - return None; - } - let parent = parent.unwrap(); - for c in inline.item.selection_set.iter() { - let s = LookAheadSelection::build_from_selection_with_parent( - c, - Some(parent), - vars, - fragments, - ); - assert!(s.is_none()); - if let Some(c) = inline.item.type_condition.as_ref().map(|t| t.item) { - if let Some(p) = parent.children.last_mut() { - p.applies_for = Applies::OnlyType(c); - } - } - } - None - } - _ => unimplemented!(), - } +impl<'a, S: ScalarValue> IntoIterator for LookAheadObject<'a, S> { + type Item = ( + BorrowedSpanning<'a, &'a str>, + BorrowedSpanning<'a, LookAheadValue<'a, S>>, + ); + type IntoIter = look_ahead_object::Iter<'a, S>; + + fn into_iter(self) -> Self::IntoIter { + (&self).into_iter() } +} - /// Convert a eventually type independent selection into one for a concrete type - pub fn for_explicit_type(&self, type_name: &str) -> ConcreteLookAheadSelection<'a, S> { - ConcreteLookAheadSelection { - children: self - .children - .iter() - .filter_map(|c| match c.applies_for { - Applies::OnlyType(t) if t == type_name => Some(c.for_explicit_type(type_name)), - Applies::All => Some(c.for_explicit_type(type_name)), - Applies::OnlyType(_) => None, - }) - .collect(), - name: self.name, - alias: self.alias, - arguments: self.arguments.clone(), - applies_for: self.applies_for, +impl<'a, S: ScalarValue> IntoIterator for &LookAheadObject<'a, S> { + type Item = ( + BorrowedSpanning<'a, &'a str>, + BorrowedSpanning<'a, LookAheadValue<'a, S>>, + ); + type IntoIter = look_ahead_object::Iter<'a, S>; + + fn into_iter(self) -> Self::IntoIter { + look_ahead_object::Iter { + slice_iter: self.input_object.iter(), + vars: self.vars, } } } -/// A selection performed by a query on a concrete type -#[derive(Debug, PartialEq)] -pub struct ConcreteLookAheadSelection<'a, S: 'a> { - name: &'a str, - alias: Option<&'a str>, - arguments: Vec>, - children: Vec>, - applies_for: Applies<'a>, +pub mod look_ahead_object { + //! [`LookAheadObject`] helper definitions. + + use std::slice; + + #[cfg(doc)] + use super::LookAheadList; + use super::{BorrowedSpanning, InputValue, LookAheadValue, ScalarValue, Spanning, Variables}; + + /// [`Iterator`] over [`LookAheadObject`] fields (named [`LookAheadValue`]s) by value. + /// + /// GraphQL variables are resolved lazily as this [`Iterator`] advances. + #[must_use] + pub struct Iter<'a, S> { + pub(super) slice_iter: slice::Iter<'a, (Spanning, Spanning>)>, + pub(super) vars: Option<&'a Variables>, + } + + impl<'a, S: ScalarValue> Iterator for Iter<'a, S> { + type Item = ( + BorrowedSpanning<'a, &'a str>, + BorrowedSpanning<'a, LookAheadValue<'a, S>>, + ); + + fn next(&mut self) -> Option { + let vars = self.vars; + self.slice_iter.next().map(move |(key, val)| { + ( + Spanning { + span: &key.span, + item: key.item.as_str(), + }, + LookAheadValue::from_input_value(val.as_ref(), vars), + ) + }) + } + } + + impl<'a, S: ScalarValue> DoubleEndedIterator for Iter<'a, S> { + fn next_back(&mut self) -> Option { + let vars = self.vars; + self.slice_iter.next_back().map(move |(key, val)| { + ( + Spanning { + span: &key.span, + item: key.item.as_str(), + }, + LookAheadValue::from_input_value(val.as_ref(), vars), + ) + }) + } + } } -/// Set of common methods for `ConcreteLookAheadSelection` and `LookAheadSelection`. +/// [Lazy][2]-evaluated [argument] used in [look-ahead][0] operations on an executed GraphQL query. /// -/// `'sel` lifetime is intended to point to the data that this `LookAheadSelection` (or -/// `ConcreteLookAheadSelection`) points to. -pub trait LookAheadMethods<'sel, S> { - /// Returns the original name of the field, represented by the current selection. - fn field_original_name(&self) -> &'sel str; +/// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) +/// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation +/// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments +#[derive(Debug)] +#[must_use] +pub struct LookAheadArgument<'a, S> { + name: &'a Spanning<&'a str>, + input_value: &'a Spanning>, + vars: &'a Variables, +} - /// Returns the alias of the field, represented by the current selection, if any. - fn field_alias(&self) -> Option<&'sel str>; +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for LookAheadArgument<'a, S> { + fn clone(&self) -> Self { + *self + } +} - /// Returns the (potentially aliased) name of the field, represented by the current selection. - fn field_name(&self) -> &'sel str; +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S> Copy for LookAheadArgument<'a, S> {} - /// Returns the child selection for the specified field. +impl<'a, S> LookAheadArgument<'a, S> { + /// Returns the name of this [argument]. /// - /// If a child has an alias, it will only match if the alias matches the specified `name`. - fn select_child(&self, name: &str) -> Option<&Self>; + /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments + #[must_use] + pub fn name(&self) -> &'a str { + self.name.item + } - /// Checks if a child selection with the specified `name` exists. + /// Returns the [`Span`] of this [argument]'s [`name`]. /// - /// If a child has an alias, it will only match if the alias matches the specified `name`. - fn has_child(&self, name: &str) -> bool { - self.select_child(name).is_some() + /// [`name`]: LookAheadArgument::name() + /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments + #[must_use] + pub fn name_span(&self) -> &'a Span { + &self.name.span } - /// Indicates whether the current node has any arguments. - fn has_arguments(&self) -> bool; - - /// Indicates whether the current node has any children. - fn has_children(&self) -> bool; - - /// Returns the top level arguments from the current selection. - fn arguments(&self) -> &[LookAheadArgument]; + /// Evaluates and returns the value of this [argument]. + /// + /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments + pub fn value(&self) -> LookAheadValue<'a, S> + where + S: ScalarValue, + { + LookAheadValue::from_input_value(self.input_value.as_ref(), Some(self.vars)).item + } - /// Returns the top level argument with the specified `name` from the current selection. - fn argument(&self, name: &str) -> Option<&LookAheadArgument> { - self.arguments().iter().find(|a| a.name == name) + /// Returns the [`Span`] of this [argument]'s [`value`]. + /// + /// [`value`]: LookAheadArgument::value() + /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments + #[must_use] + pub fn value_span(&self) -> &'a Span { + &self.input_value.span } +} - /// Returns the (possibly aliased) names of the top level children from the current selection. - fn child_names(&self) -> Vec<&'sel str>; +/// Children of a [`LookAheadSelection`]. +#[derive(Debug)] +#[must_use] +pub struct LookAheadChildren<'a, S> { + children: Vec>, +} - /// Returns an [`Iterator`] over the children from the current selection. - fn children(&self) -> &[Self] - where - Self: Sized; +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for LookAheadChildren<'a, S> { + fn clone(&self) -> Self { + Self { + children: self.children.clone(), + } + } +} - /// Returns the parent type, in case there is any for the current selection. - fn applies_for(&self) -> Option<&str>; +// Implemented manually to omit redundant `S: Default` trait bound, imposed by `#[derive(Default)]`. +impl<'a, S> Default for LookAheadChildren<'a, S> { + fn default() -> Self { + Self { children: vec![] } + } } -impl<'a, S> LookAheadMethods<'a, S> for ConcreteLookAheadSelection<'a, S> { - fn field_original_name(&self) -> &'a str { - self.name +impl<'a, S> LookAheadChildren<'a, S> { + /// Returns the number of children present. + #[must_use] + pub fn len(&self) -> usize { + self.children.len() } - fn field_alias(&self) -> Option<&'a str> { - self.alias + /// Indicates whether the current [selection] has any children. + /// + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn is_empty(&self) -> bool { + self.children.is_empty() } - fn field_name(&self) -> &'a str { - self.alias.unwrap_or(self.name) + /// Returns the child [selection] for the specified [field]. + /// + /// If a child has an alias, it will only match if the alias matches the specified `name`. + /// + /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn select(&self, name: &str) -> Option> { + self.children + .iter() + .find(|child| child.field_name() == name) + .copied() } - fn select_child(&self, name: &str) -> Option<&Self> { - self.children.iter().find(|c| c.field_name() == name) + /// Checks if the child [selection] with the specified `name` exists. + /// + /// If a child has an alias, it will only match if the alias matches the specified `name`. + /// + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn has_child(&self, name: &str) -> bool { + self.select(name).is_some() } - fn arguments(&self) -> &[LookAheadArgument] { - &self.arguments + /// Returns the possibly aliased names of the top-level children from the current [selection]. + pub fn names(&self) -> impl Iterator + DoubleEndedIterator + '_ { + self.children.iter().map(|sel| sel.field_name()) } - fn child_names(&self) -> Vec<&'a str> { - self.children.iter().map(|c| c.field_name()).collect() + /// Returns an [`Iterator`] over these children, by reference. + pub fn iter( + &self, + ) -> impl Iterator> + DoubleEndedIterator + '_ { + self.children.iter() } +} + +impl<'a, S: ScalarValue> IntoIterator for LookAheadChildren<'a, S> { + type Item = LookAheadSelection<'a, S>; + type IntoIter = vec::IntoIter; - fn has_arguments(&self) -> bool { - !self.arguments.is_empty() + fn into_iter(self) -> Self::IntoIter { + self.children.into_iter() } +} + +#[derive(Debug)] +pub(super) enum SelectionSource<'a, S> { + Field(&'a Field<'a, S>), + Spread { + field_name: &'a str, + set: Option<&'a [Selection<'a, S>]>, + }, +} - fn has_children(&self) -> bool { - !self.children.is_empty() +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for SelectionSource<'a, S> { + fn clone(&self) -> Self { + *self } +} - fn children(&self) -> &[Self] { - &self.children +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S> Copy for SelectionSource<'a, S> {} + +/// [Selection] of an an executed GraphQL query, used in [look-ahead][0] operations. +/// +/// [0]: https://en.wikipedia.org/wiki/Look-ahead_(backtracking) +/// [2]: https://en.wikipedia.org/wiki/Lazy_evaluation +/// [Selection]: https://spec.graphql.org/October2021#sec-Selection-Sets +#[derive(Debug)] +#[must_use] +pub struct LookAheadSelection<'a, S> { + source: SelectionSource<'a, S>, + applies_for: Applies<'a>, + vars: &'a Variables, + fragments: &'a HashMap<&'a str, Fragment<'a, S>>, +} + +// Implemented manually to omit redundant `S: Clone` trait bound, imposed by `#[derive(Clone)]`. +impl<'a, S> Clone for LookAheadSelection<'a, S> { + fn clone(&self) -> Self { + *self } +} - fn applies_for(&self) -> Option<&str> { - match self.applies_for { - Applies::OnlyType(typ) => Some(typ), - Applies::All => None, +// Implemented manually to omit redundant `S: Copy` trait bound, imposed by `#[derive(Copy)]`. +impl<'a, S> Copy for LookAheadSelection<'a, S> {} + +impl<'a, S> LookAheadSelection<'a, S> { + /// Constructs a new [`LookAheadSelection`] out of the provided params. + pub(super) fn new( + source: SelectionSource<'a, S>, + vars: &'a Variables, + fragments: &'a HashMap<&'a str, Fragment<'a, S>>, + ) -> Self { + Self { + source, + applies_for: Applies::All, + vars, + fragments, } } -} -impl<'a, S> LookAheadMethods<'a, S> for LookAheadSelection<'a, S> { - fn field_original_name(&self) -> &'a str { - self.name + /// Returns the original name of the [field], represented by the current [selection]. + /// + /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn field_original_name(&self) -> &'a str { + match self.source { + SelectionSource::Field(f) => f.name.item, + SelectionSource::Spread { field_name, .. } => field_name, + } } - fn field_alias(&self) -> Option<&'a str> { - self.alias + /// Returns the alias of the [field], represented by the current [selection], if any is present. + /// + /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn field_alias(&self) -> Option<&'a str> { + match self.source { + SelectionSource::Field(f) => f.alias.map(|a| a.item), + SelectionSource::Spread { .. } => None, + } } - fn field_name(&self) -> &'a str { - self.alias.unwrap_or(self.name) + /// Returns the potentially aliased name of the [field], represented by the current [selection]. + /// + /// [field]: https://spec.graphql.org/October2021#sec-Language.Fields + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn field_name(&self) -> &'a str { + self.field_alias() + .unwrap_or_else(|| self.field_original_name()) } - fn select_child(&self, name: &str) -> Option<&Self> { - self.children.iter().find(|c| c.field_name() == name) + /// Indicates whether the current [selection] has any [arguments]. + /// + /// [arguments]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn has_arguments(&self) -> bool { + match self.source { + SelectionSource::Field(f) => match &f.arguments { + Some(args) => !args.item.items.is_empty(), + None => false, + }, + _ => false, + } } - fn arguments(&self) -> &[LookAheadArgument] { - &self.arguments + /// Returns an [`Iterator`] over the top-level [arguments] from the current [selection], if any + /// are present. + /// + /// [arguments]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + pub fn arguments( + &self, + ) -> impl Iterator> + DoubleEndedIterator { + let opt_arguments = match self.source { + SelectionSource::Field(f) => f.arguments.as_ref(), + _ => None, + }; + + opt_arguments + .into_iter() + .flat_map(|args| args.item.iter()) + .map(|(name, arg)| LookAheadArgument { + name, + input_value: arg, + vars: self.vars, + }) } - fn child_names(&self) -> Vec<&'a str> { - self.children.iter().map(|c| c.field_name()).collect() + /// Returns the top-level [argument] from the current [selection] by its `name`, if any is + /// present. + /// + /// [argument]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + #[must_use] + pub fn argument(&self, name: &str) -> Option> { + self.arguments().find(|arg| arg.name() == name) } - fn has_arguments(&self) -> bool { - !self.arguments.is_empty() + /// Returns the children from the current [selection]. + /// + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + pub fn children(&self) -> LookAheadChildren<'a, S> + where + S: ScalarValue, + { + self.build_children(Applies::All) } - fn has_children(&self) -> bool { - !self.children.is_empty() + /// Returns the children from the current [selection] applying to the specified [type] only. + /// + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + /// [type]: https://spec.graphql.org/October2021#sec-Types + pub fn children_for_explicit_type(&self, type_name: &str) -> LookAheadChildren<'a, S> + where + S: ScalarValue, + { + self.build_children(Applies::OnlyType(type_name)) } - fn children(&self) -> &[Self] { - &self.children + fn build_children(&self, type_filter: Applies) -> LookAheadChildren<'a, S> + where + S: ScalarValue, + { + let mut builder = ChildrenBuilder { + vars: self.vars, + fragments: self.fragments, + type_filter, + output: vec![], + }; + match &self.source { + SelectionSource::Field(f) => { + builder.visit_parent_field(f, Applies::All); + } + SelectionSource::Spread { + set: Some(selections), + .. + } => { + for s in selections.iter() { + builder.visit_parent_selection(s, Applies::All); + } + } + SelectionSource::Spread { set: None, .. } => {} + } + LookAheadChildren { + children: builder.output, + } } - fn applies_for(&self) -> Option<&str> { + /// Returns the name of parent [type], in case there is any for the current [selection]. + /// + /// [selection]: https://spec.graphql.org/October2021#sec-Selection-Sets + /// [type]: https://spec.graphql.org/October2021#sec-Types + #[must_use] + pub fn applies_for(&self) -> Option<&str> { match self.applies_for { - Applies::OnlyType(typ) => Some(typ), + Applies::OnlyType(name) => Some(name), Applies::All => None, } } } +struct ChildrenBuilder<'a, 'f, S> { + vars: &'a Variables, + fragments: &'a HashMap<&'a str, Fragment<'a, S>>, + type_filter: Applies<'f>, + output: Vec>, +} + +impl<'a, 'f, S: ScalarValue> ChildrenBuilder<'a, 'f, S> { + fn visit_parent_selection( + &mut self, + selection: &'a Selection<'a, S>, + applies_for: Applies<'a>, + ) { + match selection { + Selection::Field(f) => { + self.visit_parent_field(&f.item, applies_for); + } + Selection::FragmentSpread(frag_sp) => { + let fragment = self + .fragments + .get(&frag_sp.item.name.item) + .expect("a fragment"); + for sel in &fragment.selection_set { + self.visit_parent_selection(sel, applies_for); + } + } + Selection::InlineFragment(inl_frag) => { + for sel in &inl_frag.item.selection_set { + self.visit_parent_selection(sel, applies_for); + } + } + } + } + + fn visit_parent_field(&mut self, field: &'a Field<'a, S>, applies_for: Applies<'a>) { + if let Some(selection_set) = &field.selection_set { + for sel in selection_set { + self.visit_child(sel, applies_for); + } + } + } + + fn visit_child(&mut self, selection: &'a Selection<'a, S>, applies_for: Applies<'a>) { + match selection { + Selection::Field(f) => { + let field = &f.item; + if !self.should_include_child(field.directives.as_ref()) { + return; + } + if let (Applies::OnlyType(type_name), Applies::OnlyType(filter)) = + (applies_for, self.type_filter) + { + if type_name != filter { + return; + } + } + + self.output.push(LookAheadSelection { + source: SelectionSource::Field(field), + applies_for, + vars: self.vars, + fragments: self.fragments, + }); + } + Selection::FragmentSpread(frag_sp) => { + if !self.should_include_child(frag_sp.item.directives.as_ref()) { + return; + } + let fragment = self + .fragments + .get(&frag_sp.item.name.item) + .expect("a fragment"); + for sel in &fragment.selection_set { + self.visit_child(sel, applies_for); + } + } + Selection::InlineFragment(inl_frag) => { + if !self.should_include_child(inl_frag.item.directives.as_ref()) { + return; + } + let applies_for = inl_frag + .item + .type_condition + .as_ref() + .map(|name| Applies::OnlyType(name.item)) + .unwrap_or(applies_for); + for sel in &inl_frag.item.selection_set { + self.visit_child(sel, applies_for); + } + } + } + } + + fn should_include_child<'b: 'a, 'c: 'a>( + &self, + directives: Option<&'b Vec>>>, + ) -> bool { + use std::ops::Not; + + directives + .map(|d| { + d.iter().all(|d| { + let directive = &d.item; + match (directive.name.item, &directive.arguments) { + ("include", Some(args)) => args + .item + .items + .iter() + .find(|i| i.0.item == "if") + .map(|(_, v)| { + if let LookAheadValue::Scalar(s) = + LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) + .item + { + s.as_bool().unwrap_or(false) + } else { + false + } + }) + .unwrap_or(false), + ("skip", Some(args)) => args + .item + .items + .iter() + .find(|i| i.0.item == "if") + .map(|(_, v)| { + if let LookAheadValue::Scalar(b) = + LookAheadValue::from_input_value(v.as_ref(), Some(self.vars)) + .item + { + b.as_bool().map(Not::not).unwrap_or(false) + } else { + false + } + }) + .unwrap_or(false), + ("skip", &None) => false, + ("include", &None) => true, + (_, _) => unreachable!(), + } + }) + }) + .unwrap_or(true) + } +} + #[cfg(test)] mod tests { - use std::{collections::HashMap, ops::Range}; + use std::collections::HashMap; use crate::{ ast::{Document, OwnedDocument}, graphql_vars, - parser::{SourcePosition, UnlocatedParseResult}, + parser::UnlocatedParseResult, schema::model::SchemaType, validation::test_harness::{MutationRoot, QueryRoot, SubscriptionRoot}, value::{DefaultScalarValue, ScalarValue}, @@ -537,110 +850,130 @@ mod tests { fragments } - fn span(range: Range<(usize, usize, usize)>) -> Span { - Span { - start: SourcePosition::new(range.start.0, range.start.1, range.start.2), - end: SourcePosition::new(range.end.0, range.end.1, range.end.2), + fn selection_look_ahead<'a, S: ScalarValue>( + selection: &'a Selection<'a, S>, + vars: &'a Variables, + fragments: &'a HashMap<&'a str, Fragment<'a, S>>, + ) -> LookAheadSelection<'a, S> { + let mut collector = ChildrenBuilder { + vars, + fragments, + type_filter: Applies::All, + output: vec![], + }; + collector.visit_child(selection, Applies::All); + collector.output.into_iter().next().unwrap() + } + + #[derive(Debug, PartialEq)] + enum ValueDebug<'a, S: ScalarValue> { + Null, + Scalar(&'a S), + Enum(&'a str), + List(Vec>), + Object(Vec<(&'a str, ValueDebug<'a, S>)>), + } + + impl<'a, S: ScalarValue> From> for ValueDebug<'a, S> { + fn from(look_ahead: LookAheadValue<'a, S>) -> Self { + match look_ahead { + LookAheadValue::Null => Self::Null, + LookAheadValue::Scalar(s) => Self::Scalar(s), + LookAheadValue::Enum(e) => Self::Enum(e), + LookAheadValue::List(list) => { + Self::List(list.iter().map(|val| val.item.into()).collect()) + } + LookAheadValue::Object(object) => Self::Object( + object + .iter() + .map(|(key, value)| (key.item, value.item.into())) + .collect(), + ), + } } } - #[test] - fn check_simple_query() { - let docs = parse_document_source::( - " -query Hero { - hero { - id - name + #[derive(Debug, PartialEq)] + struct LookAheadDebug<'a, S: ScalarValue> { + name: &'a str, + alias: Option<&'a str>, + applies_for: Applies<'a>, + arguments: Option)>>, + children: Vec>, } -} -", - ) - .unwrap(); - let fragments = extract_fragments(&docs); - if let crate::ast::Definition::Operation(ref op) = docs[0] { - let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { - name: "hero", - alias: None, - arguments: Vec::new(), - applies_for: Applies::All, - children: vec![ - LookAheadSelection { - name: "id", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - LookAheadSelection { - name: "name", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - ], - }; - assert_eq!(look_ahead, expected); - } else { - panic!("No Operation found"); + impl<'a, S: ScalarValue> LookAheadDebug<'a, S> { + fn new(look_ahead: &LookAheadSelection<'a, S>) -> Self { + Self::new_filtered(look_ahead, Applies::All) + } + + fn new_filtered(look_ahead: &LookAheadSelection<'a, S>, type_filter: Applies) -> Self { + Self { + name: look_ahead.field_name(), + alias: look_ahead.field_alias(), + applies_for: look_ahead.applies_for, + arguments: if look_ahead.has_arguments() { + Some( + look_ahead + .arguments() + .map(|argument| (argument.name(), ValueDebug::from(argument.value()))) + .collect(), + ) + } else { + None + }, + children: look_ahead + .build_children(type_filter) + .iter() + .map(|child| Self::new_filtered(child, type_filter)) + .collect(), + } } } #[test] - fn check_query_with_alias() { + fn check_simple_query() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - custom_hero: hero { - id - my_name: name - } -} -", + query Hero { + hero { + id + name + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", - alias: Some("custom_hero"), - arguments: Vec::new(), + alias: None, applies_for: Applies::All, + arguments: None, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), - children: Vec::new(), + arguments: None, + children: vec![], applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", - alias: Some("my_name"), - arguments: Vec::new(), + alias: None, + arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -649,66 +982,63 @@ query Hero { #[test] fn check_query_with_child() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - id - name - friends { - name - id - } - } -} -", + query Hero { + hero { + id + name + friends { + name + id + } + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "friends", alias: None, - arguments: Vec::new(), + arguments: None, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, @@ -717,7 +1047,7 @@ query Hero { }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -726,63 +1056,49 @@ query Hero { #[test] fn check_query_with_argument() { let docs = parse_document_source( + //language=GraphQL " -query Hero { - hero(episode: EMPIRE) { - id - name(uppercase: true) - } -} -", + query Hero { + hero(episode: EMPIRE) { + id + name(uppercase: true) + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let span0 = span((32, 2, 18)..(38, 2, 24)); - let span1 = span((77, 4, 24)..(81, 4, 28)); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: vec![LookAheadArgument { - name: "episode", - value: Spanning { - item: LookAheadValue::Enum("EMPIRE"), - span: &span0, - }, - }], + arguments: Some(vec![("episode", ValueDebug::Enum("EMPIRE"))]), applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: vec![LookAheadArgument { - name: "uppercase", - value: Spanning { - item: LookAheadValue::Scalar(&DefaultScalarValue::Boolean(true)), - span: &span1, - }, - }], + arguments: Some(vec![( + "uppercase", + ValueDebug::Scalar(&DefaultScalarValue::Boolean(true)), + )]), children: Vec::new(), applies_for: Applies::All, }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -791,56 +1107,46 @@ query Hero { #[test] fn check_query_with_variable() { let docs = parse_document_source::( + //language=GraphQL " -query Hero($episode: Episode) { - hero(episode: $episode) { - id - name - } -} -", + query Hero($episode: Episode) { + hero(episode: $episode) { + id + name + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {"episode": JEDI}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let span0 = span((51, 2, 18)..(59, 2, 26)); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: vec![LookAheadArgument { - name: "episode", - value: Spanning { - item: LookAheadValue::Enum("JEDI"), - span: &span0, - }, - }], + arguments: Some(vec![("episode", ValueDebug::Enum("JEDI"))]), applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -849,46 +1155,36 @@ query Hero($episode: Episode) { #[test] fn check_query_with_optional_variable() { let docs = parse_document_source::( + //language=GraphQL " -query Hero($episode: Episode) { - hero(episode: $episode) { - id - } -} -", + query Hero($episode: Episode) { + hero(episode: $episode) { + id + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let span0 = span((51, 2, 18)..(59, 2, 26)); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: vec![LookAheadArgument { - name: "episode", - value: Spanning { - item: LookAheadValue::Null, - span: &span0, - }, - }], + arguments: Some(vec![("episode", ValueDebug::Null)]), applies_for: Applies::All, - children: vec![LookAheadSelection { + children: vec![LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -896,61 +1192,58 @@ query Hero($episode: Episode) { #[test] fn check_query_with_fragment() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - id - ...commonFields - } -} + query Hero { + hero { + id + ...commonFields + } + } -fragment commonFields on Character { - name - appearsIn -} -", + fragment commonFields on Character { + name + appearsIn + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "appearsIn", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -959,50 +1252,48 @@ fragment commonFields on Character { #[test] fn check_query_with_directives() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - id @include(if: true) - name @include(if: false) - appearsIn @skip(if: true) - height @skip(if: false) - } -}", + query Hero { + hero { + id @include(if: true) + name @include(if: false) + appearsIn @skip(if: true) + height @skip(if: false) + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "height", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -1011,60 +1302,58 @@ query Hero { #[test] fn check_query_with_inline_fragments() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - name - ... on Droid { - primaryFunction - } - ... on Human { - height - } - } -}", + query Hero { + hero { + name + ... on Droid { + primaryFunction + } + ... on Human { + height + } + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "primaryFunction", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, - LookAheadSelection { + LookAheadDebug { name: "height", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -1073,63 +1362,56 @@ query Hero { #[test] fn check_query_with_multiple() { let docs = parse_document_source::( + //language=GraphQL " -query HeroAndHuman { - hero { - id - } - human { - name - } -} -", + query HeroAndHuman { + hero { + id + } + human { + name + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, - children: vec![LookAheadSelection { + children: vec![LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; - assert_eq!(look_ahead, expected); - - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[1], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + assert_eq!(LookAheadDebug::new(&look_ahead), expected); + + let look_ahead = selection_look_ahead(&op.item.selection_set[1], &vars, &fragments); + + let expected = LookAheadDebug { name: "human", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, - children: vec![LookAheadSelection { + children: vec![LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -1138,25 +1420,27 @@ query HeroAndHuman { #[test] fn check_complex_query() { let docs = parse_document_source( + //language=GraphQL " -query HeroNameAndFriends($id: Integer!, $withFriends: Boolean! = true) { - hero(id: $id) { - id - ... comparisonFields - friends @include(if: $withFriends) { - ... comparisonFields - ... on Human @skip(if: true) { mass } - } - } -} + query HeroNameAndFriends($id: Integer!, $withFriends: Boolean! = true) { + hero(id: $id) { + id + ... comparisonFields + friends @include(if: $withFriends) { + ... comparisonFields + ... on Human @skip(if: true) { mass } + } + } + } -fragment comparisonFields on Character { - __typename - name - appearsIn - ... on Droid { primaryFunction } - ... on Human { height } -}", + fragment comparisonFields on Character { + __typename + name + appearsIn + ... on Droid { primaryFunction } + ... on Human { height } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); @@ -1166,105 +1450,97 @@ fragment comparisonFields on Character { "id": 42, "withFriends": true, }; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let span0 = span((85, 2, 11)..(88, 2, 14)); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: vec![LookAheadArgument { - name: "id", - value: Spanning { - item: LookAheadValue::Scalar(&DefaultScalarValue::Int(42)), - span: &span0, - }, - }], + arguments: Some(vec![( + "id", + ValueDebug::Scalar(&DefaultScalarValue::Int(42)), + )]), applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "id", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "__typename", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "appearsIn", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "primaryFunction", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, - LookAheadSelection { + LookAheadDebug { name: "height", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, - LookAheadSelection { + LookAheadDebug { name: "friends", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - LookAheadSelection { + LookAheadDebug { name: "__typename", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "appearsIn", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - LookAheadSelection { + LookAheadDebug { name: "primaryFunction", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Droid"), }, - LookAheadSelection { + LookAheadDebug { name: "height", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, @@ -1272,7 +1548,7 @@ fragment comparisonFields on Character { }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -1281,54 +1557,54 @@ fragment comparisonFields on Character { #[test] fn check_resolve_concrete_type() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - name - ... on Droid { - primaryFunction - } - ... on Human { - height - } - } -}", + query Hero { + hero { + name + ... on Droid { + primaryFunction + } + ... on Human { + height + } + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap() - .for_explicit_type("Human"); - let expected = ConcreteLookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, children: vec![ - ConcreteLookAheadSelection { + LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }, - ConcreteLookAheadSelection { + LookAheadDebug { name: "height", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::OnlyType("Human"), }, ], }; - assert_eq!(look_ahead, expected); + assert_eq!( + LookAheadDebug::new_filtered(&look_ahead, Applies::OnlyType("Human")), + expected + ); } else { panic!("No Operation found"); } @@ -1336,138 +1612,118 @@ query Hero { #[test] fn check_select_child() { - let lookahead: LookAheadSelection = LookAheadSelection { - name: "hero", - alias: None, - arguments: Vec::new(), - applies_for: Applies::All, - children: vec![ - LookAheadSelection { - name: "id", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - LookAheadSelection { - name: "friends", - alias: None, - arguments: Vec::new(), - applies_for: Applies::All, - children: vec![ - LookAheadSelection { - name: "id", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - LookAheadSelection { - name: "name", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - ], - }, - ], - }; - let concret_query = lookahead.for_explicit_type("does not matter"); - - let id = lookahead.select_child("id"); - let concrete_id = concret_query.select_child("id"); - let expected = LookAheadSelection { - name: "id", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }; - assert_eq!(id, Some(&expected)); - assert_eq!( - concrete_id, - Some(&expected.for_explicit_type("does not matter")) - ); + let docs = parse_document_source::( + //language=GraphQL + " + query Hero { + hero { + id + friends { + id + name + } + } + } + ", + ) + .unwrap(); + let fragments = extract_fragments(&docs); - let friends = lookahead.select_child("friends"); - let concrete_friends = concret_query.select_child("friends"); - let expected = LookAheadSelection { - name: "friends", - alias: None, - arguments: Vec::new(), - applies_for: Applies::All, - children: vec![ - LookAheadSelection { - name: "id", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - LookAheadSelection { - name: "name", - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }, - ], - }; - assert_eq!(friends, Some(&expected)); - assert_eq!( - concrete_friends, - Some(&expected.for_explicit_type("does not matter")) - ); + if let crate::ast::Definition::Operation(ref op) = docs[0] { + let vars = graphql_vars! {}; + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let id = look_ahead.children().select("id").unwrap(); + let concrete_id = look_ahead + .children_for_explicit_type("does not matter") + .select("id") + .unwrap(); + let expected = LookAheadDebug { + name: "id", + alias: None, + arguments: None, + children: Vec::new(), + applies_for: Applies::All, + }; + assert_eq!(LookAheadDebug::new(&id), expected); + assert_eq!(LookAheadDebug::new(&concrete_id), expected); + + let friends = look_ahead.children().select("friends").unwrap(); + let concrete_friends = look_ahead + .children_for_explicit_type("does not matter") + .select("friends") + .unwrap(); + let expected = LookAheadDebug { + name: "friends", + alias: None, + arguments: None, + applies_for: Applies::All, + children: vec![ + LookAheadDebug { + name: "id", + alias: None, + arguments: None, + children: Vec::new(), + applies_for: Applies::All, + }, + LookAheadDebug { + name: "name", + alias: None, + arguments: None, + children: Vec::new(), + applies_for: Applies::All, + }, + ], + }; + assert_eq!(LookAheadDebug::new(&friends), expected); + assert_eq!(LookAheadDebug::new(&concrete_friends), expected); + } } #[test] // https://github.com/graphql-rust/juniper/issues/335 fn check_fragment_with_nesting() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - ...heroFriendNames - } -} + query Hero { + hero { + ...heroFriendNames + } + } -fragment heroFriendNames on Hero { - friends { name } -} -", + fragment heroFriendNames on Hero { + friends { name } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); - let expected = LookAheadSelection { + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let expected = LookAheadDebug { name: "hero", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, - children: vec![LookAheadSelection { + children: vec![LookAheadDebug { name: "friends", alias: None, - arguments: Vec::new(), + arguments: None, applies_for: Applies::All, - children: vec![LookAheadSelection { + children: vec![LookAheadDebug { name: "name", alias: None, - arguments: Vec::new(), + arguments: None, children: Vec::new(), applies_for: Applies::All, }], }], }; - assert_eq!(look_ahead, expected); + assert_eq!(LookAheadDebug::new(&look_ahead), expected); } else { panic!("No Operation found"); } @@ -1476,16 +1732,17 @@ fragment heroFriendNames on Hero { #[test] fn check_visitability() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero(episode: EMPIRE) { - name - aliasedName: name - friends { - name - } - } -} + query Hero { + hero(episode: EMPIRE) { + name + aliasedName: name + friends { + name + } + } + } ", ) .unwrap(); @@ -1493,73 +1750,82 @@ query Hero { if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap(); + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); assert_eq!(look_ahead.field_original_name(), "hero"); assert!(look_ahead.field_alias().is_none()); assert_eq!(look_ahead.field_name(), "hero"); assert!(look_ahead.has_arguments()); - let args = look_ahead.arguments(); - assert_eq!(args[0].name(), "episode"); - assert_eq!(args[0].value(), &LookAheadValue::Enum("EMPIRE")); + let arg = look_ahead.arguments().next().unwrap(); + assert_eq!(arg.name(), "episode"); + assert_eq!(ValueDebug::from(arg.value()), ValueDebug::Enum("EMPIRE")); - assert!(look_ahead.has_children()); + let children = look_ahead.children(); + assert!(!children.is_empty()); assert_eq!( - look_ahead.child_names(), + children.names().collect::>(), vec!["name", "aliasedName", "friends"] ); - let mut children = look_ahead.children().into_iter(); + let mut child_iter = children.iter(); - let name_child = children.next().unwrap(); - assert!(look_ahead.has_child("name")); - assert_eq!(name_child, look_ahead.select_child("name").unwrap()); + let name_child = child_iter.next().unwrap(); + assert!(children.has_child("name")); + assert_eq!( + LookAheadDebug::new(name_child), + LookAheadDebug::new(&children.select("name").unwrap()) + ); assert_eq!(name_child.field_original_name(), "name"); assert_eq!(name_child.field_alias(), None); assert_eq!(name_child.field_name(), "name"); assert!(!name_child.has_arguments()); - assert!(!name_child.has_children()); + assert!(name_child.children().is_empty()); - let aliased_name_child = children.next().unwrap(); - assert!(look_ahead.has_child("aliasedName")); + let aliased_name_child = child_iter.next().unwrap(); + assert!(children.has_child("aliasedName")); assert_eq!( - aliased_name_child, - look_ahead.select_child("aliasedName").unwrap() + LookAheadDebug::new(aliased_name_child), + LookAheadDebug::new(&children.select("aliasedName").unwrap()) ); assert_eq!(aliased_name_child.field_original_name(), "name"); assert_eq!(aliased_name_child.field_alias(), Some("aliasedName")); assert_eq!(aliased_name_child.field_name(), "aliasedName"); assert!(!aliased_name_child.has_arguments()); - assert!(!aliased_name_child.has_children()); + assert!(aliased_name_child.children().is_empty()); - let friends_child = children.next().unwrap(); - assert!(look_ahead.has_child("friends")); - assert_eq!(friends_child, look_ahead.select_child("friends").unwrap()); + let friends_child = child_iter.next().unwrap(); + assert!(children.has_child("friends")); + assert_eq!( + LookAheadDebug::new(friends_child), + LookAheadDebug::new(&children.select("friends").unwrap()) + ); assert_eq!(friends_child.field_original_name(), "friends"); assert_eq!(friends_child.field_alias(), None); assert_eq!(friends_child.field_name(), "friends"); assert!(!friends_child.has_arguments()); - assert!(friends_child.has_children()); - assert_eq!(friends_child.child_names(), vec!["name"]); + assert!(!friends_child.children().is_empty()); + assert_eq!( + friends_child.children().names().collect::>(), + vec!["name"] + ); - assert!(children.next().is_none()); + assert!(child_iter.next().is_none()); - let mut friends_children = friends_child.children().into_iter(); - let child = friends_children.next().unwrap(); - assert!(friends_child.has_child("name")); - assert_eq!(child, friends_child.select_child("name").unwrap()); + let friends_children = friends_child.children(); + let mut friends_child_iter = friends_children.iter(); + let child = friends_child_iter.next().unwrap(); + assert!(friends_children.has_child("name")); + assert_eq!( + LookAheadDebug::new(child), + LookAheadDebug::new(&children.select("name").unwrap()) + ); assert_eq!(child.field_original_name(), "name"); assert_eq!(child.field_alias(), None); assert_eq!(child.field_name(), "name"); assert!(!child.has_arguments()); - assert!(!child.has_children()); + assert!(child.children().is_empty()); - assert!(friends_children.next().is_none()); + assert!(friends_child_iter.next().is_none()); } else { panic!("No Operation found"); } @@ -1568,31 +1834,27 @@ query Hero { #[test] fn check_resolves_applies_for() { let docs = parse_document_source::( + //language=GraphQL " -query Hero { - hero { - ... on Human { - height - } - } -}", + query Hero { + hero { + ... on Human { + height + } + } + } + ", ) .unwrap(); let fragments = extract_fragments(&docs); if let crate::ast::Definition::Operation(ref op) = docs[0] { let vars = graphql_vars! {}; - let look_ahead = LookAheadSelection::build_from_selection( - &op.item.selection_set[0], - &vars, - &fragments, - ) - .unwrap() - .for_explicit_type("Human"); - - let mut children = look_ahead.children().into_iter(); + let look_ahead = selection_look_ahead(&op.item.selection_set[0], &vars, &fragments); + + let mut children = look_ahead.children_for_explicit_type("Human").into_iter(); let heights_child = children.next().unwrap(); - assert_eq!(heights_child.name, "height"); + assert_eq!(heights_child.field_original_name(), "height"); assert_eq!(heights_child.applies_for, Applies::OnlyType("Human")); assert_eq!(heights_child.applies_for().unwrap(), "Human"); } else { diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 4150689ab..d091bbf8d 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -37,7 +37,7 @@ use crate::{ pub use self::{ look_ahead::{ - Applies, ConcreteLookAheadSelection, LookAheadArgument, LookAheadMethods, + Applies, LookAheadArgument, LookAheadChildren, LookAheadList, LookAheadObject, LookAheadSelection, LookAheadValue, }, owned_executor::OwnedExecutor, @@ -698,48 +698,38 @@ where }; self.parent_selection_set .and_then(|p| { - // Search the parent's fields to find this field within the set - let found_field = p.iter().find(|&x| { - match *x { + // Search the parent's fields to find this field within the selection set. + p.iter().find_map(|x| { + match x { Selection::Field(ref field) => { let field = &field.item; // TODO: support excludes. let name = field.name.item; let alias = field.alias.as_ref().map(|a| a.item); - alias.unwrap_or(name) == field_name + + (alias.unwrap_or(name) == field_name).then(|| { + LookAheadSelection::new( + look_ahead::SelectionSource::Field(field), + self.variables, + self.fragments, + ) + }) } - _ => false, + Selection::FragmentSpread(_) | Selection::InlineFragment(_) => None, } - }); - if let Some(p) = found_field { - LookAheadSelection::build_from_selection(p, self.variables, self.fragments) - } else { - None - } + }) }) .unwrap_or_else(|| { - // We didn't find a field in the parent's selection matching - // this field, which means we're inside a FragmentSpread - let mut ret = LookAheadSelection { - name: field_name, - alias: None, - arguments: Vec::new(), - children: Vec::new(), - applies_for: Applies::All, - }; - - // Add in all the children - this will mutate `ret` - if let Some(selection_set) = self.current_selection_set { - for c in selection_set { - LookAheadSelection::build_from_selection_with_parent( - c, - Some(&mut ret), - self.variables, - self.fragments, - ); - } - } - ret + // We didn't find this field in the parent's selection matching it, which means + // we're inside a `FragmentSpread`. + LookAheadSelection::new( + look_ahead::SelectionSource::Spread { + field_name, + set: self.current_selection_set, + }, + self.variables, + self.fragments, + ) }) } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 2882fb8e8..7b8bc4f73 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -71,8 +71,9 @@ pub use crate::{ }, executor::{ Applies, Context, ExecutionError, ExecutionResult, Executor, FieldError, FieldResult, - FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadMethods, - LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables, + FromContext, IntoFieldError, IntoResolvable, LookAheadArgument, LookAheadChildren, + LookAheadList, LookAheadObject, LookAheadSelection, LookAheadValue, OwnedExecutor, + Registry, ValuesStream, Variables, }, introspection::IntrospectionFormat, macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult}, diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index b3b55426e..e6c81b60b 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -1247,7 +1247,7 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// /// ```rust -/// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; +/// # use juniper::{graphql_interface, graphql_object, Executor, ScalarValue}; /// # /// #[graphql_interface] /// // NOTICE: Specifying `ScalarValue` as existing type parameter. @@ -1754,7 +1754,7 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// [`Executor`] does so. /// /// ``` -/// # use juniper::{graphql_object, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +/// # use juniper::{graphql_object, Executor, GraphQLObject, ScalarValue}; /// # /// struct Human { /// name: String, diff --git a/tests/integration/tests/codegen_interface_attr_trait.rs b/tests/integration/tests/codegen_interface_attr_trait.rs index c30dd2d54..51f1f9301 100644 --- a/tests/integration/tests/codegen_interface_attr_trait.rs +++ b/tests/integration/tests/codegen_interface_attr_trait.rs @@ -2655,8 +2655,6 @@ mod inferred_custom_context_from_field { } mod executor { - use juniper::LookAheadMethods as _; - use super::*; #[graphql_interface(for = [Human, Droid], scalar = S)] diff --git a/tests/integration/tests/codegen_object_attr.rs b/tests/integration/tests/codegen_object_attr.rs index 1c8210403..addfcb017 100644 --- a/tests/integration/tests/codegen_object_attr.rs +++ b/tests/integration/tests/codegen_object_attr.rs @@ -1929,8 +1929,6 @@ mod inferred_custom_context_from_field { } mod executor { - use juniper::LookAheadMethods as _; - use super::*; struct Human; diff --git a/tests/integration/tests/codegen_subscription_attr.rs b/tests/integration/tests/codegen_subscription_attr.rs index 6a564c22a..e9ab167a2 100644 --- a/tests/integration/tests/codegen_subscription_attr.rs +++ b/tests/integration/tests/codegen_subscription_attr.rs @@ -1697,8 +1697,6 @@ mod inferred_custom_context_from_field { } mod executor { - use juniper::LookAheadMethods as _; - use super::*; struct Human; diff --git a/tests/integration/tests/issue_371.rs b/tests/integration/tests/issue_371.rs index 26c1903b2..41f227e0b 100644 --- a/tests/integration/tests/issue_371.rs +++ b/tests/integration/tests/issue_371.rs @@ -5,8 +5,7 @@ //! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn). use juniper::{ - graphql_object, graphql_vars, EmptyMutation, EmptySubscription, Executor, - LookAheadMethods as _, RootNode, ScalarValue, + graphql_object, graphql_vars, EmptyMutation, EmptySubscription, Executor, RootNode, ScalarValue, }; pub struct Context; @@ -19,13 +18,21 @@ pub struct Query; impl Query { fn users<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec { let lh = executor.look_ahead(); + assert_eq!(lh.field_name(), "users"); + + _ = lh.children(); + vec![User] } fn countries<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec { let lh = executor.look_ahead(); + assert_eq!(lh.field_name(), "countries"); + + _ = lh.children(); + vec![Country] } } diff --git a/tests/integration/tests/issue_398.rs b/tests/integration/tests/issue_398.rs index 31df9aeea..4fad2d125 100644 --- a/tests/integration/tests/issue_398.rs +++ b/tests/integration/tests/issue_398.rs @@ -12,8 +12,10 @@ struct Query; #[graphql_object] impl Query { fn users(executor: &Executor<'_, '_, (), S>) -> Vec { + assert_eq!(executor.look_ahead().field_name(), "users"); + // This doesn't cause a panic. - executor.look_ahead(); + _ = executor.look_ahead().children(); vec![User { country: Country { id: 1 }, @@ -28,8 +30,10 @@ struct User { #[graphql_object] impl User { fn country(&self, executor: &Executor<'_, '_, (), S>) -> &Country { + assert_eq!(executor.look_ahead().field_name(), "country"); + // This panics! - executor.look_ahead(); + _ = executor.look_ahead().children(); &self.country } diff --git a/tests/integration/tests/issue_500.rs b/tests/integration/tests/issue_500.rs index 1fcafb79f..d56c797bb 100644 --- a/tests/integration/tests/issue_500.rs +++ b/tests/integration/tests/issue_500.rs @@ -10,7 +10,9 @@ struct Query; #[graphql_object] impl Query { fn users(executor: &Executor<'_, '_, (), S>) -> Vec { - executor.look_ahead(); + assert_eq!(executor.look_ahead().field_name(), "users"); + + _ = executor.look_ahead().children(); vec![User { city: City { @@ -27,7 +29,10 @@ struct User { #[graphql_object] impl User { fn city(&self, executor: &Executor<'_, '_, (), S>) -> &City { - executor.look_ahead(); + assert_eq!(executor.look_ahead().field_name(), "city"); + + _ = executor.look_ahead().children(); + &self.city } } @@ -39,7 +44,10 @@ struct City { #[graphql_object] impl City { fn country(&self, executor: &Executor<'_, '_, (), S>) -> &Country { - executor.look_ahead(); + assert_eq!(executor.look_ahead().field_name(), "country"); + + _ = executor.look_ahead().children(); + &self.country } }