Skip to main content

oxirs_gql/
parser.rs

1//! GraphQL query parser implementation
2//!
3//! This module provides a GraphQL parser that converts GraphQL query strings
4//! into AST nodes for execution.
5
6use crate::ast::*;
7use anyhow::{anyhow, Result};
8use std::collections::HashMap;
9use std::iter::Peekable;
10use std::str::Chars;
11
12/// GraphQL parser for converting query strings to AST
13pub struct Parser {
14    chars: Peekable<Chars<'static>>,
15    position: usize,
16    line: usize,
17    column: usize,
18}
19
20impl Parser {
21    pub fn new(input: &str) -> Self {
22        // SAFETY: We ensure the string lives long enough for parsing
23        let input: &'static str = unsafe { std::mem::transmute(input) };
24        Self {
25            chars: input.chars().peekable(),
26            position: 0,
27            line: 1,
28            column: 1,
29        }
30    }
31
32    /// Parse a GraphQL document
33    pub fn parse_document(&mut self) -> Result<Document> {
34        self.skip_whitespace();
35        let mut definitions = Vec::new();
36
37        while self.chars.peek().is_some() {
38            definitions.push(self.parse_definition()?);
39            self.skip_whitespace();
40        }
41
42        if definitions.is_empty() {
43            return Err(anyhow!("Empty document"));
44        }
45
46        Ok(Document { definitions })
47    }
48
49    fn parse_definition(&mut self) -> Result<Definition> {
50        self.skip_whitespace();
51
52        if self.peek_keyword("query")
53            || self.peek_keyword("mutation")
54            || self.peek_keyword("subscription")
55            || self.peek_char('{')
56        {
57            Ok(Definition::Operation(self.parse_operation_definition()?))
58        } else if self.peek_keyword("fragment") {
59            Ok(Definition::Fragment(self.parse_fragment_definition()?))
60        } else {
61            Err(anyhow!("Unexpected definition at line {}", self.line))
62        }
63    }
64
65    fn parse_operation_definition(&mut self) -> Result<OperationDefinition> {
66        self.skip_whitespace();
67
68        let operation_type = if self.peek_char('{') {
69            OperationType::Query // Shorthand query
70        } else {
71            self.parse_operation_type()?
72        };
73
74        let name = if self.peek_name() && !self.peek_char('{') {
75            Some(self.parse_name()?)
76        } else {
77            None
78        };
79
80        let variable_definitions = if self.peek_char('(') {
81            self.parse_variable_definitions()?
82        } else {
83            Vec::new()
84        };
85
86        let directives = self.parse_directives()?;
87        let selection_set = self.parse_selection_set()?;
88
89        Ok(OperationDefinition {
90            operation_type,
91            name,
92            variable_definitions,
93            directives,
94            selection_set,
95        })
96    }
97
98    fn parse_operation_type(&mut self) -> Result<OperationType> {
99        let keyword = self.parse_name()?;
100        match keyword.as_str() {
101            "query" => Ok(OperationType::Query),
102            "mutation" => Ok(OperationType::Mutation),
103            "subscription" => Ok(OperationType::Subscription),
104            _ => Err(anyhow!("Invalid operation type: {}", keyword)),
105        }
106    }
107
108    fn parse_variable_definitions(&mut self) -> Result<Vec<VariableDefinition>> {
109        self.expect_char('(')?;
110        let mut definitions = Vec::new();
111
112        while !self.peek_char(')') {
113            definitions.push(self.parse_variable_definition()?);
114            if self.peek_char(',') {
115                self.next_char();
116            }
117            self.skip_whitespace();
118        }
119
120        self.expect_char(')')?;
121        Ok(definitions)
122    }
123
124    fn parse_variable_definition(&mut self) -> Result<VariableDefinition> {
125        let variable = self.parse_variable()?;
126        self.expect_char(':')?;
127        let type_ = self.parse_type()?;
128
129        let default_value = if self.peek_char('=') {
130            self.next_char();
131            Some(self.parse_value()?)
132        } else {
133            None
134        };
135
136        let directives = self.parse_directives()?;
137
138        Ok(VariableDefinition {
139            variable,
140            type_,
141            default_value,
142            directives,
143        })
144    }
145
146    fn parse_variable(&mut self) -> Result<Variable> {
147        self.expect_char('$')?;
148        let name = self.parse_name()?;
149        Ok(Variable { name })
150    }
151
152    fn parse_type(&mut self) -> Result<Type> {
153        self.skip_whitespace();
154        let mut type_ = if self.peek_char('[') {
155            self.next_char(); // consume '['
156            let inner_type = self.parse_type()?;
157            self.expect_char(']')?;
158            Type::ListType(Box::new(inner_type))
159        } else {
160            let name = self.parse_name()?;
161            Type::NamedType(name)
162        };
163
164        if self.peek_char('!') {
165            self.next_char();
166            type_ = Type::NonNullType(Box::new(type_));
167        }
168
169        Ok(type_)
170    }
171
172    fn parse_selection_set(&mut self) -> Result<SelectionSet> {
173        self.expect_char('{')?;
174        let mut selections = Vec::new();
175
176        while !self.peek_char('}') {
177            selections.push(self.parse_selection()?);
178            self.skip_whitespace();
179        }
180
181        self.expect_char('}')?;
182        Ok(SelectionSet { selections })
183    }
184
185    fn parse_selection(&mut self) -> Result<Selection> {
186        self.skip_whitespace();
187
188        if self.peek_keyword("...") {
189            self.consume_keyword("...")?;
190            if self.peek_keyword("on") {
191                Ok(Selection::InlineFragment(self.parse_inline_fragment()?))
192            } else {
193                Ok(Selection::FragmentSpread(self.parse_fragment_spread()?))
194            }
195        } else {
196            Ok(Selection::Field(self.parse_field()?))
197        }
198    }
199
200    fn parse_field(&mut self) -> Result<Field> {
201        let mut name = self.parse_name()?;
202        let mut alias = None;
203
204        // Check for alias
205        if self.peek_char(':') {
206            self.next_char();
207            alias = Some(name);
208            name = self.parse_name()?;
209        }
210
211        let arguments = if self.peek_char('(') {
212            self.parse_arguments()?
213        } else {
214            Vec::new()
215        };
216
217        let directives = self.parse_directives()?;
218
219        let selection_set = if self.peek_char('{') {
220            Some(self.parse_selection_set()?)
221        } else {
222            None
223        };
224
225        Ok(Field {
226            alias,
227            name,
228            arguments,
229            directives,
230            selection_set,
231        })
232    }
233
234    fn parse_arguments(&mut self) -> Result<Vec<Argument>> {
235        self.expect_char('(')?;
236        let mut arguments = Vec::new();
237
238        while !self.peek_char(')') {
239            arguments.push(self.parse_argument()?);
240            if self.peek_char(',') {
241                self.next_char();
242            }
243            self.skip_whitespace();
244        }
245
246        self.expect_char(')')?;
247        Ok(arguments)
248    }
249
250    fn parse_argument(&mut self) -> Result<Argument> {
251        let name = self.parse_name()?;
252        self.expect_char(':')?;
253        let value = self.parse_value()?;
254
255        Ok(Argument { name, value })
256    }
257
258    fn parse_value(&mut self) -> Result<Value> {
259        self.skip_whitespace();
260
261        if self.peek_char('$') {
262            Ok(Value::Variable(self.parse_variable()?))
263        } else if self.peek_char('"') {
264            Ok(Value::StringValue(self.parse_string()?))
265        } else if self.peek_char('[') {
266            Ok(Value::ListValue(self.parse_list_value()?))
267        } else if self.peek_char('{') {
268            Ok(Value::ObjectValue(self.parse_object_value()?))
269        } else if self.peek_keyword("null") {
270            self.consume_keyword("null")?;
271            Ok(Value::NullValue)
272        } else if self.peek_keyword("true") {
273            self.consume_keyword("true")?;
274            Ok(Value::BooleanValue(true))
275        } else if self.peek_keyword("false") {
276            self.consume_keyword("false")?;
277            Ok(Value::BooleanValue(false))
278        } else if self.peek_numeric() {
279            self.parse_numeric_value()
280        } else {
281            // Enum value
282            let name = self.parse_name()?;
283            Ok(Value::EnumValue(name))
284        }
285    }
286
287    fn parse_string(&mut self) -> Result<String> {
288        self.expect_char('"')?;
289        let mut value = String::new();
290
291        while !self.peek_char('"') {
292            if self.peek_char('\\') {
293                self.next_char(); // consume '\'
294                match self.next_char() {
295                    Some('"') => value.push('"'),
296                    Some('\\') => value.push('\\'),
297                    Some('/') => value.push('/'),
298                    Some('b') => value.push('\u{0008}'),
299                    Some('f') => value.push('\u{000C}'),
300                    Some('n') => value.push('\n'),
301                    Some('r') => value.push('\r'),
302                    Some('t') => value.push('\t'),
303                    Some('u') => {
304                        // Unicode escape sequence
305                        let mut hex = String::new();
306                        for _ in 0..4 {
307                            if let Some(ch) = self.next_char() {
308                                hex.push(ch);
309                            } else {
310                                return Err(anyhow!("Incomplete unicode escape"));
311                            }
312                        }
313                        let code = u32::from_str_radix(&hex, 16)
314                            .map_err(|_| anyhow!("Invalid unicode escape"))?;
315                        if let Some(unicode_char) = std::char::from_u32(code) {
316                            value.push(unicode_char);
317                        } else {
318                            return Err(anyhow!("Invalid unicode code point"));
319                        }
320                    }
321                    Some(ch) => return Err(anyhow!("Invalid escape sequence: \\{}", ch)),
322                    None => return Err(anyhow!("Unterminated string")),
323                }
324            } else {
325                match self.next_char() {
326                    Some(ch) => value.push(ch),
327                    None => return Err(anyhow!("Unterminated string")),
328                }
329            }
330        }
331
332        self.expect_char('"')?;
333        Ok(value)
334    }
335
336    fn parse_list_value(&mut self) -> Result<Vec<Value>> {
337        self.expect_char('[')?;
338        let mut values = Vec::new();
339
340        while !self.peek_char(']') {
341            values.push(self.parse_value()?);
342            if self.peek_char(',') {
343                self.next_char();
344            }
345            self.skip_whitespace();
346        }
347
348        self.expect_char(']')?;
349        Ok(values)
350    }
351
352    fn parse_object_value(&mut self) -> Result<HashMap<String, Value>> {
353        self.expect_char('{')?;
354        let mut object = HashMap::new();
355
356        while !self.peek_char('}') {
357            let name = self.parse_name()?;
358            self.expect_char(':')?;
359            let value = self.parse_value()?;
360            object.insert(name, value);
361
362            if self.peek_char(',') {
363                self.next_char();
364            }
365            self.skip_whitespace();
366        }
367
368        self.expect_char('}')?;
369        Ok(object)
370    }
371
372    fn parse_numeric_value(&mut self) -> Result<Value> {
373        let mut number = String::new();
374
375        // Handle negative sign
376        if self.peek_char('-') {
377            number.push(
378                self.next_char()
379                    .expect("peeked character should be available"),
380            );
381        }
382
383        // Parse integer part
384        while let Some(&ch) = self.chars.peek() {
385            if ch.is_ascii_digit() {
386                number.push(
387                    self.next_char()
388                        .expect("peeked character should be available"),
389                );
390            } else {
391                break;
392            }
393        }
394
395        // Check for decimal point
396        if self.peek_char('.') {
397            number.push(
398                self.next_char()
399                    .expect("peeked character should be available"),
400            );
401
402            while let Some(&ch) = self.chars.peek() {
403                if ch.is_ascii_digit() {
404                    number.push(
405                        self.next_char()
406                            .expect("peeked character should be available"),
407                    );
408                } else {
409                    break;
410                }
411            }
412
413            // Parse as float
414            number
415                .parse::<f64>()
416                .map(Value::FloatValue)
417                .map_err(|_| anyhow!("Invalid float: {}", number))
418        } else {
419            // Check for exponent
420            if self.peek_char('e') || self.peek_char('E') {
421                number.push(
422                    self.next_char()
423                        .expect("peeked character should be available"),
424                );
425
426                if self.peek_char('+') || self.peek_char('-') {
427                    number.push(
428                        self.next_char()
429                            .expect("peeked character should be available"),
430                    );
431                }
432
433                while let Some(&ch) = self.chars.peek() {
434                    if ch.is_ascii_digit() {
435                        number.push(
436                            self.next_char()
437                                .expect("peeked character should be available"),
438                        );
439                    } else {
440                        break;
441                    }
442                }
443
444                // Parse as float
445                number
446                    .parse::<f64>()
447                    .map(Value::FloatValue)
448                    .map_err(|_| anyhow!("Invalid float: {}", number))
449            } else {
450                // Parse as integer
451                number
452                    .parse::<i64>()
453                    .map(Value::IntValue)
454                    .map_err(|_| anyhow!("Invalid integer: {}", number))
455            }
456        }
457    }
458
459    fn parse_fragment_definition(&mut self) -> Result<FragmentDefinition> {
460        self.consume_keyword("fragment")?;
461        let name = self.parse_name()?;
462        self.consume_keyword("on")?;
463        let type_condition = self.parse_name()?;
464        let directives = self.parse_directives()?;
465        let selection_set = self.parse_selection_set()?;
466
467        Ok(FragmentDefinition {
468            name,
469            type_condition,
470            directives,
471            selection_set,
472        })
473    }
474
475    fn parse_fragment_spread(&mut self) -> Result<FragmentSpread> {
476        let fragment_name = self.parse_name()?;
477        let directives = self.parse_directives()?;
478
479        Ok(FragmentSpread {
480            fragment_name,
481            directives,
482        })
483    }
484
485    fn parse_inline_fragment(&mut self) -> Result<InlineFragment> {
486        let type_condition = if self.peek_keyword("on") {
487            self.consume_keyword("on")?;
488            Some(self.parse_name()?)
489        } else {
490            None
491        };
492
493        let directives = self.parse_directives()?;
494        let selection_set = self.parse_selection_set()?;
495
496        Ok(InlineFragment {
497            type_condition,
498            directives,
499            selection_set,
500        })
501    }
502
503    fn parse_directives(&mut self) -> Result<Vec<Directive>> {
504        let mut directives = Vec::new();
505
506        while self.peek_char('@') {
507            directives.push(self.parse_directive()?);
508        }
509
510        Ok(directives)
511    }
512
513    fn parse_directive(&mut self) -> Result<Directive> {
514        self.expect_char('@')?;
515        let name = self.parse_name()?;
516
517        let arguments = if self.peek_char('(') {
518            self.parse_arguments()?
519        } else {
520            Vec::new()
521        };
522
523        Ok(Directive { name, arguments })
524    }
525
526    fn parse_name(&mut self) -> Result<String> {
527        self.skip_whitespace();
528        let mut name = String::new();
529
530        // First character must be letter or underscore
531        match self.chars.peek() {
532            Some(&ch) if ch.is_alphabetic() || ch == '_' => {
533                name.push(
534                    self.next_char()
535                        .expect("peeked character should be available"),
536                );
537            }
538            _ => return Err(anyhow!("Expected name at line {}", self.line)),
539        }
540
541        // Subsequent characters can be letters, digits, or underscores
542        while let Some(&ch) = self.chars.peek() {
543            if ch.is_alphanumeric() || ch == '_' {
544                name.push(
545                    self.next_char()
546                        .expect("peeked character should be available"),
547                );
548            } else {
549                break;
550            }
551        }
552
553        Ok(name)
554    }
555
556    // Helper methods
557
558    fn next_char(&mut self) -> Option<char> {
559        if let Some(ch) = self.chars.next() {
560            self.position += 1;
561            if ch == '\n' {
562                self.line += 1;
563                self.column = 1;
564            } else {
565                self.column += 1;
566            }
567            Some(ch)
568        } else {
569            None
570        }
571    }
572
573    fn peek_char(&mut self, expected: char) -> bool {
574        self.skip_whitespace();
575        self.chars.peek() == Some(&expected)
576    }
577
578    fn peek_name(&mut self) -> bool {
579        self.skip_whitespace();
580        matches!(self.chars.peek(), Some(&ch) if ch.is_alphabetic() || ch == '_')
581    }
582
583    fn peek_numeric(&mut self) -> bool {
584        self.skip_whitespace();
585        matches!(self.chars.peek(), Some(&ch) if ch.is_ascii_digit() || ch == '-')
586    }
587
588    fn peek_keyword(&mut self, keyword: &str) -> bool {
589        self.skip_whitespace();
590
591        let chars: Vec<char> = self.chars.clone().take(keyword.len()).collect();
592        let word: String = chars.into_iter().collect();
593
594        if word == keyword {
595            // Only apply word-boundary check for alphabetic keywords (e.g. "query",
596            // "fragment", "on", "true", "false", "null"). Punctuation-only keywords
597            // like "..." do not need a word-boundary check because they are not
598            // identifiers and can be immediately followed by alphanumeric characters
599            // (e.g. "...FragmentName").
600            let keyword_is_alpha = keyword
601                .chars()
602                .next()
603                .map(|c| c.is_alphabetic())
604                .unwrap_or(false);
605
606            if keyword_is_alpha {
607                let next_chars: Vec<char> =
608                    self.chars.clone().skip(keyword.len()).take(1).collect();
609                if let Some(&next_ch) = next_chars.first() {
610                    !next_ch.is_alphanumeric() && next_ch != '_'
611                } else {
612                    true
613                }
614            } else {
615                true
616            }
617        } else {
618            false
619        }
620    }
621
622    fn consume_keyword(&mut self, keyword: &str) -> Result<()> {
623        self.skip_whitespace();
624
625        for expected_ch in keyword.chars() {
626            match self.next_char() {
627                Some(ch) if ch == expected_ch => continue,
628                Some(ch) => return Err(anyhow!("Expected '{}', found '{}'", expected_ch, ch)),
629                None => return Err(anyhow!("Unexpected end of input")),
630            }
631        }
632
633        Ok(())
634    }
635
636    fn expect_char(&mut self, expected: char) -> Result<()> {
637        self.skip_whitespace();
638        match self.next_char() {
639            Some(ch) if ch == expected => Ok(()),
640            Some(ch) => Err(anyhow!("Expected '{}', found '{}'", expected, ch)),
641            None => Err(anyhow!("Expected '{}', found end of input", expected)),
642        }
643    }
644
645    fn skip_whitespace(&mut self) {
646        while let Some(&ch) = self.chars.peek() {
647            if ch.is_whitespace() {
648                self.next_char();
649            } else if ch == '#' {
650                // Skip comments
651                while let Some(&ch) = self.chars.peek() {
652                    self.next_char();
653                    if ch == '\n' {
654                        break;
655                    }
656                }
657            } else {
658                break;
659            }
660        }
661    }
662}
663
664/// Parse a GraphQL document from a string
665pub fn parse_document(input: &str) -> Result<Document> {
666    let mut parser = Parser::new(input);
667    parser.parse_document()
668}
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    #[test]
675    fn test_simple_query() {
676        let query = "{ hello }";
677        let doc = parse_document(query).expect("should succeed");
678
679        assert_eq!(doc.definitions.len(), 1);
680        if let Definition::Operation(op) = &doc.definitions[0] {
681            assert!(matches!(op.operation_type, OperationType::Query));
682            assert_eq!(op.selection_set.selections.len(), 1);
683        }
684    }
685
686    #[test]
687    fn test_named_query() {
688        let query = "query GetUser { user { id name } }";
689        let doc = parse_document(query).expect("should succeed");
690
691        assert_eq!(doc.definitions.len(), 1);
692        if let Definition::Operation(op) = &doc.definitions[0] {
693            assert!(matches!(op.operation_type, OperationType::Query));
694            assert_eq!(op.name, Some("GetUser".to_string()));
695        }
696    }
697
698    #[test]
699    fn test_query_with_arguments() {
700        let query = r#"{ user(id: "123") { name } }"#;
701        let doc = parse_document(query).expect("should succeed");
702
703        assert_eq!(doc.definitions.len(), 1);
704        if let Definition::Operation(op) = &doc.definitions[0] {
705            if let Selection::Field(field) = &op.selection_set.selections[0] {
706                assert_eq!(field.name, "user");
707                assert_eq!(field.arguments.len(), 1);
708                assert_eq!(field.arguments[0].name, "id");
709            }
710        }
711    }
712
713    #[test]
714    fn test_query_with_variables() {
715        let query = "query GetUser($id: ID!) { user(id: $id) { name } }";
716        let doc = parse_document(query).expect("should succeed");
717
718        assert_eq!(doc.definitions.len(), 1);
719        if let Definition::Operation(op) = &doc.definitions[0] {
720            assert_eq!(op.variable_definitions.len(), 1);
721            assert_eq!(op.variable_definitions[0].variable.name, "id");
722        }
723    }
724
725    #[test]
726    fn test_fragment() {
727        let query = "fragment UserFields on User { id name email }";
728        let doc = parse_document(query).expect("should succeed");
729
730        assert_eq!(doc.definitions.len(), 1);
731        if let Definition::Fragment(frag) = &doc.definitions[0] {
732            assert_eq!(frag.name, "UserFields");
733            assert_eq!(frag.type_condition, "User");
734            assert_eq!(frag.selection_set.selections.len(), 3);
735        }
736    }
737}