├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── graphql-language ├── Cargo.toml └── src │ ├── ast.rs │ ├── lexer │ ├── error.rs │ ├── macros.rs │ ├── mod.rs │ └── tests.rs │ ├── lib.rs │ └── source.rs ├── graphql ├── Cargo.toml └── src │ ├── lib.rs │ └── types.rs └── rustfmt.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | target/ 12 | 13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 14 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - stable 5 | - nightly 6 | os: 7 | - linux 8 | - osx 9 | cache: 10 | directories: 11 | - $HOME/.cargo 12 | script: 13 | - cd graphql && cargo test 14 | - cd graphql-language && cargo test 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 cksac 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-rs 2 | A work-in-progress implementation of GraphQL for Rust 3 | 4 | ### Items 5 | - [ ] Type System (WIP) 6 | - [ ] Lexer (WIP) 7 | - [ ] Parser (WIP) 8 | - [ ] Executor 9 | - [ ] Validator 10 | - [ ] Visitor 11 | - [ ] Printer 12 | - [ ] Examples -------------------------------------------------------------------------------- /graphql-language/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-language" 3 | version = "0.1.0" 4 | description = "Parses and performs various operations on the GraphQL language." 5 | keywords = ["graphql", "language", "parser"] 6 | authors = ["cscks "] 7 | license = "MIT" 8 | readme = "../README.md" 9 | repository = "https://github.com/cksac/graphql-rs" 10 | homepage = "https://github.com/cksac/graphql-rs" 11 | 12 | [lib] 13 | name = "graphql_language" 14 | path = "src/lib.rs" 15 | -------------------------------------------------------------------------------- /graphql-language/src/ast.rs: -------------------------------------------------------------------------------- 1 | //! AST node definitions which will be parsed into. Based off of the 2 | //! `graphql-js` [`ast.js`][1] definitions. 3 | //! 4 | //! [1]: https://github.com/graphql/graphql-js/blob/dfe676c3011efe9560b9fa0fcbd2b7bd87476d02/src/language/ast.js 5 | 6 | use source::Source; 7 | 8 | /// Contains some character offsets that identify where the source of the AST 9 | /// is from. 10 | pub struct Location<'a> { 11 | pub start: usize, 12 | pub end: usize, 13 | pub source: Option<&'a Source<'a>> 14 | } 15 | 16 | /// All AST node types implement this trait. 17 | pub trait Node<'a> { 18 | fn location(&self) -> Option<&Location<'a>>; 19 | } 20 | 21 | macro_rules! impl_node_for { 22 | ($data:ident) => { 23 | impl<'a> Node<'a> for $data<'a> { 24 | fn location(&self) -> Option<&Location<'a>> { 25 | self.loc.as_ref() 26 | } 27 | } 28 | } 29 | } 30 | 31 | /// Name :: /[_A-Za-z][_0-9A-Za-z]*/ 32 | pub struct Name<'a> { 33 | pub loc: Option>, 34 | pub value: &'a str 35 | } 36 | 37 | impl_node_for! { Name } 38 | 39 | /// IntValue :: IntegerPart 40 | pub struct IntValue<'a> { 41 | pub loc: Option>, 42 | pub value: &'a str 43 | } 44 | 45 | impl_node_for! { IntValue } 46 | 47 | /// FloatValue :: 48 | /// - IntegerPart FractionalPart 49 | /// - IntegerPart ExponentPart 50 | /// - IntegerPart FractionalPart ExponentPart 51 | pub struct FloatValue<'a> { 52 | pub loc: Option>, 53 | pub value: &'a str 54 | } 55 | 56 | impl_node_for! { FloatValue } 57 | 58 | /// StringValue :: 59 | /// - `""` 60 | /// - `"` StringCharacter+ `"` 61 | pub struct StringValue<'a> { 62 | pub loc: Option>, 63 | pub value: String 64 | } 65 | 66 | impl_node_for! { StringValue } 67 | 68 | /// Document : Definition+ 69 | pub struct Document<'a> { 70 | pub loc: Option>, 71 | pub definitions: Vec> 72 | } 73 | 74 | impl_node_for! { Document } 75 | 76 | /// Definition : 77 | /// - OperationDefinition 78 | /// - FragmentDefinition 79 | pub enum Definition<'a> { 80 | Operation(OperationDefinition<'a>), 81 | Fragment(FragmentDefinition<'a>) 82 | } 83 | 84 | /// OperationDefinition : 85 | /// - SelectionSet 86 | /// - OperationType Name? VariableDefinitions? Directives? SelectionSet 87 | pub struct OperationDefinition<'a> { 88 | pub loc: Option>, 89 | pub operation: OperationType, 90 | pub name: Option>, 91 | pub variable_definitions: Option>, 92 | pub directives: Option>, 93 | pub selection_set: SelectionSet<'a> 94 | } 95 | 96 | impl_node_for! { OperationDefinition } 97 | 98 | /// OperationType : one of query mutation 99 | pub enum OperationType { 100 | Query, 101 | Mutation 102 | } 103 | 104 | /// SelectionSet : { Selection+ } 105 | pub struct SelectionSet<'a> { 106 | pub loc: Option>, 107 | pub selections: Vec> 108 | } 109 | 110 | impl_node_for! { SelectionSet } 111 | 112 | /// Selection : 113 | /// - Field 114 | /// - FragmentSpread 115 | /// - InlineFragment 116 | pub enum Selection<'a> { 117 | Field(Field<'a>), 118 | FragmentSpread(FragmentSpread<'a>), 119 | InlineFragment(InlineFragment<'a>) 120 | } 121 | 122 | /// Field : Alias? Name Arguments? Directives? SelectionSet? 123 | pub struct Field<'a> { 124 | pub loc: Option>, 125 | pub alias: Option>, 126 | pub name: Name<'a>, 127 | pub arguments: Option>, 128 | pub directives: Option>, 129 | pub selection_set: Option> 130 | } 131 | 132 | impl_node_for! { Field } 133 | 134 | /// Alias : Name : 135 | pub type Alias<'a> = Name<'a>; 136 | 137 | /// Arguments : ( Argument+ ) 138 | pub type Arguments<'a> = Vec>; 139 | 140 | /// Argument : Name : Value 141 | pub struct Argument<'a> { 142 | pub loc: Option>, 143 | pub name: Name<'a>, 144 | pub value: Value<'a> 145 | } 146 | 147 | impl_node_for! { Argument } 148 | 149 | /// FragmentSpread : ... FragmentName Directives? 150 | pub struct FragmentSpread<'a> { 151 | pub loc: Option>, 152 | pub name: Name<'a>, 153 | pub directives: Option> 154 | } 155 | 156 | impl_node_for! { FragmentSpread } 157 | 158 | /// InlineFragment : ... TypeCondition? Directives? SelectionSet 159 | pub struct InlineFragment<'a> { 160 | pub loc: Option>, 161 | pub type_condition: Option>, 162 | pub directives: Option>, 163 | pub selection_set: SelectionSet<'a> 164 | } 165 | 166 | impl_node_for! { InlineFragment } 167 | 168 | /// FragmentDefinition : fragment FragmentName TypeCondition Directives? SelectionSet 169 | pub struct FragmentDefinition<'a> { 170 | pub loc: Option>, 171 | pub name: Name<'a>, 172 | pub type_condition: TypeCondition<'a>, 173 | pub directives: Option>, 174 | pub selection_set: SelectionSet<'a> 175 | } 176 | 177 | impl_node_for! { FragmentDefinition } 178 | 179 | /// FragmentName : Name but not `on` 180 | pub type FragmentName<'a> = Name<'a>; 181 | 182 | /// TypeCondition : on NamedType 183 | pub type TypeCondition<'a> = NamedType<'a>; 184 | 185 | /// Value[Const] : 186 | /// - [~Const] Variable 187 | /// - IntValue 188 | /// - FloatValue 189 | /// - StringValue 190 | /// - BooleanValue 191 | /// - EnumValue 192 | /// - ListValue[?Const] 193 | /// - ObjectValue[?Const] 194 | pub enum Value<'a> { 195 | Variable(Variable<'a>), 196 | Int(IntValue<'a>), 197 | Float(FloatValue<'a>), 198 | String(StringValue<'a>), 199 | Boolean(BooleanValue<'a>), 200 | Enum(EnumValue<'a>), 201 | List(ListValue<'a>), 202 | Object(ObjectValue<'a>) 203 | } 204 | 205 | /// BooleanValue : one of `true` `false` 206 | pub struct BooleanValue<'a> { 207 | pub loc: Option>, 208 | pub value: bool 209 | } 210 | 211 | impl_node_for! { BooleanValue } 212 | 213 | /// EnumValue : Name but not `true`, `false` or `null` 214 | pub struct EnumValue<'a> { 215 | pub loc: Option>, 216 | pub name: Name<'a> 217 | } 218 | 219 | impl_node_for! { EnumValue } 220 | 221 | /// ListValue[Const] : 222 | /// - [ ] 223 | /// - [ Value[?Const]+ ] 224 | pub struct ListValue<'a> { 225 | pub loc: Option>, 226 | pub values: Vec> 227 | } 228 | 229 | impl_node_for! { ListValue } 230 | 231 | /// ObjectValue[Const] : 232 | /// - { } 233 | /// - { ObjectField[?Const]+ } 234 | pub struct ObjectValue<'a> { 235 | pub loc: Option>, 236 | pub fields: Vec> 237 | } 238 | 239 | impl_node_for! { ObjectValue } 240 | 241 | /// ObjectField[Const] : Name : Value[?Const] 242 | pub struct ObjectField<'a> { 243 | pub loc: Option>, 244 | pub name: Name<'a>, 245 | pub value: Value<'a> 246 | } 247 | 248 | impl_node_for! { ObjectField } 249 | 250 | /// VariableDefinitions : ( VariableDefinition+ ) 251 | pub type VariableDefinitions<'a> = Vec>; 252 | 253 | /// VariableDefinition : Variable : Type DefaultValue? 254 | pub struct VariableDefinition<'a> { 255 | pub loc: Option>, 256 | pub variable: Variable<'a>, 257 | pub type_: Type<'a>, 258 | pub default_value: Option> 259 | } 260 | 261 | impl_node_for! { VariableDefinition } 262 | 263 | /// Variable : $ Name 264 | pub struct Variable<'a> { 265 | pub loc: Option>, 266 | pub name: Name<'a> 267 | } 268 | 269 | impl_node_for! { Variable } 270 | 271 | /// DefaultValue : = Value[Const] 272 | pub type DefaultValue<'a> = Value<'a>; 273 | 274 | /// Type : 275 | /// - NamedType 276 | /// - ListType 277 | /// - NonNullType 278 | pub enum Type<'a> { 279 | Named(NamedType<'a>), 280 | List(Box>), 281 | NonNullNamed(Box>), 282 | NonNullList(Box>) 283 | } 284 | 285 | /// NamedType : Name 286 | pub struct NamedType<'a> { 287 | pub loc: Option>, 288 | pub name: Name<'a> 289 | } 290 | 291 | impl_node_for! { NamedType } 292 | 293 | /// ListType : [ Type ] 294 | pub struct ListType<'a> { 295 | pub loc: Option>, 296 | pub type_: Type<'a> 297 | } 298 | 299 | impl_node_for! { ListType } 300 | 301 | /// NonNullType : 302 | /// - NamedType ! 303 | /// - ListType ! 304 | /// 305 | /// Are implementation deviates from the spec here. This is because 306 | /// `NonNullType` is expected to be a [node][1], but it is also expected to be 307 | /// a [union][2]. The best way to express this in Rust is with two types. 308 | /// 309 | /// [1]: https://github.com/graphql/graphql-js/blob/dfe676c3011efe9560b9fa0fcbd2b7bd87476d02/src/language/ast.js#L49 310 | /// [2]: https://github.com/graphql/graphql-js/blob/dfe676c3011efe9560b9fa0fcbd2b7bd87476d02/src/language/ast.js#L254 311 | pub struct NonNullNamedType<'a> { 312 | pub loc: Option>, 313 | pub type_: NamedType<'a> 314 | } 315 | 316 | impl_node_for! { NonNullNamedType } 317 | 318 | /// See documentation for the `NonNullNamedType` struct. 319 | pub struct NonNullListType<'a> { 320 | pub loc: Option>, 321 | pub type_: ListType<'a> 322 | } 323 | 324 | impl_node_for! { NonNullListType } 325 | 326 | /// Directives : Directive+ 327 | pub type Directives<'a> = Vec>; 328 | 329 | /// Directive : @ Name Arguments? 330 | pub struct Directive<'a> { 331 | pub loc: Option>, 332 | pub name: Name<'a>, 333 | pub arguments: Option> 334 | } 335 | 336 | impl_node_for! { Directive } 337 | -------------------------------------------------------------------------------- /graphql-language/src/lexer/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | 4 | #[derive(PartialEq, Debug)] 5 | pub enum Error { 6 | UnxepectedChar, 7 | InvalidInt, 8 | InvalidFloat, 9 | UnterminatedString, 10 | BadEscape, 11 | BadUnicodeEscape, 12 | InvalidUtfChar, 13 | } 14 | 15 | impl fmt::Display for Error { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | match *self { 18 | Error::UnxepectedChar => write!(f, "Unexpected character"), 19 | Error::InvalidInt => write!(f, "Invalid integer number"), 20 | Error::InvalidFloat => write!(f, "Invalid float number"), 21 | Error::UnterminatedString => write!(f, "Unterminated string"), 22 | Error::BadEscape => write!(f, "Bad character escape sequence"), 23 | Error::BadUnicodeEscape => write!(f, "Bad unicode escape sequence"), 24 | Error::InvalidUtfChar => write!(f, "Invalid UTF-8 character"), 25 | } 26 | } 27 | } 28 | 29 | impl error::Error for Error { 30 | fn description(&self) -> &str { 31 | match *self { 32 | Error::UnxepectedChar => "Unexpected character", 33 | Error::InvalidInt => "Invalid integer number", 34 | Error::InvalidFloat => "Invalid float number", 35 | Error::UnterminatedString => "Unterminated string", 36 | Error::BadEscape => "Bad character escape sequence", 37 | Error::BadUnicodeEscape => "Bad unicode escape sequence", 38 | Error::InvalidUtfChar => "Invalid UTF-8 character", 39 | } 40 | } 41 | 42 | fn cause(&self) -> Option<&error::Error> { 43 | None 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /graphql-language/src/lexer/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! punctuator { 3 | ($lexer: ident, $p: ident) => ({ 4 | $lexer.iter.next(); 5 | Ok(Token::Punctuator(Punctuator::$p, $lexer.lo, $lexer.lo + 1)) 6 | }) 7 | } 8 | 9 | // Advance iter and hi position if pattern match 10 | #[macro_export] 11 | macro_rules! take { 12 | ($lexer: ident, $($p: pat)|*) => ( 13 | match $lexer.iter.peek() { 14 | $( 15 | Some(&(i, $p)) => { 16 | $lexer.iter.next(); 17 | $lexer.hi = i + 1; 18 | true 19 | } 20 | )* 21 | _ => false 22 | }); 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! take_while { 27 | ($lexer: ident, $($p: pat)|*) => ({ 28 | let mut is_taken = false; 29 | while let Some(&(p, c)) = $lexer.iter.peek() { 30 | match c { 31 | $( 32 | $p => { 33 | $lexer.iter.next(); 34 | $lexer.hi = p + 1; 35 | is_taken = true; 36 | } 37 | )* 38 | _ => { 39 | break; 40 | } 41 | } 42 | } 43 | is_taken 44 | }); 45 | } 46 | 47 | #[macro_export] 48 | macro_rules! take_while_not { 49 | ($lexer: ident, $($p: pat)|*) => ({ 50 | let mut is_taken = false; 51 | while let Some(&(p, c)) = $lexer.iter.peek() { 52 | match c { 53 | $( 54 | $p => { 55 | $lexer.hi = p; 56 | break; 57 | } 58 | )* 59 | _ => { 60 | $lexer.iter.next(); 61 | is_taken = true; 62 | } 63 | } 64 | } 65 | is_taken 66 | }); 67 | } 68 | 69 | #[macro_export] 70 | macro_rules! peek { 71 | ($lexer: ident, $($p: pat)|*) => ( 72 | match $lexer.iter.peek() { 73 | $( 74 | Some(&(_, $p)) => true, 75 | )* 76 | _ => false 77 | }); 78 | } 79 | 80 | // Advance iter if pattern match, keep hi position unchange 81 | #[macro_export] 82 | macro_rules! follow_by { 83 | ($lexer: ident, $($p: pat)|*) => ( 84 | match $lexer.iter.peek() { 85 | $( 86 | Some(&(_, $p)) => { 87 | $lexer.iter.next(); 88 | true 89 | } 90 | )* 91 | _ => false 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /graphql-language/src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::str::CharIndices; 2 | use std::iter::Peekable; 3 | use std::result; 4 | 5 | #[cfg(test)] 6 | mod tests; 7 | 8 | #[macro_use] 9 | mod macros; 10 | 11 | mod error; 12 | pub use self::error::Error; 13 | pub type Result = result::Result; 14 | 15 | #[derive(PartialEq, Debug)] 16 | pub enum Token<'a> { 17 | Eof, 18 | Punctuator(Punctuator, usize, usize), 19 | Name(&'a str, usize, usize), 20 | IntValue(&'a str, usize, usize), 21 | FloatValue(&'a str, usize, usize), 22 | StringValue(String, usize, usize), 23 | } 24 | 25 | #[derive(PartialEq, Debug)] 26 | pub enum Punctuator { 27 | Bang, 28 | Dollar, 29 | LeftParen, 30 | RightParen, 31 | Spread, 32 | Colon, 33 | Equals, 34 | At, 35 | LeftBracket, 36 | RightBracket, 37 | LeftBrace, 38 | RightBrace, 39 | Pipe, 40 | } 41 | 42 | pub struct Lexer<'a> { 43 | input: &'a str, 44 | iter: Peekable>, 45 | eof_emmited: bool, 46 | lo: usize, 47 | hi: usize, 48 | } 49 | 50 | impl<'a> Lexer<'a> { 51 | pub fn new(input: &'a str) -> Lexer<'a> { 52 | Lexer { 53 | input: input, 54 | iter: input.char_indices().peekable(), 55 | eof_emmited: false, 56 | lo: 0, 57 | hi: 0, 58 | } 59 | } 60 | } 61 | 62 | impl<'a> Iterator for Lexer<'a> { 63 | type Item = Result>; 64 | fn next(&mut self) -> Option>> { 65 | if self.eof_emmited { 66 | None 67 | } else if self.iter.peek().is_none() { 68 | self.eof_emmited = true; 69 | Some(Ok(Token::Eof)) 70 | } else { 71 | skip_ignored_token(self); 72 | let &(p, c) = self.iter.peek().unwrap(); 73 | self.lo = p; 74 | Some(match c { 75 | '!' => punctuator!(self, Bang), 76 | '$' => punctuator!(self, Dollar), 77 | '(' => punctuator!(self, LeftParen), 78 | ')' => punctuator!(self, RightParen), 79 | ':' => punctuator!(self, Colon), 80 | '=' => punctuator!(self, Equals), 81 | '@' => punctuator!(self, At), 82 | '[' => punctuator!(self, LeftBracket), 83 | ']' => punctuator!(self, RightBracket), 84 | '{' => punctuator!(self, LeftBrace), 85 | '}' => punctuator!(self, RightBrace), 86 | '|' => punctuator!(self, Pipe), 87 | '.' => scan_spread(self), 88 | '_' | 'a'...'z' | 'A'...'Z' => scan_name(self), 89 | '-' | '0'...'9' => scan_number(self), 90 | '"' => scan_string(self), 91 | _ => Err(Error::UnxepectedChar), 92 | }) 93 | } 94 | } 95 | } 96 | 97 | fn skip_ignored_token(lexer: &mut Lexer) { 98 | loop { 99 | if take!(lexer, '#') { 100 | take_while_not!(lexer, '\r' | '\n'); 101 | } 102 | if !take_while!(lexer, '\u{feff}' | ' ' | '\t' | '\r' | '\n' | ',') { 103 | break; 104 | } 105 | } 106 | } 107 | 108 | fn scan_spread<'a>(lexer: &mut Lexer<'a>) -> Result> { 109 | if take!(lexer, '.') && take!(lexer, '.') && take!(lexer, '.') { 110 | Ok(Token::Punctuator(Punctuator::Spread, lexer.lo, lexer.hi)) 111 | } else { 112 | lexer.iter.next(); 113 | Err(Error::UnxepectedChar) 114 | } 115 | } 116 | 117 | fn scan_name<'a>(lexer: &mut Lexer<'a>) -> Result> { 118 | take_while!(lexer, '_' | 'a'...'z' | 'A'...'Z' | '0'...'9'); 119 | Ok(Token::Name(&lexer.input[lexer.lo..lexer.hi], lexer.lo, lexer.hi)) 120 | } 121 | 122 | fn scan_number<'a>(lexer: &mut Lexer<'a>) -> Result> { 123 | take!(lexer, '-'); 124 | // Integer part 125 | if take!(lexer, '1'...'9') { 126 | take_while!(lexer, '0'...'9'); 127 | if follow_by!(lexer, ' ' | '\t' | '\r' | '\n' | ',') || lexer.iter.peek().is_none() { 128 | return Ok(Token::IntValue(&lexer.input[lexer.lo..lexer.hi], lexer.lo, lexer.hi)); 129 | } 130 | } else if take!(lexer, '0') { 131 | if follow_by!(lexer, ' ' | '\t' | '\r' | '\n' | ',') || lexer.iter.peek().is_none() { 132 | return Ok(Token::IntValue(&lexer.input[lexer.lo..lexer.hi], lexer.lo, lexer.hi)); 133 | } 134 | } 135 | if !peek!(lexer, '.' | 'E' | 'e') { 136 | lexer.iter.next(); 137 | return Err(Error::InvalidInt); 138 | } 139 | // Fractional part 140 | if take!(lexer, '.') { 141 | if !take_while!(lexer, '0'...'9') { 142 | lexer.iter.next(); 143 | return Err(Error::InvalidFloat); 144 | } 145 | } 146 | // Exponent part 147 | if take!(lexer, 'E' | 'e') { 148 | take!(lexer, '+' | '-'); 149 | if !take_while!(lexer, '0'...'9') { 150 | lexer.iter.next(); 151 | return Err(Error::InvalidFloat); 152 | } 153 | } 154 | if follow_by!(lexer, ' ' | '\t' | '\r' | '\n' | ',') || lexer.iter.peek().is_none() { 155 | return Ok(Token::FloatValue(&lexer.input[lexer.lo..lexer.hi], lexer.lo, lexer.hi)); 156 | } 157 | lexer.iter.next(); 158 | Err(Error::InvalidFloat) 159 | } 160 | 161 | fn scan_string<'a>(lexer: &mut Lexer<'a>) -> Result> { 162 | if take!(lexer, '"') { 163 | lexer.lo += 1; 164 | } 165 | loop { 166 | if take!(lexer, '\\') { 167 | if !take!(lexer, '\\' | '"' | 'b' | 'f' | 'n' | 'r' | 't' | '/' | 'u') { 168 | lexer.iter.next(); 169 | return Err(Error::BadEscape); 170 | } 171 | } 172 | if !take_while_not!(lexer, '"' | '\\' | '\r' | '\n' | '\x00'...'\x1f') { 173 | if peek!(lexer, '\x00'...'\x1f') { 174 | lexer.iter.next(); 175 | return Err(Error::UnxepectedChar) 176 | } else if !peek!(lexer, '"' | '\\') { 177 | lexer.iter.next(); 178 | return Err(Error::UnterminatedString); 179 | } 180 | } 181 | if peek!(lexer, '\r' | '\n') { 182 | lexer.iter.next(); 183 | return Err(Error::UnterminatedString); 184 | } 185 | if peek!(lexer, '"') { 186 | lexer.iter.next(); 187 | let s = try!(unexcape_str(&lexer.input[lexer.lo..lexer.hi])); 188 | return Ok(Token::StringValue(s, lexer.lo, lexer.hi)); 189 | } 190 | } 191 | } 192 | 193 | fn unexcape_str(s: &str) -> Result { 194 | let mut buf = String::with_capacity(s.len()); 195 | let mut p = s.chars().peekable(); 196 | while let Some(c) = p.next() { 197 | match c { 198 | '\\' => { 199 | match p.next() { 200 | Some('\\') => buf.push('\\'), 201 | Some('/') => buf.push('/'), 202 | Some('"') => buf.push('"'), 203 | Some('n') => buf.push('\n'), 204 | Some('r') => buf.push('\r'), 205 | Some('t') => buf.push('\t'), 206 | Some('b') => buf.push('\x08'), 207 | Some('f') => buf.push('\x0c'), 208 | Some('u') => { 209 | let u1 = try!(p.next().ok_or(Error::BadUnicodeEscape)); 210 | let u2 = try!(p.next().ok_or(Error::BadUnicodeEscape)); 211 | let u3 = try!(p.next().ok_or(Error::BadUnicodeEscape)); 212 | let u4 = try!(p.next().ok_or(Error::BadUnicodeEscape)); 213 | let s = try!(u32::from_str_radix(format!("{}{}{}{}", u1, u2, u3, u4).as_ref(), 16) 214 | .or(Err(Error::BadUnicodeEscape))); 215 | let ch = try!(::std::char::from_u32(s).ok_or(Error::InvalidUtfChar)); 216 | buf.push(ch); 217 | } 218 | _ => return Err(Error::UnxepectedChar), 219 | } 220 | } 221 | _ => buf.push(c), 222 | } 223 | } 224 | Ok(buf) 225 | } 226 | -------------------------------------------------------------------------------- /graphql-language/src/lexer/tests.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::Punctuator::*; 3 | use super::Token::*; 4 | use super::{Lexer, Token, Error}; 5 | 6 | trait Assert { 7 | fn next_is_token(&mut self, expected: Token); 8 | fn next_is_error(&mut self, expected: Error); 9 | } 10 | 11 | impl<'a> Assert for Lexer<'a> { 12 | fn next_is_token(&mut self, expected: Token) { 13 | assert_eq!(self.next(), Some(Ok(expected))); 14 | } 15 | 16 | fn next_is_error(&mut self, expected: Error) { 17 | assert_eq!(self.next(), Some(Err(expected))); 18 | } 19 | } 20 | 21 | fn assert_token(input: &str, expected: Token) { 22 | let mut lexer = Lexer::new(input); 23 | lexer.next_is_token(expected); 24 | } 25 | 26 | fn assert_error(input: &str, expected: Error) { 27 | let mut lexer = Lexer::new(input); 28 | lexer.next_is_error(expected); 29 | } 30 | 31 | #[test] 32 | fn disallows_unsupported_control_chars() { 33 | assert_error("\u{0007}", Error::UnxepectedChar); 34 | } 35 | 36 | #[test] 37 | fn accepts_bom_header() { 38 | assert_token("\u{feff} foo", Name("foo", 4, 7)); 39 | } 40 | 41 | #[test] 42 | fn skips_ignored_chars() { 43 | assert_token(" 44 | 45 | foo 46 | 47 | ", 48 | Name("foo", 6, 9)); 49 | 50 | assert_token(" 51 | #comment 52 | foo#comment 53 | ", 54 | Name("foo", 18, 21)); 55 | 56 | assert_token(",,,foo,,,", Name("foo", 3, 6)); 57 | assert_token("", Eof); 58 | } 59 | 60 | #[test] 61 | fn lexes_names() { 62 | assert_token("simple", Name("simple", 0, 6)); 63 | assert_token("Capital", Name("Capital", 0, 7)); 64 | assert_token("camelName", Name("camelName", 0, 9)); 65 | assert_token("snake_name", Name("snake_name", 0, 10)); 66 | } 67 | 68 | #[test] 69 | fn lexes_bad_names() { 70 | let mut lexer = Lexer::new("a-b"); 71 | lexer.next_is_token(Name("a", 0, 1)); 72 | lexer.next_is_error(Error::InvalidInt); 73 | lexer.next_is_token(Eof); 74 | } 75 | 76 | #[test] 77 | fn lexes_string() { 78 | assert_token("\"simple\"", StringValue("simple".into(), 1, 7)); 79 | assert_token("\" white space \"", 80 | StringValue(" white space ".into(), 1, 14)); 81 | assert_token("\"quote \\\"\"", StringValue(r#"quote ""#.into(), 1, 9)); 82 | assert_token("\"escaped \\n\\r\\b\\t\\f\"", 83 | StringValue("escaped \n\r\x08\t\x0c".into(), 1, 19)); 84 | assert_token("\"slashes \\\\ \\/\"", 85 | StringValue(r#"slashes \ /"#.into(), 1, 14)); 86 | assert_token("\"unicode \\u1234\\u5678\\u90AB\\uCDEF\"", 87 | StringValue("unicode \u{1234}\u{5678}\u{90ab}\u{cdef}".into(), 1, 33)); 88 | assert_token("\"unicode фы世界\"", 89 | StringValue("unicode фы世界".into(), 1, 19)); 90 | assert_token("\"фы世界\"", StringValue("фы世界".into(), 1, 11)); 91 | assert_token("\"contains escaped \\u0007 control char\"", 92 | StringValue("contains escaped \u{0007} control char".into(), 1, 37)); 93 | assert_token("\"escaped null-byte is not \\u0000 end of file\"", 94 | StringValue("escaped null-byte is not \u{0} end of file".into(), 1, 44)); 95 | } 96 | 97 | #[test] 98 | fn lexes_bad_string() { 99 | assert_error("\"", Error::UnterminatedString); 100 | assert_error("\"no end quote", Error::UnterminatedString); 101 | assert_error("\"contains unescaped \u{0007} control char\"", 102 | Error::UnxepectedChar); 103 | assert_error("\"unescaped null-byte is not \u{0000} end of file\"", 104 | Error::UnxepectedChar); 105 | assert_error("\"multi\nline\"", Error::UnterminatedString); 106 | assert_error("\"multi\rline\"", Error::UnterminatedString); 107 | assert_error("\"bad \\u123", Error::UnterminatedString); 108 | assert_error("\"bad \\z esc\"", Error::BadEscape); 109 | assert_error("\"bad \\u1 esc\"", Error::BadUnicodeEscape); 110 | assert_error("\"bad \\u0XX1 esc\"", Error::BadUnicodeEscape); 111 | assert_error("\"bad \\uXXXX esc\"", Error::BadUnicodeEscape); 112 | assert_error("\"bфы世ыы𠱸d \\uXXXF esc\"", Error::BadUnicodeEscape); 113 | } 114 | 115 | #[test] 116 | fn lexes_number() { 117 | assert_token("0", IntValue("0", 0, 1)); 118 | assert_token("9", IntValue("9", 0, 1)); 119 | assert_token("4", IntValue("4", 0, 1)); 120 | assert_token("-4", IntValue("-4", 0, 2)); 121 | assert_token("0.0", FloatValue("0.0", 0, 3)); 122 | assert_token("-0.0", FloatValue("-0.0", 0, 4)); 123 | assert_token("4.123", FloatValue("4.123", 0, 5)); 124 | assert_token("-4.123", FloatValue("-4.123", 0, 6)); 125 | assert_token("0.123", FloatValue("0.123", 0, 5)); 126 | assert_token("0.0123", FloatValue("0.0123", 0, 6)); 127 | assert_token("123e4", FloatValue("123e4", 0, 5)); 128 | assert_token("123E4", FloatValue("123E4", 0, 5)); 129 | assert_token("123E-4", FloatValue("123E-4", 0, 6)); 130 | assert_token("123E+4", FloatValue("123E+4", 0, 6)); 131 | assert_token("-1.123e4", FloatValue("-1.123e4", 0, 8)); 132 | assert_token("-1.123E4", FloatValue("-1.123E4", 0, 8)); 133 | assert_token("-1.123e-4", FloatValue("-1.123e-4", 0, 9)); 134 | assert_token("-1.123E+4", FloatValue("-1.123E+4", 0, 9)); 135 | assert_token("-1.123e4567", FloatValue("-1.123e4567", 0, 11)); 136 | assert_token("1e0", FloatValue("1e0", 0, 3)); 137 | assert_token("0e0", FloatValue("0e0", 0, 3)); 138 | assert_token("1e00", FloatValue("1e00", 0, 4)); 139 | assert_token("1e-0", FloatValue("1e-0", 0, 4)); 140 | assert_token("1e-00", FloatValue("1e-00", 0, 5)); 141 | assert_token("1e+0", FloatValue("1e+0", 0, 4)); 142 | assert_token("1e+00", FloatValue("1e+00", 0, 5)); 143 | } 144 | 145 | #[test] 146 | fn lexes_bad_number() { 147 | assert_error("00", Error::InvalidInt); 148 | assert_error("+1", Error::UnxepectedChar); 149 | assert_error(".123", Error::UnxepectedChar); 150 | assert_error("1.", Error::InvalidFloat); 151 | assert_error("1.A", Error::InvalidFloat); 152 | assert_error("-A", Error::InvalidInt); 153 | assert_error("1.0e", Error::InvalidFloat); 154 | assert_error("1.0e-", Error::InvalidFloat); 155 | assert_error("1.0e+", Error::InvalidFloat); 156 | assert_error("1.0eA", Error::InvalidFloat); 157 | } 158 | 159 | #[test] 160 | fn lexes_punctuation() { 161 | assert_token("!", Punctuator(Bang, 0, 1)); 162 | assert_token("$", Punctuator(Dollar, 0, 1)); 163 | assert_token("(", Punctuator(LeftParen, 0, 1)); 164 | assert_token(")", Punctuator(RightParen, 0, 1)); 165 | assert_token(":", Punctuator(Colon, 0, 1)); 166 | assert_token("=", Punctuator(Equals, 0, 1)); 167 | assert_token("@", Punctuator(At, 0, 1)); 168 | assert_token("[", Punctuator(LeftBracket, 0, 1)); 169 | assert_token("]", Punctuator(RightBracket, 0, 1)); 170 | assert_token("{", Punctuator(LeftBrace, 0, 1)); 171 | assert_token("}", Punctuator(RightBrace, 0, 1)); 172 | assert_token("|", Punctuator(Pipe, 0, 1)); 173 | assert_token("...", Punctuator(Spread, 0, 3)); 174 | } 175 | 176 | #[test] 177 | fn lexes_unexpected_chars() { 178 | assert_error(".", Error::UnxepectedChar); 179 | assert_error("..", Error::UnxepectedChar); 180 | assert_error(".A", Error::UnxepectedChar); 181 | assert_error("..A", Error::UnxepectedChar); 182 | assert_error("?", Error::UnxepectedChar); 183 | assert_error("\u{203B}", Error::UnxepectedChar); 184 | assert_error("\u{203b}", Error::UnxepectedChar); 185 | assert_error("ф", Error::UnxepectedChar); 186 | } 187 | -------------------------------------------------------------------------------- /graphql-language/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod lexer; 3 | pub mod source; -------------------------------------------------------------------------------- /graphql-language/src/source.rs: -------------------------------------------------------------------------------- 1 | static DEFAULT_SOURCE_NAME: &'static str = "GraphQL"; 2 | 3 | #[derive(Debug)] 4 | pub struct Source<'a> { 5 | pub name: &'a str, 6 | pub body: &'a str, 7 | } 8 | 9 | impl<'a> Source<'a> { 10 | pub fn new(body: &'a str) -> Self { 11 | Source { 12 | name: DEFAULT_SOURCE_NAME, 13 | body: body, 14 | } 15 | } 16 | 17 | pub fn name(mut self, name: &'a str) -> Self { 18 | self.name = name; 19 | self 20 | } 21 | } 22 | 23 | use std::io::Error; 24 | use std::path::Path; 25 | 26 | pub fn from_file<'a>(path: &'a Path, buf: &'a mut String) -> Result, Error> { 27 | use std::fs::File; 28 | use std::io::Read; 29 | use std::ffi::OsStr; 30 | 31 | let mut f = try!(File::open(path.clone())); 32 | try!(f.read_to_string(buf)); 33 | Ok(Source::new(buf.as_str()).name(path 34 | .file_name() 35 | .and_then(OsStr::to_str) 36 | .unwrap_or(DEFAULT_SOURCE_NAME))) 37 | } 38 | -------------------------------------------------------------------------------- /graphql/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-rs" 3 | version = "0.1.0" 4 | description = "An implementation of GraphQL for Rust." 5 | keywords = ["graphql", "core"] 6 | authors = ["cscks "] 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/cksac/graphql-rs" 10 | homepage = "https://github.com/cksac/graphql-rs" 11 | 12 | [lib] 13 | name = "graphql_rs" 14 | path = "src/lib.rs" 15 | 16 | [dependencies] 17 | graphql-language = { path = "../graphql-language" } 18 | -------------------------------------------------------------------------------- /graphql/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate graphql_language; 2 | 3 | pub mod types; 4 | 5 | pub use graphql_language::*; 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use types::*; 10 | use std::str::FromStr; 11 | 12 | // Custom Scalar type 13 | struct Int64; 14 | impl GraphQLType for Int64 { 15 | fn name(&self) -> &str { 16 | "Int64" 17 | } 18 | 19 | fn description(&self) -> Option<&str> { 20 | Some("The Int64 scalar type represents a signed 64‐bit numeric non‐fractional values.") 21 | } 22 | } 23 | 24 | impl GraphQLScalar for Int64 { 25 | type ValueType = i64; 26 | fn coerce_literal(&self, value: &str) -> Option { 27 | i64::from_str(value).ok() 28 | } 29 | } 30 | 31 | #[test] 32 | fn test_scalar_type() { 33 | let int = GraphQLScalarType::int(); 34 | assert_eq!("Int", int.name()); 35 | assert_eq!(Some(10), int.coerce_literal("10")); 36 | assert_eq!(None, int.coerce_literal("10.1")); 37 | assert_eq!(None, int.coerce_literal("a")); 38 | assert_eq!(None, int.coerce_literal(&i64::max_value().to_string())); 39 | assert_eq!(None, int.coerce_literal(&i64::min_value().to_string())); 40 | 41 | let float = GraphQLScalarType::float(); 42 | assert_eq!("Float", float.name()); 43 | assert_eq!(Some(2.0), float.coerce_literal("2.0")); 44 | assert_eq!(Some(2.0), float.coerce_literal("2")); 45 | assert_eq!(None, float.coerce_literal("2.0a")); 46 | 47 | let string = GraphQLScalarType::string(); 48 | assert_eq!("String", string.name()); 49 | assert_eq!(Some(String::from("abc")), string.coerce_literal("abc")); 50 | assert_eq!(Some(String::from("2.0")), string.coerce_literal("2.0")); 51 | 52 | let boolean = GraphQLScalarType::boolean(); 53 | assert_eq!("Boolean", boolean.name()); 54 | assert_eq!(Some(true), boolean.coerce_literal("true")); 55 | assert_eq!(Some(false), boolean.coerce_literal("false")); 56 | assert_eq!(None, boolean.coerce_literal("1")); 57 | assert_eq!(None, boolean.coerce_literal("0")); 58 | assert_eq!(None, boolean.coerce_literal("True")); 59 | assert_eq!(None, boolean.coerce_literal("False")); 60 | assert_eq!(None, boolean.coerce_literal("TRUE")); 61 | assert_eq!(None, boolean.coerce_literal("FALSE")); 62 | 63 | let int64 = GraphQLScalarType::custom(|| Int64); 64 | assert_eq!("Int64", int64.name()); 65 | } 66 | 67 | #[test] 68 | fn test_object_type() { 69 | let int = &GraphQLScalarType::int(); 70 | let string = &GraphQLScalarType::string(); 71 | let boolean = &GraphQLScalarType::boolean(); 72 | let int64 = &GraphQLScalarType::custom(|| Int64); 73 | 74 | let image = &GraphQLObjectType::new("Image") 75 | .description("Image Type") 76 | .field("url", |f| f.type_of(string)) 77 | .field("width", |f| f.type_of(int)) 78 | .field("height", |f| f.type_of(int)) 79 | .build(); 80 | assert_eq!("Image", image.name()); 81 | 82 | let author = &GraphQLObjectType::new("Author") 83 | .description("Author Type") 84 | .field("id", |f| f.type_of(int64)) 85 | .field("name", |f| f.type_of(string)) 86 | .field("pic", |f| { 87 | f.type_of(image) 88 | .arg("width", |a| a.type_of(int)) 89 | .arg("height", |a| a.type_of(int)) 90 | }) 91 | .field("recentArticle", |f| f.placeholder_type_of("Article")) 92 | .build(); 93 | assert_eq!("Author", author.name()); 94 | 95 | let article = &GraphQLObjectType::new("Article") 96 | .field("id", |f| f.type_of(string)) 97 | .field("isPublished", |f| f.type_of(boolean)) 98 | .field("author", |f| f.type_of(author)) 99 | .field("title", |f| f.type_of(string)) 100 | .field("body", |f| f.type_of(string)) 101 | .build(); 102 | assert_eq!("Article", article.name()); 103 | 104 | author.replace_field_placeholder_type("recentArticle", article); 105 | } 106 | 107 | #[test] 108 | fn test_interface_type() { 109 | let int = &GraphQLScalarType::int(); 110 | let string = &GraphQLScalarType::string(); 111 | 112 | let named_entity = &GraphQLInterfaceType::new("NamedEntity") 113 | .field("name", |f| f.type_of(string)) 114 | .build(); 115 | assert_eq!("NamedEntity", named_entity.name()); 116 | 117 | let person = &GraphQLObjectType::new("Person") 118 | .field("name", |f| f.type_of(string)) 119 | .field("age", |f| f.type_of(int)) 120 | .impl_interface(named_entity) 121 | .build(); 122 | assert_eq!("Person", person.name()); 123 | 124 | let business = &GraphQLObjectType::new("Business") 125 | .field("name", |f| f.type_of(string)) 126 | .field("employeeCount", |f| f.type_of(int)) 127 | .impl_interface(named_entity) 128 | .build(); 129 | assert_eq!("Business", business.name()); 130 | } 131 | 132 | #[test] 133 | fn test_union_type() { 134 | let int = &GraphQLScalarType::int(); 135 | let string = &GraphQLScalarType::string(); 136 | 137 | let person = &GraphQLObjectType::new("Person") 138 | .field("name", |f| f.type_of(string)) 139 | .field("age", |f| f.type_of(int)) 140 | .build(); 141 | assert_eq!("Person", person.name()); 142 | 143 | let photo = &GraphQLObjectType::new("Photo") 144 | .field("height", |f| f.type_of(int)) 145 | .field("width", |f| f.type_of(int)) 146 | .build(); 147 | assert_eq!("Photo", photo.name()); 148 | 149 | let search_result = &GraphQLUnionType::new("SearchResult") 150 | .description("Result will be either Person or Photo") 151 | .maybe_type_of(person) 152 | .maybe_type_of(photo) 153 | .build(); 154 | 155 | assert_eq!("SearchResult", search_result.name()); 156 | } 157 | 158 | #[test] 159 | fn test_enum_type() { 160 | let rgb = GraphQLEnumType::new("RGB") 161 | .value("RED", |v| v) 162 | .value("GREEN", |v| v) 163 | .value("BLUE", |v| v) 164 | .build(); 165 | assert_eq!("RGB", rgb.name()); 166 | 167 | let days = GraphQLEnumType::new("DAYS") 168 | .description("Days of the week") 169 | .value("SAT", |v| v.description("Satarday")) 170 | .value("SUN", |v| v.description("Sunday")) 171 | .value("MON", |v| v.description("Monday")) 172 | .value("TUE", |v| v.description("Tuesday")) 173 | .value("WED", |v| v.description("Wedsday")) 174 | .value("THU", |v| v.description("Thusday")) 175 | .value("FRI", |v| v.description("Friday")) 176 | .build(); 177 | assert_eq!("DAYS", days.name()); 178 | } 179 | 180 | #[test] 181 | fn test_input_object_type() { 182 | let float = &GraphQLScalarType::float(); 183 | let optional_float = &GraphQLOptionalType::input(float); 184 | 185 | let geo_point = &GraphQLInputObjectType::new("GeoPoint") 186 | .field("lat", |f| f.type_of(float)) 187 | .field("lon", |f| f.type_of(float)) 188 | .field("alt", |f| f.type_of(optional_float)) 189 | .build(); 190 | assert_eq!("GeoPoint", geo_point.name()); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /graphql/src/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::str::FromStr; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | macro_rules! blanket_impl { 7 | ($trait_: ident for $($type_: ty),*) => { 8 | $( 9 | impl $trait_ for $type_ {} 10 | )* 11 | }; 12 | } 13 | 14 | macro_rules! impl_graphql_type_for { 15 | ($tt: ty where name = $name: expr, description = $desc: expr) => { 16 | impl GraphQLType for $tt { 17 | fn name(&self) -> &str { $name } 18 | fn description(&self) -> Option<&str> { Some($desc) } 19 | } 20 | }; 21 | 22 | ($($type_: ty),*) => { 23 | $( 24 | impl GraphQLType for $type_ { 25 | fn name(&self) -> &str { self.name.as_ref() } 26 | 27 | fn description(&self) -> Option<&str> { 28 | self.description.as_ref().map(|s| s.as_ref()) 29 | } 30 | } 31 | )* 32 | }; 33 | } 34 | 35 | macro_rules! impl_scalar_type_for { 36 | ($tt: ty as $value_type: ident where name = $name: expr, description = $desc: expr) => { 37 | impl_graphql_type_for! { $tt where name = $name, description = $desc } 38 | 39 | impl GraphQLScalar for $tt { 40 | type ValueType = $value_type; 41 | fn coerce_literal(&self, value: &str) -> Option { 42 | $value_type::from_str(value).ok() 43 | } 44 | } 45 | }; 46 | } 47 | 48 | pub trait GraphQLType { 49 | fn name(&self) -> &str; 50 | fn description(&self) -> Option<&str>; 51 | } 52 | impl_graphql_type_for! { GraphQLObject, GraphQLInterface, GraphQLUnion, GraphQLEnum, GraphQLInputObject, GraphQLList, GraphQLInputList, GraphQLInputOptional, GraphQLOptional } 53 | 54 | pub trait GraphQLInput: GraphQLType {} 55 | impl GraphQLInput for T {} 56 | blanket_impl! { GraphQLInput for GraphQLEnum, GraphQLInputObject, GraphQLInputList, GraphQLInputOptional } 57 | 58 | pub trait GraphQLOutput: GraphQLType {} 59 | impl GraphQLOutput for T {} 60 | blanket_impl! { GraphQLOutput for GraphQLObject, GraphQLInterface, GraphQLUnion, GraphQLEnum, GraphQLList, GraphQLOptional } 61 | 62 | /// Scalars 63 | pub trait GraphQLScalar: GraphQLType { 64 | type ValueType; 65 | fn coerce_literal(&self, value: &str) -> Option; 66 | } 67 | 68 | /// Built-in Scalars 69 | pub struct GraphQLInt; 70 | impl_scalar_type_for! { GraphQLInt as i32 where 71 | name = "Int", 72 | description = "The Int scalar type represents a signed 32‐bit numeric non‐fractional values." 73 | } 74 | 75 | pub struct GraphQLFloat; 76 | impl_scalar_type_for! { GraphQLFloat as f64 where 77 | name = "Float", 78 | description = "The Float scalar type represents signed double-precision fractional values as specified by IEEE 754." 79 | } 80 | 81 | pub struct GraphQLString; 82 | impl_scalar_type_for! { GraphQLString as String where 83 | name = "String", 84 | description = "The String scalar type represents textual data, represented as UTF-8 character sequences." 85 | } 86 | 87 | pub struct GraphQLBoolean; 88 | impl_scalar_type_for! { GraphQLBoolean as bool where 89 | name = "Boolean", 90 | description = "The Boolean scalar type represents true or false." 91 | } 92 | 93 | /// Object 94 | pub struct GraphQLObject { 95 | name: String, 96 | description: Option, 97 | fields: RefCell>, 98 | interfaces: Option>>, 99 | } 100 | 101 | impl GraphQLObject { 102 | pub fn replace_field_placeholder_type(&self, 103 | field_name: &str, 104 | other_type: &Rc) { 105 | let field = self.fields.borrow_mut().remove(field_name); 106 | if field.is_none() { 107 | panic!("Object type {:} does not have placeholder {:} field.", 108 | self.name, 109 | field_name); 110 | } 111 | 112 | if let Some(mut f) = field { 113 | let f_type_name = f.typ.name().to_owned(); 114 | if !f_type_name.ends_with("___TypePlaceholder___") { 115 | panic!("Field {:} in object type {:} is not a placeholder.", 116 | field_name, 117 | self.name); 118 | } 119 | 120 | let target_type = f_type_name.trim_right_matches("___TypePlaceholder___"); 121 | if target_type != other_type.name() { 122 | panic!("Placeholder {:} in object type {:} should replaced by {:} type instead of \ 123 | {:} type.", 124 | field_name, 125 | self.name, 126 | target_type, 127 | other_type.name()); 128 | } 129 | 130 | f.typ = other_type.clone(); 131 | self.fields.borrow_mut().insert(field_name.to_owned(), f); 132 | } 133 | } 134 | } 135 | 136 | pub struct GraphQLField { 137 | name: String, 138 | description: Option, 139 | deprecation_reason: Option, 140 | typ: Rc, 141 | args: Option>, 142 | } 143 | 144 | pub struct GraphQLArgument { 145 | name: String, 146 | description: Option, 147 | typ: Rc, 148 | default_value: Option, 149 | } 150 | 151 | /// Interfaces 152 | pub struct GraphQLInterface { 153 | name: String, 154 | description: Option, 155 | fields: RefCell>, 156 | } 157 | 158 | impl GraphQLInterface { 159 | pub fn replace_field_placeholder_type(&self, 160 | field_name: &str, 161 | other_type: &Rc) { 162 | let mut field = self.fields.borrow_mut().remove(field_name); 163 | if field.is_none() { 164 | panic!("Interface type {:} does not have placeholder {:} field.", 165 | self.name, 166 | field_name); 167 | } 168 | 169 | if let Some(mut f) = field { 170 | let f_type_name = f.typ.name().to_owned(); 171 | if !f_type_name.ends_with("___TypePlaceholder___") { 172 | panic!("Field {:} in interface type {:} is not a placeholder.", 173 | field_name, 174 | self.name); 175 | } 176 | 177 | let target_type = f_type_name.trim_right_matches("___TypePlaceholder___"); 178 | if target_type != other_type.name() { 179 | panic!("Placeholder {:} in interface type {:} should replaced by {:} type instead of \ 180 | {:} type.", 181 | field_name, 182 | self.name, 183 | target_type, 184 | other_type.name()); 185 | } 186 | 187 | f.typ = other_type.clone(); 188 | self.fields.borrow_mut().insert(field_name.to_owned(), f); 189 | } 190 | } 191 | } 192 | 193 | /// Union 194 | pub struct GraphQLUnion { 195 | name: String, 196 | description: Option, 197 | types: HashMap>, 198 | } 199 | 200 | /// Enum 201 | pub struct GraphQLEnum { 202 | name: String, 203 | description: Option, 204 | values: HashMap, 205 | } 206 | 207 | pub struct GraphQLEnumValue { 208 | value: String, 209 | description: Option, 210 | deprecation_reason: Option, 211 | } 212 | 213 | /// Input Object 214 | pub struct GraphQLInputObject { 215 | name: String, 216 | description: Option, 217 | fields: RefCell>, 218 | } 219 | 220 | pub struct GraphQLInputField { 221 | name: String, 222 | description: Option, 223 | typ: Rc, 224 | } 225 | 226 | /// List 227 | pub struct GraphQLInputList { 228 | name: String, 229 | description: Option, 230 | of_typ: Rc, 231 | } 232 | 233 | pub struct GraphQLList { 234 | name: String, 235 | description: Option, 236 | of_typ: Rc, 237 | } 238 | 239 | /// Optional 240 | pub struct GraphQLInputOptional { 241 | name: String, 242 | description: Option, 243 | of_typ: Rc, 244 | } 245 | 246 | pub struct GraphQLOptional { 247 | name: String, 248 | description: Option, 249 | of_typ: Rc, 250 | } 251 | 252 | // ///////////////////////////////////////////////////////////////////////////// 253 | // Type Builders 254 | // ///////////////////////////////////////////////////////////////////////////// 255 | 256 | // Internal type placeholder for forward reference to other graphql type. 257 | struct Placeholder { 258 | name: String, 259 | } 260 | 261 | impl Placeholder { 262 | fn new(target_type_name: &str) -> Placeholder { 263 | Placeholder { name: format!("{:}___TypePlaceholder___", target_type_name) } 264 | } 265 | } 266 | 267 | impl GraphQLType for Placeholder { 268 | fn name(&self) -> &str { 269 | self.name.as_ref() 270 | } 271 | 272 | fn description(&self) -> Option<&str> { 273 | None 274 | } 275 | } 276 | 277 | impl GraphQLOutput for Placeholder {} 278 | 279 | /// Scalar type builder 280 | pub struct GraphQLScalarType; 281 | 282 | impl GraphQLScalarType { 283 | pub fn int() -> Rc { 284 | Rc::new(GraphQLInt) 285 | } 286 | 287 | pub fn float() -> Rc { 288 | Rc::new(GraphQLFloat) 289 | } 290 | 291 | pub fn string() -> Rc { 292 | Rc::new(GraphQLString) 293 | } 294 | 295 | pub fn boolean() -> Rc { 296 | Rc::new(GraphQLBoolean) 297 | } 298 | 299 | pub fn custom(f: F) -> Rc 300 | where T: GraphQLScalar, 301 | F: Fn() -> T 302 | { 303 | Rc::new(f()) 304 | } 305 | } 306 | 307 | /// Object type builder 308 | pub struct GraphQLObjectType { 309 | name: String, 310 | description: Option, 311 | fields: HashMap, 312 | interfaces: Option>>, 313 | } 314 | 315 | impl GraphQLObjectType { 316 | pub fn new(name: &str) -> GraphQLObjectType { 317 | GraphQLObjectType { 318 | name: name.to_owned(), 319 | description: None, 320 | fields: HashMap::new(), 321 | interfaces: None, 322 | } 323 | } 324 | 325 | pub fn description(mut self, description: &str) -> GraphQLObjectType { 326 | self.description = Some(description.to_owned()); 327 | self 328 | } 329 | 330 | pub fn field(mut self, name: &str, f: F) -> GraphQLObjectType 331 | where F: Fn(GraphQLFieldBuilder) -> GraphQLFieldBuilder 332 | { 333 | let field = f(GraphQLFieldBuilder::new(name)).build(); 334 | self.fields.insert(name.to_owned(), field); 335 | self 336 | } 337 | 338 | pub fn impl_interface(mut self, interface: &Rc) -> GraphQLObjectType { 339 | match self.interfaces { 340 | Some(ref mut interfaces) => { 341 | interfaces.insert(interface.name().to_owned(), interface.clone()); 342 | } 343 | None => { 344 | let mut interfaces = HashMap::new(); 345 | interfaces.insert(interface.name().to_owned(), interface.clone()); 346 | self.interfaces = Some(interfaces); 347 | } 348 | } 349 | self 350 | } 351 | 352 | pub fn build(self) -> Rc { 353 | if self.fields.len() == 0 { 354 | panic!("Object type {:} must contains at least one field", 355 | self.name); 356 | } 357 | 358 | Rc::new(GraphQLObject { 359 | name: self.name, 360 | description: self.description, 361 | fields: RefCell::new(self.fields), 362 | interfaces: self.interfaces, 363 | }) 364 | } 365 | } 366 | 367 | /// interface type builder 368 | pub struct GraphQLInterfaceType { 369 | name: String, 370 | description: Option, 371 | fields: HashMap, 372 | } 373 | 374 | impl GraphQLInterfaceType { 375 | pub fn new(name: &str) -> GraphQLInterfaceType { 376 | GraphQLInterfaceType { 377 | name: name.to_owned(), 378 | description: None, 379 | fields: HashMap::new(), 380 | } 381 | } 382 | 383 | pub fn description(mut self, description: &str) -> GraphQLInterfaceType { 384 | self.description = Some(description.to_owned()); 385 | self 386 | } 387 | 388 | pub fn field(mut self, name: &str, f: F) -> GraphQLInterfaceType 389 | where F: Fn(GraphQLFieldBuilder) -> GraphQLFieldBuilder 390 | { 391 | let field = f(GraphQLFieldBuilder::new(name)).build(); 392 | self.fields.insert(name.to_owned(), field); 393 | self 394 | } 395 | 396 | pub fn build(self) -> Rc { 397 | if self.fields.len() == 0 { 398 | panic!("Interface type {:} must contains at least one field", 399 | self.name); 400 | } 401 | 402 | Rc::new(GraphQLInterface { 403 | name: self.name, 404 | description: self.description, 405 | fields: RefCell::new(self.fields), 406 | }) 407 | } 408 | } 409 | 410 | pub struct GraphQLFieldBuilder { 411 | name: String, 412 | description: Option, 413 | deprecation_reason: Option, 414 | typ: Option>, 415 | args: Option>, 416 | } 417 | 418 | impl GraphQLFieldBuilder { 419 | fn new(name: &str) -> GraphQLFieldBuilder { 420 | GraphQLFieldBuilder { 421 | name: name.to_owned(), 422 | description: None, 423 | deprecation_reason: None, 424 | typ: None, 425 | args: None, 426 | } 427 | } 428 | 429 | pub fn description(mut self, description: &str) -> GraphQLFieldBuilder { 430 | self.description = Some(description.to_owned()); 431 | self 432 | } 433 | 434 | pub fn mark_deprecated(mut self, reason: &str) -> GraphQLFieldBuilder { 435 | self.deprecation_reason = Some(reason.to_owned()); 436 | self 437 | } 438 | 439 | pub fn type_of(mut self, typ: &Rc) -> GraphQLFieldBuilder { 440 | self.typ = Some(typ.clone()); 441 | self 442 | } 443 | 444 | pub fn placeholder_type_of(mut self, target_type: &str) -> GraphQLFieldBuilder { 445 | self.typ = Some(Rc::new(Placeholder::new(target_type))); 446 | self 447 | } 448 | 449 | pub fn arg(mut self, name: &str, f: F) -> GraphQLFieldBuilder 450 | where F: Fn(GraphQLArgumentBuilder) -> GraphQLArgumentBuilder 451 | { 452 | let arg = f(GraphQLArgumentBuilder::new(name)).build(); 453 | match self.args { 454 | Some(ref mut args) => { 455 | args.insert(name.to_owned(), arg); 456 | } 457 | None => { 458 | let mut args = HashMap::new(); 459 | args.insert(name.to_owned(), arg); 460 | self.args = Some(args); 461 | } 462 | } 463 | self 464 | } 465 | 466 | fn build(self) -> GraphQLField { 467 | if self.typ.is_none() { 468 | panic!("Field {:} missing type defination", self.name); 469 | } 470 | 471 | GraphQLField { 472 | name: self.name, 473 | description: self.description, 474 | deprecation_reason: self.deprecation_reason, 475 | typ: self.typ.unwrap(), 476 | args: None, 477 | } 478 | } 479 | } 480 | 481 | pub struct GraphQLArgumentBuilder { 482 | name: String, 483 | description: Option, 484 | typ: Option>, 485 | default_value: Option, 486 | } 487 | 488 | impl GraphQLArgumentBuilder { 489 | fn new(name: &str) -> GraphQLArgumentBuilder { 490 | GraphQLArgumentBuilder { 491 | name: name.to_owned(), 492 | description: None, 493 | default_value: None, 494 | typ: None, 495 | } 496 | } 497 | 498 | pub fn type_of(mut self, typ: &Rc) -> GraphQLArgumentBuilder { 499 | self.typ = Some(typ.clone()); 500 | self 501 | } 502 | 503 | pub fn default_value(mut self, default_value: &str) -> GraphQLArgumentBuilder { 504 | self.default_value = Some(default_value.to_owned()); 505 | self 506 | } 507 | 508 | fn build(self) -> GraphQLArgument { 509 | if self.typ.is_none() { 510 | panic!("Argument {:} missing type defination", self.name); 511 | } 512 | 513 | GraphQLArgument { 514 | name: self.name, 515 | description: self.description, 516 | default_value: self.default_value, 517 | typ: self.typ.unwrap(), 518 | } 519 | } 520 | } 521 | 522 | /// Union type builder 523 | pub struct GraphQLUnionType { 524 | name: String, 525 | description: Option, 526 | types: HashMap>, 527 | } 528 | 529 | impl GraphQLUnionType { 530 | pub fn new(name: &str) -> GraphQLUnionType { 531 | GraphQLUnionType { 532 | name: name.to_owned(), 533 | description: None, 534 | types: HashMap::new(), 535 | } 536 | } 537 | 538 | pub fn description(mut self, description: &str) -> GraphQLUnionType { 539 | self.description = Some(description.to_owned()); 540 | self 541 | } 542 | 543 | pub fn maybe_type_of(mut self, typ: &Rc) -> GraphQLUnionType { 544 | self.types.insert(typ.name().to_owned(), typ.clone()); 545 | self 546 | } 547 | 548 | pub fn build(self) -> Rc { 549 | if self.types.len() == 0 { 550 | panic!("Union {:} must has at least one possible type defined.", 551 | self.name); 552 | } 553 | 554 | Rc::new(GraphQLUnion { 555 | name: self.name, 556 | description: self.description, 557 | types: self.types, 558 | }) 559 | } 560 | } 561 | 562 | /// Enum type builder 563 | pub struct GraphQLEnumType { 564 | name: String, 565 | description: Option, 566 | values: HashMap, 567 | } 568 | 569 | impl GraphQLEnumType { 570 | pub fn new(name: &str) -> GraphQLEnumType { 571 | GraphQLEnumType { 572 | name: name.to_owned(), 573 | description: None, 574 | values: HashMap::new(), 575 | } 576 | } 577 | 578 | pub fn description(mut self, description: &str) -> GraphQLEnumType { 579 | self.description = Some(description.to_owned()); 580 | self 581 | } 582 | 583 | pub fn value(mut self, name: &str, f: F) -> GraphQLEnumType 584 | where F: Fn(GraphQLEnumValueBuilder) -> GraphQLEnumValueBuilder 585 | { 586 | let v = f(GraphQLEnumValueBuilder::new(name)).build(); 587 | self.values.insert(name.to_owned(), v); 588 | self 589 | } 590 | 591 | pub fn build(self) -> Rc { 592 | if self.values.len() == 0 { 593 | panic!("Enum {:} must has at least one value defined.", self.name); 594 | } 595 | 596 | Rc::new(GraphQLEnum { 597 | name: self.name, 598 | description: self.description, 599 | values: self.values, 600 | }) 601 | } 602 | } 603 | 604 | pub struct GraphQLEnumValueBuilder { 605 | value: String, 606 | description: Option, 607 | deprecation_reason: Option, 608 | } 609 | 610 | impl GraphQLEnumValueBuilder { 611 | fn new(value: &str) -> GraphQLEnumValueBuilder { 612 | GraphQLEnumValueBuilder { 613 | value: value.to_owned(), 614 | description: None, 615 | deprecation_reason: None, 616 | } 617 | } 618 | 619 | pub fn description(mut self, description: &str) -> GraphQLEnumValueBuilder { 620 | self.description = Some(description.to_owned()); 621 | self 622 | } 623 | 624 | pub fn mark_deprecated(mut self, deprecation_reason: &str) -> GraphQLEnumValueBuilder { 625 | let reason = deprecation_reason.trim().to_owned(); 626 | if reason.len() == 0 { 627 | panic!("Deprecation reason for enum value {:} cannot be empty", 628 | self.value); 629 | } 630 | self.deprecation_reason = Some(reason); 631 | self 632 | } 633 | 634 | fn build(self) -> GraphQLEnumValue { 635 | GraphQLEnumValue { 636 | value: self.value, 637 | description: self.description, 638 | deprecation_reason: self.deprecation_reason, 639 | } 640 | } 641 | } 642 | 643 | /// Input object type builder 644 | pub struct GraphQLInputObjectType { 645 | name: String, 646 | description: Option, 647 | fields: HashMap, 648 | } 649 | 650 | impl GraphQLInputObjectType { 651 | pub fn new(name: &str) -> GraphQLInputObjectType { 652 | GraphQLInputObjectType { 653 | name: name.to_owned(), 654 | description: None, 655 | fields: HashMap::new(), 656 | } 657 | } 658 | 659 | pub fn description(mut self, description: &str) -> GraphQLInputObjectType { 660 | self.description = Some(description.to_owned()); 661 | self 662 | } 663 | 664 | pub fn field(mut self, name: &str, f: F) -> GraphQLInputObjectType 665 | where F: Fn(GraphQLInputFieldBuilder) -> GraphQLInputFieldBuilder 666 | { 667 | let field = f(GraphQLInputFieldBuilder::new(name)).build(); 668 | self.fields.insert(name.to_owned(), field); 669 | self 670 | } 671 | 672 | pub fn build(self) -> Rc { 673 | if self.fields.len() == 0 { 674 | panic!("Input object type {:} must contains at least one field", 675 | self.name); 676 | } 677 | 678 | Rc::new(GraphQLInputObject { 679 | name: self.name, 680 | description: self.description, 681 | fields: RefCell::new(self.fields), 682 | }) 683 | } 684 | } 685 | 686 | pub struct GraphQLInputFieldBuilder { 687 | name: String, 688 | description: Option, 689 | typ: Option>, 690 | } 691 | 692 | impl GraphQLInputFieldBuilder { 693 | fn new(name: &str) -> GraphQLInputFieldBuilder { 694 | GraphQLInputFieldBuilder { 695 | name: name.to_owned(), 696 | description: None, 697 | typ: None, 698 | } 699 | } 700 | 701 | pub fn type_of(mut self, typ: &Rc) -> GraphQLInputFieldBuilder { 702 | self.typ = Some(typ.clone()); 703 | self 704 | } 705 | 706 | fn build(self) -> GraphQLInputField { 707 | if self.typ.is_none() { 708 | panic!("Input object field {:} missing type defination", self.name); 709 | } 710 | 711 | GraphQLInputField { 712 | name: self.name, 713 | description: self.description, 714 | typ: self.typ.unwrap(), 715 | } 716 | } 717 | } 718 | 719 | /// List type builder 720 | pub struct GraphQLListType; 721 | impl GraphQLListType { 722 | pub fn input(of_type: &Rc) -> Rc { 723 | Rc::new(GraphQLInputList { 724 | name: of_type.name().to_owned(), 725 | description: Some(format!("List of {}", of_type.name())), 726 | of_typ: of_type.clone(), 727 | }) 728 | } 729 | 730 | pub fn output(of_type: &Rc) -> Rc { 731 | Rc::new(GraphQLList { 732 | name: of_type.name().to_owned(), 733 | description: Some(format!("List of {}", of_type.name())), 734 | of_typ: of_type.clone(), 735 | }) 736 | } 737 | } 738 | 739 | /// Optional type builder 740 | pub struct GraphQLOptionalType; 741 | impl GraphQLOptionalType { 742 | pub fn input(of_type: &Rc) -> Rc { 743 | Rc::new(GraphQLInputOptional { 744 | name: of_type.name().to_owned(), 745 | description: Some(format!("Optional {}", of_type.name())), 746 | of_typ: of_type.clone(), 747 | }) 748 | } 749 | 750 | pub fn output(of_type: &Rc) -> Rc { 751 | Rc::new(GraphQLOptional { 752 | name: of_type.name().to_owned(), 753 | description: Some(format!("Optional {}", of_type.name())), 754 | of_typ: of_type.clone(), 755 | }) 756 | } 757 | } 758 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 --------------------------------------------------------------------------------