├── .editorconfig ├── .gitignore ├── Cargo.toml ├── src └── lib.rs └── tests └── parser.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.rs] 2 | end_of_line = lf 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-parser" 3 | version = "0.1.0" 4 | authors = ["Vincent Prouillet "] 5 | 6 | [dependencies] 7 | pest = "0.4" 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Needed by pest 2 | #![recursion_limit = "300"] 3 | 4 | 5 | #[macro_use] 6 | extern crate pest; 7 | 8 | use std::result::Result; 9 | use std::collections::HashMap; 10 | 11 | use pest::prelude::*; 12 | 13 | 14 | /// A type literal in a query 15 | #[derive(Clone, Debug, PartialEq)] 16 | pub enum Type { 17 | /// A type like Int 18 | Named(String), 19 | /// A non-nullable type like Int! 20 | NonNullNamed(String), 21 | /// A nullable type like [Int]. 22 | /// The nullable part is the list, not Int in that example 23 | List(Vec), 24 | /// A non-nullable list like [Int]!, the types inside can be null 25 | NonNullList(Vec), 26 | } 27 | 28 | /// Input to fields and directives 29 | #[derive(Clone, Debug, PartialEq)] 30 | #[allow(missing_docs)] 31 | pub enum InputValue { 32 | Variable(String), 33 | Int(i64), 34 | Float(f64), 35 | String(String), 36 | Boolean(bool), 37 | Null, 38 | Enum(String), 39 | List(Vec), 40 | Object, 41 | } 42 | 43 | #[derive(Clone, Debug, PartialEq)] 44 | pub enum OperationType { 45 | Query, 46 | Mutation, 47 | } 48 | 49 | #[derive(Clone, Debug, PartialEq)] 50 | pub enum Selection { 51 | Field(Box), 52 | FragmentSpread(Box), 53 | InlineFragment(Box), 54 | } 55 | 56 | 57 | #[derive(Clone, Debug, PartialEq)] 58 | pub struct Directive { 59 | pub name: String, 60 | pub arguments: HashMap, 61 | } 62 | 63 | #[derive(Clone, Debug, PartialEq)] 64 | pub struct FragmentSpread { 65 | pub name: String, 66 | pub directives: Vec, 67 | } 68 | 69 | #[derive(Clone, Debug, PartialEq)] 70 | pub struct InlineFragment { 71 | pub type_condition: String, 72 | pub directives: Vec, 73 | pub selection_set: Selection, 74 | } 75 | 76 | #[derive(Clone, Debug, PartialEq)] 77 | pub struct Field { 78 | pub alias: Option, 79 | pub name: String, 80 | pub arguments: HashMap, 81 | pub directives: Vec, 82 | pub selection_set: Selection, 83 | } 84 | 85 | /// All nodes in GraphQL AST 86 | #[derive(Clone, Debug, PartialEq)] 87 | pub enum Node { 88 | Name(String), 89 | Document, 90 | } 91 | 92 | 93 | impl_rdp! { 94 | grammar! { 95 | whitespace = _{ ([" "] | ["\t"] | ["\r"] | ["\n"]) + } 96 | 97 | comment = _{ ["#"] ~ (!(["\n"]) ~ any)* ~ ["\n"] } 98 | letters = _{ ['A'..'Z'] | ['a'..'z'] } 99 | exp = _{ (["e"] | ["E"]) ~ (["+"] | ["-"])? ~ ['1'..'9']+ } 100 | hex = _{ ['0'..'9'] | ['a'..'f'] | ['A'..'F'] } 101 | unicode = _{ ["u"] ~ hex ~ hex ~ hex ~ hex } 102 | escape = _{ ["\\"] ~ (["\""] | ["\\"] | ["/"] | ["b"] | ["f"] | ["n"] | ["r"] | ["t"] | unicode) } 103 | 104 | op_true = { ["true"] } 105 | op_false = { ["false"] } 106 | boolean = _{ op_true | op_false } 107 | null = { ["null"] } 108 | int = @{ ["-"]? ~ (["0"] | ['1'..'9'] ~ ['0'..'9']*) } 109 | float = @{ 110 | ["-"]? ~ 111 | ( 112 | ['1'..'9']+ ~ exp | 113 | ["0"] ~ ["."] ~ ['0'..'9']+ ~ exp? | 114 | ['1'..'9'] ~ ['0'..'9']* ~ ["."] ~ ['0'..'9']+ ~ exp? 115 | ) 116 | } 117 | string = @{ ["\""] ~ (escape | !(["\""] | ["\\"]) ~ any)* ~ ["\""] } 118 | variable = @{ ["$"] ~ name } 119 | enum_val = @{ !(boolean | null) ~ name } 120 | list = @{ ["["] ~ value ~ ["]"] } 121 | arg = { name ~ [":"] ~ value } 122 | object = { ["{"] ~ (arg ~ ([","] ~ arg)*)? ~ ["}"] } 123 | 124 | name = @{ (["_"] | letters) ~ (["_"] | letters | ['0'..'9'])* } 125 | value = @{ variable | float | int | string | boolean | null | enum_val | list | object } 126 | 127 | // More variables stuff 128 | named_type = { name } 129 | list_type = {["["] ~ types ~ ["]"]} 130 | non_null_type = { (named_type | list_type) ~ ["!"]} 131 | types = { named_type | list_type | non_null_type } 132 | default_value = { ["="] ~ value } 133 | variable_def = { variable ~ [":"] ~ types ~ default_value? } 134 | variable_defs = { ["("] ~ variable_def? ~ ([","] ~ variable_def)* ~ [")"] } 135 | 136 | // Directive 137 | directive = { ["@"] ~ name ~ args? } 138 | 139 | // Selections 140 | selection = { field | fragment_spread | fragment_inline } 141 | selection_set = { ["{"] ~ selection+ ~ ["}"] } 142 | 143 | // Field 144 | alias = { name ~ [":"]} 145 | args = { ["("] ~ arg ~ ([","] ~ arg)* ~ [","]? ~ [")"]} 146 | field = { alias? ~ name ~ args? ~ directive? ~selection_set? } 147 | 148 | // Fragments 149 | fragment_name = { !["on"] ~ name } 150 | fragment_def = { ["fragment"] ~ fragment_name ~ ["on"] ~ name ~ directive? ~ selection_set } 151 | fragment_spread = @{ ["..."] ~ fragment_name ~ directive? } 152 | fragment_inline = { ["..."] ~ (["on"] ~ name)? ~ directive? ~ selection_set } 153 | 154 | query = { ["query"] ~ name? ~ variable_defs? ~ selection_set } 155 | mutation = { ["mutation"] ~ name? ~ variable_defs? ~ selection_set } 156 | operation = { query | mutation | selection_set } 157 | 158 | document = @{ soi ~ (operation | fragment_def)+ ~ eoi } 159 | } 160 | } 161 | 162 | pub fn parse(input: &str) -> Result<(), String> { 163 | let mut parser = Rdp::new(StringInput::new(input)); 164 | 165 | if !parser.document() { 166 | let (_, pos) = parser.expected(); 167 | let (line_no, col_no) = parser.input().line_col(pos); 168 | return Err(format!("Invalid GraphQL syntax at line {}, column {}", line_no, col_no)); 169 | } 170 | 171 | // parser.main() 172 | Ok(()) 173 | } 174 | 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use pest::prelude::*; 179 | use super::Rdp; 180 | 181 | #[test] 182 | fn test_lex_int() { 183 | let input = vec!["123", "-10", "0"]; 184 | for i in input { 185 | let mut parser = Rdp::new(StringInput::new(i)); 186 | assert!(parser.int()); 187 | assert!(parser.end()); 188 | } 189 | } 190 | 191 | #[test] 192 | fn test_lex_float() { 193 | let input = vec![ 194 | "123.1", "-10.1", "0.123", "12.43", 195 | "123e4", "123E4", "123e-4", "123e+4", 196 | "-1.123e4", "-1.123E4", "-1.123e-4", "-1.123e+4", "-1.123e4567", 197 | ]; 198 | for i in input { 199 | let mut parser = Rdp::new(StringInput::new(i)); 200 | assert!(parser.float()); 201 | assert!(parser.end()); 202 | } 203 | } 204 | 205 | #[test] 206 | fn test_lex_null() { 207 | let mut parser = Rdp::new(StringInput::new("null")); 208 | assert!(parser.null()); 209 | assert!(parser.end()); 210 | } 211 | 212 | #[test] 213 | fn test_lex_boolean() { 214 | let input = vec!["true", "false"]; 215 | for i in input { 216 | let mut parser = Rdp::new(StringInput::new(i)); 217 | assert!(parser.boolean()); 218 | assert!(parser.end()); 219 | } 220 | } 221 | 222 | #[test] 223 | fn test_lex_escape() { 224 | let input = vec![r#"\n"#, r#"\""#]; 225 | for i in input { 226 | let mut parser = Rdp::new(StringInput::new(i)); 227 | assert!(parser.escape()); 228 | assert!(parser.end()); 229 | } 230 | } 231 | 232 | #[test] 233 | fn test_lex_string() { 234 | let input = vec![ 235 | r#""simple""#, r#"" white space ""#, r#""quote \"""#, 236 | r#""escaped \\n\\r\\b\\t\\f""#, r#""slashes \\\\ \\/""#, 237 | r#""unicode \\u1234\\u5678\\u90AB\\uCDEF""#, 238 | ]; 239 | for i in input { 240 | let mut parser = Rdp::new(StringInput::new(i)); 241 | assert!(parser.string()); 242 | assert!(parser.end()); 243 | } 244 | } 245 | 246 | #[test] 247 | fn test_lex_comment() { 248 | let input = vec!["# a comment \n", "#\n"]; 249 | for i in input { 250 | let mut parser = Rdp::new(StringInput::new(i)); 251 | assert!(parser.comment()); 252 | assert!(parser.end()); 253 | } 254 | } 255 | 256 | #[test] 257 | fn test_lex_name() { 258 | let input = vec![ 259 | "name", "Name", "NAME", "other_name", "othername", 260 | "name12", "__type" 261 | ]; 262 | for i in input { 263 | let mut parser = Rdp::new(StringInput::new(i)); 264 | assert!(parser.name()); 265 | assert!(parser.end()); 266 | } 267 | } 268 | 269 | #[test] 270 | fn test_lex_object() { 271 | let input = vec![ 272 | "{}", "{ lon: 12.43 }", "{ lon: 12.43, lat: -53.211 }", 273 | ]; 274 | for i in input { 275 | let mut parser = Rdp::new(StringInput::new(i)); 276 | println!("{:?}", i); 277 | assert!(parser.object()); 278 | assert!(parser.end()); 279 | } 280 | } 281 | 282 | #[test] 283 | fn test_lex_args() { 284 | let input = vec![ 285 | "(size: small)", r#"(size: "small")"#, "(size: SMALL)", 286 | "(size: $size)", "(first: 10, after: 20)", 287 | ]; 288 | for i in input { 289 | let mut parser = Rdp::new(StringInput::new(i)); 290 | println!("{:?}", i); 291 | assert!(parser.args()); 292 | assert!(parser.end()); 293 | } 294 | } 295 | 296 | #[test] 297 | fn test_lex_field() { 298 | let input = vec![ 299 | "name", "pic: profilePic(size: small)", "newName: name", 300 | "pic: profilePic(size: 140)", "field1(first: 10, after: 20)", 301 | "field1(first: 10, after: 20,)", 302 | "alias: field1(first:10, after:$foo,) @include(if: $foo)" 303 | ]; 304 | for i in input { 305 | let mut parser = Rdp::new(StringInput::new(i)); 306 | println!("{:?}", i); 307 | assert!(parser.field()); 308 | assert!(parser.end()); 309 | } 310 | } 311 | 312 | #[test] 313 | fn test_lex_directive() { 314 | let input = vec![ 315 | "@defer", "@stream", "@live", "@include(if: $foo)", 316 | ]; 317 | for i in input { 318 | let mut parser = Rdp::new(StringInput::new(i)); 319 | println!("{:?}", i); 320 | assert!(parser.directive()); 321 | assert!(parser.end()); 322 | } 323 | 324 | } 325 | 326 | #[test] 327 | fn test_lex_selection_set() { 328 | let input = vec![ 329 | r#"{ 330 | id 331 | firstName 332 | lastName 333 | }"#, 334 | r#"{ 335 | me { 336 | id 337 | firstName 338 | lastName 339 | birthday { 340 | month 341 | day 342 | } 343 | friends { 344 | name 345 | } 346 | } 347 | }"#, 348 | r#"{ 349 | user(id: 4) { 350 | name 351 | } 352 | }"#, 353 | r#"{ 354 | user(id: 4) { 355 | id 356 | name 357 | smallPic: profilePic(size: 64) 358 | bigPic: profilePic(size: 1024) 359 | } 360 | }"#, 361 | r#"{ 362 | me { 363 | ...userData 364 | } 365 | }"#, 366 | r#"{ 367 | me { 368 | ... on User { 369 | friends { 370 | count 371 | } 372 | } 373 | } 374 | }"#, 375 | r#"{ 376 | hero(episode: $episode) { 377 | name 378 | friends { 379 | name 380 | } 381 | } 382 | }"#, 383 | ]; 384 | for i in input { 385 | let mut parser = Rdp::new(StringInput::new(i)); 386 | println!("{:?}", i); 387 | assert!(parser.selection_set()); 388 | assert!(parser.end()); 389 | } 390 | } 391 | 392 | #[test] 393 | fn test_lex_fragment_def() { 394 | let input = vec![ 395 | r#"fragment friendFields on User { 396 | id 397 | name 398 | profilePic(size: 50) 399 | }"#, 400 | ]; 401 | for i in input { 402 | let mut parser = Rdp::new(StringInput::new(i)); 403 | println!("{:?}", i); 404 | assert!(parser.fragment_def()); 405 | assert!(parser.end()); 406 | } 407 | } 408 | 409 | #[test] 410 | fn test_lex_inline() { 411 | let input = vec![ 412 | r#"... on User { 413 | friends { 414 | count 415 | } 416 | }"#, 417 | r#"... @include(if: $expandedInfo) { 418 | firstName 419 | lastName 420 | birthday 421 | }"#, 422 | ]; 423 | for i in input { 424 | let mut parser = Rdp::new(StringInput::new(i)); 425 | println!("{:?}", i); 426 | assert!(parser.fragment_inline()); 427 | assert!(parser.end()); 428 | } 429 | } 430 | 431 | #[test] 432 | fn test_lex_fragment_spread() { 433 | let mut parser = Rdp::new(StringInput::new("...userData")); 434 | assert!(parser.fragment_spread()); 435 | assert!(parser.end()); 436 | } 437 | 438 | #[test] 439 | fn test_variable_defs() { 440 | let input = vec![ 441 | "()", "($episode: Episode)", "($episode: Episode, $user: Int)", 442 | "($episode: Episode = 1, $user: Int)", 443 | ]; 444 | for i in input { 445 | let mut parser = Rdp::new(StringInput::new(i)); 446 | println!("{:?}", i); 447 | assert!(parser.variable_defs()); 448 | assert!(parser.end()); 449 | } 450 | } 451 | 452 | #[test] 453 | fn test_lex_query() { 454 | let input = vec![ 455 | r#"query HeroNameAndFriends($episode: Episode) { 456 | hero(episode: $episode) { 457 | name 458 | friends { 459 | name 460 | } 461 | } 462 | }"#, 463 | r#"query inlineFragmentNoType($expandedInfo: Boolean) { 464 | user(handle: "zuck") { 465 | id 466 | name 467 | ... @include(if: $expandedInfo) { 468 | firstName 469 | lastName 470 | birthday 471 | } 472 | } 473 | }"#, 474 | //r#"{ 475 | // nearestThing(location: { lon: 12.43, lat: -53.211 }) 476 | //}"#, 477 | ]; 478 | for i in input { 479 | let mut parser = Rdp::new(StringInput::new(i)); 480 | println!("{:?}", i); 481 | assert!(parser.query()); 482 | assert!(parser.end()); 483 | } 484 | } 485 | 486 | #[test] 487 | fn test_lex_mutation() { 488 | let input = vec![ 489 | r#"mutation likeStory { 490 | like(story: 123) { 491 | story { 492 | id 493 | } 494 | } 495 | }"#, 496 | ]; 497 | for i in input { 498 | let mut parser = Rdp::new(StringInput::new(i)); 499 | println!("{:?}", i); 500 | assert!(parser.mutation()); 501 | assert!(parser.end()); 502 | } 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /tests/parser.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Keats/graphql-parser/a561e5b3ba60c71217abce9c839e66be6f370292/tests/parser.rs --------------------------------------------------------------------------------