Skip to content

Commit 29f088e

Browse files
authored
Merge pull request #8 from tailhook/schema
Schema language
2 parents c6e5211 + 0a330ce commit 29f088e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2027
-273
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ GraphQL Parser
55
[Github](https://github.com/tailhook/graphql-parser) |
66
[Crate](https://crates.io/crates/graphql-parser)
77

8-
A parser, formatter and AST for graphql query language for rust.
8+
A parser, formatter and AST for graphql query and schema definition language
9+
for rust.
910

10-
Current this library supports full graphql syntax, and the following
11-
extensions:
11+
Supported extensions:
1212

1313
1. Subscriptions
1414
2. Block (triple quoted) strings
1515

16-
Schema definition language (also often called IDL) is on the to do list.
1716

1817
License
1918
=======

src/common.rs

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
use std::collections::BTreeMap;
2+
3+
use combine::{parser, ParseResult, Parser};
4+
use combine::easy::Error;
5+
use combine::error::StreamError;
6+
use combine::combinator::{many, many1, optional, position, choice};
7+
8+
use tokenizer::{Kind as T, Token, TokenStream};
9+
use helpers::{punct, ident, kind, name};
10+
use position::Pos;
11+
12+
13+
/// An alias for string, used where graphql expects a name
14+
pub type Name = String;
15+
16+
#[derive(Debug, Clone, PartialEq)]
17+
pub struct Directive {
18+
pub position: Pos,
19+
pub name: Name,
20+
pub arguments: Vec<(Name, Value)>,
21+
}
22+
23+
/// This represents integer number
24+
///
25+
/// But since there is no definition on limit of number in spec
26+
/// (only in implemetation), we do a trick similar to the one
27+
/// in `serde_json`: encapsulate value in new-type, allowing type
28+
/// to be extended later.
29+
#[derive(Debug, Clone, PartialEq)]
30+
// we use i64 as a reference implementation: graphql-js thinks even 32bit
31+
// integers is enough. We might consider lift this limit later though
32+
pub struct Number(pub(crate) i64);
33+
34+
#[derive(Debug, Clone, PartialEq)]
35+
pub enum Value {
36+
Variable(Name),
37+
Int(Number),
38+
Float(f64),
39+
String(String),
40+
Boolean(bool),
41+
Null,
42+
Enum(Name),
43+
List(Vec<Value>),
44+
Object(BTreeMap<Name, Value>),
45+
}
46+
47+
#[derive(Debug, Clone, PartialEq)]
48+
pub enum Type {
49+
NamedType(Name),
50+
ListType(Box<Type>),
51+
NonNullType(Box<Type>),
52+
}
53+
54+
impl Number {
55+
/// Returns a number as i64 if it fits the type
56+
pub fn as_i64(&self) -> Option<i64> {
57+
Some(self.0)
58+
}
59+
}
60+
61+
62+
pub fn directives<'a>(input: &mut TokenStream<'a>)
63+
-> ParseResult<Vec<Directive>, TokenStream<'a>>
64+
{
65+
many(position()
66+
.skip(punct("@"))
67+
.and(name())
68+
.and(parser(arguments))
69+
.map(|((position, name), arguments)| {
70+
Directive { position, name, arguments }
71+
}))
72+
.parse_stream(input)
73+
}
74+
75+
pub fn arguments<'a>(input: &mut TokenStream<'a>)
76+
-> ParseResult<Vec<(String, Value)>, TokenStream<'a>>
77+
{
78+
optional(
79+
punct("(")
80+
.with(many1(name()
81+
.skip(punct(":"))
82+
.and(parser(value))))
83+
.skip(punct(")")))
84+
.map(|opt| {
85+
opt.unwrap_or_else(Vec::new)
86+
})
87+
.parse_stream(input)
88+
}
89+
90+
pub fn int_value<'a>(input: &mut TokenStream<'a>)
91+
-> ParseResult<Value, TokenStream<'a>>
92+
{
93+
kind(T::IntValue).and_then(|tok| tok.value.parse())
94+
.map(Number).map(Value::Int)
95+
.parse_stream(input)
96+
}
97+
98+
pub fn float_value<'a>(input: &mut TokenStream<'a>)
99+
-> ParseResult<Value, TokenStream<'a>>
100+
{
101+
kind(T::FloatValue).and_then(|tok| tok.value.parse())
102+
.map(Value::Float)
103+
.parse_stream(input)
104+
}
105+
106+
fn unquote_block_string(src: &str) -> Result<String, Error<Token, Token>> {
107+
debug_assert!(src.starts_with("\"\"\"") && src.ends_with("\"\"\""));
108+
let indent = src[3..src.len()-3].lines().skip(1)
109+
.filter_map(|line| {
110+
let trimmed = line.trim_left().len();
111+
if trimmed > 0 {
112+
Some(line.len() - trimmed)
113+
} else {
114+
None // skip whitespace-only lines
115+
}
116+
})
117+
.min().unwrap_or(0);
118+
let mut result = String::with_capacity(src.len()-6);
119+
let mut lines = src[3..src.len()-3].lines();
120+
if let Some(first) = lines.next() {
121+
let stripped = first.trim();
122+
if !stripped.is_empty() {
123+
result.push_str(stripped);
124+
result.push('\n');
125+
}
126+
}
127+
let mut last_line = 0;
128+
for line in lines {
129+
last_line = result.len();
130+
if line.len() > indent {
131+
result.push_str(&line[indent..].replace(r#"\""""#, r#"""""#));
132+
}
133+
result.push('\n');
134+
}
135+
if result[last_line..].trim().is_empty() {
136+
result.truncate(last_line);
137+
}
138+
139+
Ok(result)
140+
}
141+
142+
fn unquote_string(s: &str) -> Result<String, Error<Token, Token>> {
143+
let mut res = String::with_capacity(s.len());
144+
debug_assert!(s.starts_with('"') && s.ends_with('"'));
145+
let mut chars = s[1..s.len()-1].chars();
146+
while let Some(c) = chars.next() {
147+
match c {
148+
'\\' => {
149+
match chars.next().expect("slash cant be and the end") {
150+
c@'"' | c@'\\' | c@'/' => res.push(c),
151+
'b' => res.push('\u{0010}'),
152+
'f' => res.push('\u{000C}'),
153+
'n' => res.push('\n'),
154+
'r' => res.push('\r'),
155+
't' => res.push('\t'),
156+
'u' => {
157+
unimplemented!();
158+
}
159+
c => {
160+
return Err(Error::unexpected_message(
161+
format_args!("bad escaped char {:?}", c)));
162+
}
163+
}
164+
}
165+
c => res.push(c),
166+
}
167+
}
168+
169+
Ok(res)
170+
}
171+
172+
pub fn string<'a>(input: &mut TokenStream<'a>)
173+
-> ParseResult<String, TokenStream<'a>>
174+
{
175+
choice((
176+
kind(T::StringValue).and_then(|tok| unquote_string(tok.value)),
177+
kind(T::BlockString).and_then(|tok| unquote_block_string(tok.value)),
178+
)).parse_stream(input)
179+
}
180+
181+
pub fn string_value<'a>(input: &mut TokenStream<'a>)
182+
-> ParseResult<Value, TokenStream<'a>>
183+
{
184+
kind(T::StringValue).and_then(|tok| unquote_string(tok.value))
185+
.map(Value::String)
186+
.parse_stream(input)
187+
}
188+
189+
pub fn block_string_value<'a>(input: &mut TokenStream<'a>)
190+
-> ParseResult<Value, TokenStream<'a>>
191+
{
192+
kind(T::BlockString).and_then(|tok| unquote_block_string(tok.value))
193+
.map(Value::String)
194+
.parse_stream(input)
195+
}
196+
197+
pub fn plain_value<'a>(input: &mut TokenStream<'a>)
198+
-> ParseResult<Value, TokenStream<'a>>
199+
{
200+
ident("true").map(|_| Value::Boolean(true))
201+
.or(ident("false").map(|_| Value::Boolean(false)))
202+
.or(ident("null").map(|_| Value::Null))
203+
.or(name().map(Value::Enum))
204+
.or(parser(int_value))
205+
.or(parser(float_value))
206+
.or(parser(string_value))
207+
.or(parser(block_string_value))
208+
.parse_stream(input)
209+
}
210+
211+
pub fn value<'a>(input: &mut TokenStream<'a>)
212+
-> ParseResult<Value, TokenStream<'a>>
213+
{
214+
parser(plain_value)
215+
.or(punct("$").with(name()).map(Value::Variable))
216+
.or(punct("[").with(many(parser(value))).skip(punct("]"))
217+
.map(Value::List))
218+
.or(punct("{")
219+
.with(many(name().skip(punct(":")).and(parser(value))))
220+
.skip(punct("}"))
221+
.map(Value::Object))
222+
.parse_stream(input)
223+
}
224+
225+
pub fn default_value<'a>(input: &mut TokenStream<'a>)
226+
-> ParseResult<Value, TokenStream<'a>>
227+
{
228+
parser(plain_value)
229+
.or(punct("[").with(many(parser(default_value))).skip(punct("]"))
230+
.map(Value::List))
231+
.or(punct("{")
232+
.with(many(name().skip(punct(":")).and(parser(default_value))))
233+
.skip(punct("}"))
234+
.map(Value::Object))
235+
.parse_stream(input)
236+
}
237+
238+
pub fn parse_type<'a>(input: &mut TokenStream<'a>)
239+
-> ParseResult<Type, TokenStream<'a>>
240+
{
241+
name().map(Type::NamedType)
242+
.or(punct("[")
243+
.with(parser(parse_type))
244+
.skip(punct("]"))
245+
.map(Box::new)
246+
.map(Type::ListType))
247+
.and(optional(punct("!")).map(|v| v.is_some()))
248+
.map(|(typ, strict)|
249+
if strict {
250+
Type::NonNullType(Box::new(typ))
251+
} else {
252+
typ
253+
}
254+
)
255+
.parse_stream(input)
256+
}

src/format.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Formatting graphql
22
use std::default::Default;
33

4+
use common::Directive;
5+
46

57
#[derive(Debug, PartialEq)]
68
pub(crate) struct Formatter<'a> {
@@ -127,3 +129,10 @@ impl<'a> Formatter<'a> {
127129
}
128130
}
129131
}
132+
133+
pub(crate) fn format_directives(dirs: &[Directive], f: &mut Formatter) {
134+
for dir in dirs {
135+
f.write(" ");
136+
dir.display(f);
137+
}
138+
}

src/lib.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//!
1414
//! 1. Subscriptions
1515
//! 2. Block (triple quoted) strings
16+
//! 3. Schema definition language a/k/a IDL (which is still in RFC)
1617
//!
1718
//!
1819
//! Example: Parse and Format Query
@@ -39,18 +40,72 @@
3940
//! # }
4041
//! ```
4142
//!
43+
//! Example: Parse and Format Schema
44+
//! --------------------------------
45+
//!
46+
//! ```rust
47+
//! # extern crate failure;
48+
//! # extern crate graphql_parser;
49+
//! use graphql_parser::parse_schema;
50+
//!
51+
//! # fn parse() -> Result<(), failure::Error> {
52+
//! let ast = parse_schema(r#"
53+
//! schema {
54+
//! query: Query
55+
//! }
56+
//! type Query {
57+
//! users: [User!]!,
58+
//! }
59+
//! """
60+
//! Example user object
61+
//!
62+
//! This is just a demo comment.
63+
//! """
64+
//! type User {
65+
//! name: String!,
66+
//! }
67+
//! "#)?;
68+
//! // Format canonical representation
69+
//! assert_eq!(format!("{}", ast), "\
70+
//! schema {
71+
//! query: Query
72+
//! }
73+
//!
74+
//! type Query {
75+
//! users: [User!]!
76+
//! }
77+
//!
78+
//! \"\"\"
79+
//! Example user object
80+
//!
81+
//! This is just a demo comment.
82+
//! \"\"\"
83+
//! type User {
84+
//! name: String!
85+
//! }
86+
//! ");
87+
//! # Ok(())
88+
//! # }
89+
//! # fn main() {
90+
//! # parse().unwrap()
91+
//! # }
92+
//! ```
93+
//!
4294
#![warn(missing_debug_implementations)]
4395

4496
extern crate combine;
4597
#[macro_use] extern crate failure;
4698
#[cfg(test)] #[macro_use] extern crate pretty_assertions;
4799

48100

101+
mod common;
49102
mod format;
50103
mod position;
51104
mod tokenizer;
52105
mod helpers;
53-
mod query;
106+
pub mod query;
107+
pub mod schema;
54108

55109
pub use query::{parse_query, QueryParseError};
110+
pub use schema::{parse_schema, SchemaParseError};
56111
pub use position::Pos;

0 commit comments

Comments
 (0)