diff --git a/Cargo.toml b/Cargo.toml index 602e85f..246ab28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ thiserror = "1.0.11" [dev-dependencies] pretty_assertions = "0.5.0" +k9 = "0.11.0" \ No newline at end of file diff --git a/src/common.rs b/src/common.rs index aae4d39..295dd70 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,6 +5,7 @@ use combine::easy::Error; use combine::error::StreamError; use combine::combinator::{many, many1, optional, position, choice}; +use crate::schema::{EnumType, InputObjectType, InterfaceType, ObjectType, ScalarType, UnionType}; use crate::tokenizer::{Kind as T, Token, TokenStream}; use crate::helpers::{punct, ident, kind, name}; use crate::position::Pos; @@ -65,6 +66,23 @@ pub enum Type<'a, T: Text<'a>> { NonNullType(Box>), } +#[derive(Debug, Clone, PartialEq)] +pub enum CompositeType<'a, T: Text<'a>> { + Object(ObjectType<'a, T>), + Interface(InterfaceType<'a, T>), + Union(UnionType<'a, T>), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum InputType<'a, T: Text<'a>> { + Scalar(ScalarType<'a, T>), + Enum(EnumType<'a, T>), + InputObject(InputObjectType<'a, T>), + List(Box>), + NonNullType(Box>), +} + + impl Number { /// Returns a number as i64 if it fits the type pub fn as_i64(&self) -> Option { diff --git a/src/lib.rs b/src/lib.rs index 3649bdf..63e321d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,7 @@ mod common; + #[macro_use] mod format; mod position; @@ -102,6 +103,7 @@ mod tokenizer; mod helpers; pub mod query; pub mod schema; +pub mod validation; pub use crate::query::parse_query; pub use crate::schema::parse_schema; diff --git a/src/query/mod.rs b/src/query/mod.rs index 1d21ac1..3b88607 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -4,6 +4,7 @@ mod ast; mod error; mod format; mod grammar; +pub mod query_visitor; pub use self::grammar::{parse_query, consume_definition}; diff --git a/src/query/query_visitor.rs b/src/query/query_visitor.rs new file mode 100644 index 0000000..900d421 --- /dev/null +++ b/src/query/query_visitor.rs @@ -0,0 +1,402 @@ +//! Query syntax tree traversal. +//! +//! Each method of [`QueryVisitor`] is a hook that can be overridden to customize the behavior when +//! visiting the corresponding type of node. By default, the methods don't do anything. The actual +//! walking of the ast is done by the `walk_*` functions. So to run a visitor over the whole +//! document you should use [`walk_document`]. +//! +//! Example: +//! +//! ``` +//! use graphql_parser::query::{ +//! Field, +//! parse_query, +//! query_visitor::{QueryVisitor, walk_document}, +//! }; +//! +//! struct FieldsCounter { +//! count: usize, +//! } +//! +//! impl FieldsCounter { +//! fn new() -> Self { +//! Self { count: 0 } +//! } +//! } +//! +//! impl<'ast> QueryVisitor<'ast> for FieldsCounter { +//! fn visit_field(&mut self, node: &'ast Field) { +//! self.count += 1 +//! } +//! } +//! +//! fn main() { +//! let mut number_of_type = FieldsCounter::new(); +//! +//! let doc = parse_query(r#" +//! query TestQuery { +//! users { +//! id +//! country { +//! id +//! } +//! } +//! } +//! "#).expect("Failed to parse query"); +//! +//! walk_document(&mut number_of_type, &doc, None); +//! +//! assert_eq!(number_of_type.count, 2); +//! } +//! ``` +//! +//! [`QueryVisitor`]: /graphql_parser/query/query_visitor/trait.QueryVisitor.html +//! [`walk_document`]: /graphql_parser/query/query_visitor/fn.walk_document.html + +#![allow(unused_variables)] + +use super::ast::*; +use crate::validation::TypeInfo; + +/// Trait for easy query syntax tree traversal. +/// +/// See [module docs](/graphql_parser/query/query_visitor/index.html) for more info. +pub trait QueryVisitor<'ast, T: Text<'ast>> { + fn visit_document( + &mut self, + node: &'ast Document<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_definition( + &mut self, + node: &'ast Definition<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_fragment_definition( + &mut self, + node: &'ast FragmentDefinition<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_operation_definition( + &mut self, + node: &'ast OperationDefinition<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_query( + &mut self, + node: &'ast Query<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_mutation( + &mut self, + node: &'ast Mutation<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_subscription( + &mut self, + node: &'ast Subscription<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_selection_set( + &mut self, + node: &'ast SelectionSet<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_variable_definition( + &mut self, + node: &'ast VariableDefinition<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_selection( + &mut self, + node: &'ast Selection<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_field( + &mut self, + node: &'ast Field<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_fragment_spread( + &mut self, + node: &'ast FragmentSpread<'ast, T>, + type_info: &mut Option>, + ) { + } + + fn visit_inline_fragment( + &mut self, + node: &'ast InlineFragment<'ast, T>, + type_info: &mut Option>, + ) { + } +} + +/// Walk a query syntax tree and call the visitor methods for each type of node. +/// +/// This function is how you should initiate a visitor. +pub fn walk_document<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Document<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_document(node, &mut None); + } + + visitor.visit_document(node, type_info); + + for def in &node.definitions { + walk_definition(visitor, def, type_info); + } +} + +fn walk_definition<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Definition<'ast, T>, + type_info: &mut Option>, +) { + use super::ast::Definition::*; + + if let Some(info) = type_info { + info.visit_definition(node, &mut None); + } + + visitor.visit_definition(node, type_info); + + match node { + Operation(inner) => { + visitor.visit_operation_definition(inner, type_info); + walk_operation_definition(visitor, inner, type_info); + } + Fragment(inner) => { + visitor.visit_fragment_definition(inner, type_info); + walk_fragment_definition(visitor, inner, type_info); + } + } +} + +fn walk_fragment_definition<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast FragmentDefinition<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_fragment_definition(node, &mut None); + } + + visitor.visit_fragment_definition(node, type_info); + + walk_selection_set(visitor, &node.selection_set, type_info); +} + +fn walk_operation_definition<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast OperationDefinition<'ast, T>, + type_info: &mut Option>, +) { + use super::ast::OperationDefinition::*; + + if let Some(info) = type_info { + info.visit_operation_definition(node, &mut None); + } + + visitor.visit_operation_definition(node, type_info); + + match node { + SelectionSet(inner) => { + visitor.visit_selection_set(inner, type_info); + walk_selection_set(visitor, inner, type_info); + } + Query(inner) => { + visitor.visit_query(inner, type_info); + walk_query(visitor, inner, type_info); + } + Mutation(inner) => { + visitor.visit_mutation(inner, type_info); + walk_mutation(visitor, inner, type_info); + } + Subscription(inner) => { + visitor.visit_subscription(inner, type_info); + walk_subscription(visitor, inner, type_info); + } + } +} + +fn walk_query<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Query<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_query(node, &mut None); + } + + visitor.visit_query(node, type_info); + + for var_def in &node.variable_definitions { + visitor.visit_variable_definition(var_def, type_info); + walk_variable_definition(visitor, var_def, type_info); + } + + visitor.visit_selection_set(&node.selection_set, type_info); + walk_selection_set(visitor, &node.selection_set, type_info); +} + +fn walk_mutation<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Mutation<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_mutation(node, &mut None); + } + + visitor.visit_mutation(node, type_info); + + for var_def in &node.variable_definitions { + visitor.visit_variable_definition(var_def, type_info); + walk_variable_definition(visitor, var_def, type_info); + } + + visitor.visit_selection_set(&node.selection_set, type_info); + walk_selection_set(visitor, &node.selection_set, type_info); +} + +fn walk_subscription<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Subscription<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_subscription(node, &mut None); + } + + visitor.visit_subscription(node, type_info); + + for var_def in &node.variable_definitions { + visitor.visit_variable_definition(var_def, type_info); + walk_variable_definition(visitor, var_def, type_info); + } + + visitor.visit_selection_set(&node.selection_set, type_info); + walk_selection_set(visitor, &node.selection_set, type_info); +} + +fn walk_selection_set<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast SelectionSet<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_selection_set(node, &mut None); + } + + visitor.visit_selection_set(node, type_info); + + for selection in &node.items { + visitor.visit_selection(selection, type_info); + walk_selection(visitor, selection, type_info); + } +} + +fn walk_variable_definition<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast VariableDefinition<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_variable_definition(node, &mut None); + } + + visitor.visit_variable_definition(node, type_info); +} + +fn walk_selection<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Selection<'ast, T>, + type_info: &mut Option>, +) { + use super::ast::Selection::*; + + if let Some(info) = type_info { + info.visit_selection(node, &mut None); + } + + visitor.visit_selection(node, type_info); + + match node { + Field(inner) => { + visitor.visit_field(inner, type_info); + walk_field(visitor, inner, type_info); + } + FragmentSpread(inner) => { + visitor.visit_fragment_spread(inner, type_info); + walk_fragment_spread(visitor, inner, type_info); + } + InlineFragment(inner) => { + visitor.visit_inline_fragment(inner, type_info); + walk_inline_fragment(visitor, inner, type_info); + } + } +} + +fn walk_field<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast Field<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_field(node, &mut None); + } + + visitor.visit_field(node, type_info); +} + +fn walk_fragment_spread<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast FragmentSpread<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_fragment_spread(node, &mut None); + } + + visitor.visit_fragment_spread(node, type_info); +} + +fn walk_inline_fragment<'ast, T: Text<'ast>, V: QueryVisitor<'ast, T>>( + visitor: &mut V, + node: &'ast InlineFragment<'ast, T>, + type_info: &mut Option>, +) { + if let Some(info) = type_info { + info.visit_inline_fragment(node, &mut None); + } + + visitor.visit_inline_fragment(node, type_info); + + walk_selection_set(visitor, &node.selection_set, type_info); +} diff --git a/src/schema/ast.rs b/src/schema/ast.rs index 39ba829..ee028db 100644 --- a/src/schema/ast.rs +++ b/src/schema/ast.rs @@ -518,4 +518,4 @@ impl FromStr for DirectiveLocation { Ok(val) } -} +} \ No newline at end of file diff --git a/src/schema/mod.rs b/src/schema/mod.rs index 49d5e9d..2f72eb8 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -4,6 +4,7 @@ mod ast; mod grammar; mod error; mod format; +pub mod schema_visitor; pub use self::ast::*; pub use self::error::ParseError; diff --git a/src/schema/schema_visitor.rs b/src/schema/schema_visitor.rs new file mode 100644 index 0000000..ffe8758 --- /dev/null +++ b/src/schema/schema_visitor.rs @@ -0,0 +1,218 @@ +//! Schema syntax tree traversal. +//! +//! Each method of [`SchemaVisitor`] is a hook that can be overridden to customize the behavior when +//! visiting the corresponding type of node. By default, the methods don't do anything. The actual +//! walking of the ast is done by the `walk_*` functions. So to run a visitor over the whole +//! document you should use [`walk_document`]. +//! +//! Example: +//! +//! ``` +//! use graphql_parser::schema::{ +//! ObjectType, +//! parse_schema, +//! schema_visitor::{SchemaVisitor, walk_document}, +//! }; +//! +//! struct ObjectTypesCounter { +//! count: usize, +//! } +//! +//! impl ObjectTypesCounter { +//! fn new() -> Self { +//! Self { count: 0 } +//! } +//! } +//! +//! impl<'ast> SchemaVisitor<'ast> for ObjectTypesCounter { +//! fn visit_object_type(&mut self, node: &'ast ObjectType) { +//! self.count += 1; +//! } +//! } +//! +//! fn main() { +//! let mut number_of_type = ObjectTypesCounter::new(); +//! +//! let doc = parse_schema(r#" +//! schema { +//! query: Query +//! } +//! +//! type Query { +//! users: [User!]! +//! } +//! +//! type User { +//! id: ID! +//! } +//! "#).expect("Failed to parse schema"); +//! +//! walk_document(&mut number_of_type, &doc); +//! +//! assert_eq!(number_of_type.count, 2); +//! } +//! ``` +//! +//! [`SchemaVisitor`]: /graphql_parser/schema/schema_visitor/trait.SchemaVisitor.html +//! [`walk_document`]: /graphql_parser/schema/schema_visitor/fn.walk_document.html + +#![allow(unused_variables)] + +use super::ast::*; + +/// Trait for easy schema syntax tree traversal. +/// +/// See [module docs](/graphql_parser/schema/schema_visitor/index.html) for more info. +pub trait SchemaVisitor<'ast> { + fn visit_document(&mut self, doc: &'ast Document<'ast, &'ast str>) {} + + fn visit_schema_definition(&mut self, node: &'ast SchemaDefinition<'ast, &'ast str>) {} + + fn visit_directive_definition(&mut self, node: &'ast DirectiveDefinition<'ast, &'ast str>) {} + + fn visit_type_definition(&mut self, node: &'ast TypeDefinition<'ast, &'ast str>) {} + + fn visit_scalar_type(&mut self, node: &'ast ScalarType<'ast, &'ast str>) {} + + fn visit_object_type(&mut self, node: &'ast ObjectType<'ast, &'ast str>) {} + + fn visit_interface_type(&mut self, node: &'ast InterfaceType<'ast, &'ast str>) {} + + fn visit_union_type(&mut self, node: &'ast UnionType<'ast, &'ast str>) {} + + fn visit_enum_type(&mut self, node: &'ast EnumType<'ast, &'ast str>) {} + + fn visit_input_object_type(&mut self, node: &'ast InputObjectType<'ast, &'ast str>) {} + + fn visit_type_extension(&mut self, node: &'ast TypeExtension<'ast, &'ast str>) {} + + fn visit_scalar_type_extension(&mut self, node: &'ast ScalarTypeExtension<'ast, &'ast str>) {} + + fn visit_object_type_extension(&mut self, node: &'ast ObjectTypeExtension<'ast, &'ast str>) {} + + fn visit_interface_type_extension(&mut self, node: &'ast InterfaceTypeExtension<'ast, &'ast str>) {} + + fn visit_union_type_extension(&mut self, node: &'ast UnionTypeExtension<'ast, &'ast str>) {} + + fn visit_enum_type_extension(&mut self, node: &'ast EnumTypeExtension<'ast, &'ast str>) {} + + fn visit_input_object_type_extension(&mut self, node: &'ast InputObjectTypeExtension<'ast, &'ast str>) {} +} + +/// Walk a schema syntax tree and call the visitor methods for each type of node. +/// +/// This function is how you should initiate a visitor. +pub fn walk_document<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, doc: &'ast Document<'ast, &'ast str>) { + use super::ast::Definition::*; + + for def in &doc.definitions { + match def { + SchemaDefinition(inner) => { + visitor.visit_schema_definition(inner); + walk_schema_definition(visitor, inner); + } + TypeDefinition(inner) => { + visitor.visit_type_definition(inner); + walk_type_definition(visitor, inner); + } + TypeExtension(inner) => { + visitor.visit_type_extension(inner); + walk_type_extension(visitor, inner); + } + DirectiveDefinition(inner) => { + visitor.visit_directive_definition(inner); + walk_directive_definition(visitor, inner); + } + } + } +} + +fn walk_schema_definition<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast SchemaDefinition<'ast, &'ast str>) {} + +fn walk_directive_definition<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast DirectiveDefinition<'ast, &'ast str>) {} + +fn walk_type_definition<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast TypeDefinition<'ast, &'ast str>) { + use super::ast::TypeDefinition::*; + + match node { + Scalar(inner) => { + visitor.visit_scalar_type(inner); + walk_scalar_type(visitor, inner); + } + Object(inner) => { + visitor.visit_object_type(inner); + walk_object_type(visitor, inner); + } + Interface(inner) => { + visitor.visit_interface_type(inner); + walk_interface_type(visitor, inner); + } + Union(inner) => { + visitor.visit_union_type(inner); + walk_union_type(visitor, inner); + } + Enum(inner) => { + visitor.visit_enum_type(inner); + walk_enum_type(visitor, inner); + } + InputObject(inner) => { + visitor.visit_input_object_type(inner); + walk_input_object_type(visitor, inner); + } + } +} + +fn walk_scalar_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast ScalarType<'ast, &'ast str>) {} + +fn walk_object_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast ObjectType<'ast, &'ast str>) {} + +fn walk_interface_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast InterfaceType<'ast, &'ast str>) {} + +fn walk_union_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast UnionType<'ast, &'ast str>) {} + +fn walk_enum_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast EnumType<'ast, &'ast str>) {} + +fn walk_input_object_type<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast InputObjectType<'ast, &'ast str>) {} + +fn walk_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast TypeExtension<'ast, &'ast str>) { + use super::ast::TypeExtension::*; + + match node { + Scalar(inner) => { + visitor.visit_scalar_type_extension(inner); + walk_scalar_type_extension(visitor, inner); + } + Object(inner) => { + visitor.visit_object_type_extension(inner); + walk_object_type_extension(visitor, inner); + } + Interface(inner) => { + visitor.visit_interface_type_extension(inner); + walk_interface_type_extension(visitor, inner); + } + Union(inner) => { + visitor.visit_union_type_extension(inner); + walk_union_type_extension(visitor, inner); + } + Enum(inner) => { + visitor.visit_enum_type_extension(inner); + walk_enum_type_extension(visitor, inner); + } + InputObject(inner) => { + visitor.visit_input_object_type_extension(inner); + walk_input_object_type_extension(visitor, inner); + } + } +} + +fn walk_scalar_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast ScalarTypeExtension<'ast, &'ast str>) {} + +fn walk_object_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast ObjectTypeExtension<'ast, &'ast str>) {} + +fn walk_interface_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast InterfaceTypeExtension<'ast, &'ast str>) {} + +fn walk_union_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast UnionTypeExtension<'ast, &'ast str>) {} + +fn walk_enum_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast EnumTypeExtension<'ast, &'ast str>) {} + +fn walk_input_object_type_extension<'ast, V: SchemaVisitor<'ast>>(visitor: &mut V, node: &'ast InputObjectTypeExtension<'ast, &'ast str>) {} diff --git a/src/validation/mod.rs b/src/validation/mod.rs new file mode 100644 index 0000000..d5bc458 --- /dev/null +++ b/src/validation/mod.rs @@ -0,0 +1,2 @@ +pub mod type_info; +pub use type_info::TypeInfo; \ No newline at end of file diff --git a/src/validation/type_info.rs b/src/validation/type_info.rs new file mode 100644 index 0000000..c39dbcb --- /dev/null +++ b/src/validation/type_info.rs @@ -0,0 +1,186 @@ +//! TypeInfo is a utility class which, given a GraphQL schema, can keep track +//! of the current field and type definitions at any point in a GraphQL document +//! AST during a recursive descent by calling `enter(node)` and `leave(node)`. + +use crate::{ + common::{CompositeType, InputType}, + query::{query_visitor::QueryVisitor, Field}, + schema::{Directive, Document, EnumValue, Text, Type, Value}, +}; +pub struct TypeInfo<'ast, T: Text<'ast>> { + schema: Document<'ast, T>, + type_stack: Vec>>, + parent_type_stack: Vec>>, + input_type_stack: Vec>>, + field_def_stack: Vec>>, + default_value_stack: Vec>, + directive: Option>, + argument: Option<(T::Value, Value<'ast, T>)>, + enum_value: Option>, +} + +impl<'ast, T: Text<'ast>> TypeInfo<'ast, T> { + pub fn new(schema: Document<'ast, T>) -> TypeInfo<'ast, T> { + TypeInfo { + schema, + type_stack: Vec::new(), + parent_type_stack: Vec::new(), + input_type_stack: Vec::new(), + field_def_stack: Vec::new(), + default_value_stack: Vec::new(), + directive: None, + argument: None, + enum_value: None, + } + } + + pub fn get_type(&self) -> &Option> { + let type_stack_length = self.type_stack.len(); + + if type_stack_length > 0 { + &self.type_stack[type_stack_length - 1] + } else { + &None + } + } + + pub fn get_parent_type(&self) -> &Option> { + // if (this._parentTypeStack.length > 0) { + // return this._parentTypeStack[this._parentTypeStack.length - 1]; + // } + let parent_type_stack_length = self.parent_type_stack.len(); + + if parent_type_stack_length > 0 { + &self.parent_type_stack[parent_type_stack_length - 1] + } else { + &None + } + } + + pub fn get_input_type(&self) -> &Option> { + let input_type_stack = self.input_type_stack.len(); + + if input_type_stack > 0 { + &self.input_type_stack[input_type_stack - 1] + } else { + &None + } + } +} + +impl<'ast, T: Text<'ast>> QueryVisitor<'ast, T> for TypeInfo<'ast, T> {} + +#[cfg(test)] +mod type_info_tests { + use std::{borrow::Borrow, convert::TryInto}; + + use k9::assert_equal; + use query::{Text, Type}; + + use crate::{ + common::{CompositeType, InputType}, + parse_query, parse_schema, + query::{ + self, + query_visitor::{walk_document, QueryVisitor}, + }, + schema::{self, ObjectType}, + validation::TypeInfo, + }; + + const TEST_SCHEMA: &str = r#" + interface Pet { + name: String + } + + type Dog implements Pet { + name: String + } + + type Cat implements Pet { + name: String + } + + type Human { + name: String + pets: [Pet] + } + + type Alien { + name(surname: Boolean): String + } + + type QueryRoot { + human(id: ID): Human + alien: Alien + } + + schema { + query: QueryRoot + } + "#; + + #[test] + pub fn visit_document_maintains_type_info() { + let schema_ast: schema::Document = parse_schema(TEST_SCHEMA).unwrap(); + let mut type_info = TypeInfo::new(schema_ast); + + let query_ast: query::Document = parse_query( + r#"{ + human(id: 4) { + name, + pets { + ... { name } + }, + unknown + } + }"#, + ) + .unwrap(); + + #[derive(Debug, PartialEq)] + struct TestVisitor<'ast, T: Text<'ast>> { + parent_types: Vec<&'ast Option>>, + current_types: Vec<&'ast Option>>, + input_types: Vec<&'ast Option>>, + } + + impl<'ast, T: Text<'ast>> TestVisitor<'ast, T> { + pub fn new() -> Self { + Self { + parent_types: Vec::new(), + current_types: Vec::new(), + input_types: Vec::new(), + } + } + } + + impl<'ast, T: Text<'ast>> QueryVisitor<'ast, T> for TestVisitor<'ast, T> { + fn visit_document( + &mut self, + node: &'ast query::Document<'ast, T>, + type_info: &mut Option>, + ) { + if let Some(info) = type_info { + self.parent_types.push(info.get_parent_type()); + self.current_types.push(info.get_type()); + self.input_types.push(info.get_input_type()); + } + } + } + + let mut visitor = TestVisitor::new(); + + walk_document(&mut visitor, &query_ast, &mut Some(type_info)); + + let expected = TestVisitor { + parent_types: vec![&Some(CompositeType::Object(ObjectType::new( + "Document".to_string(), + )))], + current_types: Vec::new(), + input_types: Vec::new(), + }; + + assert_equal!(visitor, expected); + } +}