Skip to content

Query visitor with schema type info #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -20,3 +20,4 @@ thiserror = "1.0.11"

[dev-dependencies]
pretty_assertions = "0.5.0"
k9 = "0.11.0"
18 changes: 18 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -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<Type<'a, T>>),
}

#[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<InputType<'a, T>>),
NonNullType(Box<InputType<'a, T>>),
}


impl Number {
/// Returns a number as i64 if it fits the type
pub fn as_i64(&self) -> Option<i64> {
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -95,13 +95,15 @@


mod common;

#[macro_use]
mod format;
mod position;
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;
1 change: 1 addition & 0 deletions src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ mod ast;
mod error;
mod format;
mod grammar;
pub mod query_visitor;


pub use self::grammar::{parse_query, consume_definition};
402 changes: 402 additions & 0 deletions src/query/query_visitor.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/schema/ast.rs
Original file line number Diff line number Diff line change
@@ -518,4 +518,4 @@ impl FromStr for DirectiveLocation {

Ok(val)
}
}
}
1 change: 1 addition & 0 deletions src/schema/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
218 changes: 218 additions & 0 deletions src/schema/schema_visitor.rs
Original file line number Diff line number Diff line change
@@ -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>) {}
2 changes: 2 additions & 0 deletions src/validation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod type_info;
pub use type_info::TypeInfo;
186 changes: 186 additions & 0 deletions src/validation/type_info.rs
Original file line number Diff line number Diff line change
@@ -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<Option<Type<'ast, T>>>,
parent_type_stack: Vec<Option<CompositeType<'ast, T>>>,
input_type_stack: Vec<Option<InputType<'ast, T>>>,
field_def_stack: Vec<Option<Field<'ast, T>>>,
default_value_stack: Vec<Option<T>>,
directive: Option<Directive<'ast, T>>,
argument: Option<(T::Value, Value<'ast, T>)>,
enum_value: Option<EnumValue<'ast, T>>,
}

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<Type<'ast, T>> {
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<CompositeType<'ast, T>> {
// 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<InputType<'ast, T>> {
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<String> = parse_schema(TEST_SCHEMA).unwrap();
let mut type_info = TypeInfo::new(schema_ast);

let query_ast: query::Document<String> = 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<CompositeType<'ast, T>>>,
current_types: Vec<&'ast Option<Type<'ast, T>>>,
input_types: Vec<&'ast Option<InputType<'ast, T>>>,
}

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<TypeInfo<'ast, T>>,
) {
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);
}
}