bluejay_parser/ast/executable/
executable_document.rs

1use crate::ast::executable::{
2    ExecutableDefinition, ExplicitOperationDefinition, Field, FragmentDefinition, FragmentSpread,
3    ImplicitOperationDefinition, InlineFragment, OperationDefinition, Selection, SelectionSet,
4    VariableDefinition, VariableDefinitions, VariableType,
5};
6use crate::ast::{
7    Argument, Arguments, DepthLimiter, Directive, Directives, Parse, ParseError, Tokens,
8    TryFromTokens, Value,
9};
10use crate::Error;
11
12#[derive(Debug)]
13pub struct ExecutableDocument<'a> {
14    operation_definitions: Vec<OperationDefinition<'a>>,
15    fragment_definitions: Vec<FragmentDefinition<'a>>,
16}
17
18impl<'a> ExecutableDocument<'a> {
19    pub(crate) fn new(
20        operation_definitions: Vec<OperationDefinition<'a>>,
21        fragment_definitions: Vec<FragmentDefinition<'a>>,
22    ) -> Self {
23        Self {
24            operation_definitions,
25            fragment_definitions,
26        }
27    }
28
29    pub fn operation_definitions(&self) -> &[OperationDefinition<'a>] {
30        &self.operation_definitions
31    }
32
33    pub fn fragment_definitions(&self) -> &[FragmentDefinition<'a>] {
34        &self.fragment_definitions
35    }
36
37    #[inline]
38    fn is_empty(&self) -> bool {
39        self.operation_definitions.is_empty() && self.fragment_definitions.is_empty()
40    }
41}
42
43impl<'a> Parse<'a> for ExecutableDocument<'a> {
44    #[inline]
45    fn parse_from_tokens(
46        mut tokens: impl Tokens<'a>,
47        max_depth: usize,
48    ) -> Result<Self, Vec<Error>> {
49        let mut instance: Self = Self::new(Vec::new(), Vec::new());
50        let mut errors = Vec::new();
51        let mut last_pass_had_error = false;
52
53        loop {
54            last_pass_had_error = if let Some(res) =
55                ExecutableDefinition::try_from_tokens(&mut tokens, DepthLimiter::new(max_depth))
56            {
57                match res {
58                    Ok(ExecutableDefinition::Operation(operation_definition)) => {
59                        instance.operation_definitions.push(operation_definition);
60                        false
61                    }
62                    Ok(ExecutableDefinition::Fragment(fragment_definition)) => {
63                        instance.fragment_definitions.push(fragment_definition);
64                        false
65                    }
66                    Err(ParseError::MaxDepthExceeded) => {
67                        errors.push(ParseError::MaxDepthExceeded);
68                        // no sense in continuing to parse if we've hit the depth limit
69                        break;
70                    }
71                    Err(err) => {
72                        if !last_pass_had_error {
73                            errors.push(err);
74                        }
75                        true
76                    }
77                }
78            } else if let Some(token) = tokens.next() {
79                if !last_pass_had_error {
80                    errors.push(ParseError::UnexpectedToken { span: token.into() });
81                }
82                true
83            } else {
84                break;
85            }
86        }
87
88        let lex_errors = tokens.into_errors();
89
90        let errors = if lex_errors.is_empty() {
91            if errors.is_empty() && instance.is_empty() {
92                vec![ParseError::EmptyDocument.into()]
93            } else {
94                errors.into_iter().map(Into::into).collect()
95            }
96        } else {
97            lex_errors.into_iter().map(Into::into).collect()
98        };
99
100        if errors.is_empty() {
101            Ok(instance)
102        } else {
103            Err(errors)
104        }
105    }
106}
107
108impl<'a> bluejay_core::executable::ExecutableDocument for ExecutableDocument<'a> {
109    type Value<const CONST: bool> = Value<'a, CONST>;
110    type VariableType = VariableType<'a>;
111    type Argument<const CONST: bool> = Argument<'a, CONST>;
112    type Arguments<const CONST: bool> = Arguments<'a, CONST>;
113    type Directive<const CONST: bool> = Directive<'a, CONST>;
114    type Directives<const CONST: bool> = Directives<'a, CONST>;
115    type FragmentSpread = FragmentSpread<'a>;
116    type Field = Field<'a>;
117    type Selection = Selection<'a>;
118    type SelectionSet = SelectionSet<'a>;
119    type InlineFragment = InlineFragment<'a>;
120    type VariableDefinition = VariableDefinition<'a>;
121    type VariableDefinitions = VariableDefinitions<'a>;
122    type ExplicitOperationDefinition = ExplicitOperationDefinition<'a>;
123    type ImplicitOperationDefinition = ImplicitOperationDefinition<'a>;
124    type OperationDefinition = OperationDefinition<'a>;
125    type FragmentDefinition = FragmentDefinition<'a>;
126    type FragmentDefinitions<'b>
127        = std::slice::Iter<'b, Self::FragmentDefinition>
128    where
129        Self: 'b;
130    type OperationDefinitions<'b>
131        = std::slice::Iter<'b, Self::OperationDefinition>
132    where
133        Self: 'b;
134
135    fn operation_definitions(&self) -> Self::OperationDefinitions<'_> {
136        self.operation_definitions.iter()
137    }
138
139    fn fragment_definitions(&self) -> Self::FragmentDefinitions<'_> {
140        self.fragment_definitions.iter()
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::{ExecutableDocument, Parse};
147    use crate::ast::ParseOptions;
148
149    #[test]
150    fn test_success() {
151        let document = r#"
152            {
153                dog {
154                    ...fragmentOne
155                    ...fragmentTwo
156                }
157            }
158
159            fragment fragmentOne on Dog {
160                name
161            }
162
163            fragment fragmentTwo on Dog {
164                owner {
165                    name
166                }
167            }
168        "#;
169
170        let defs = ExecutableDocument::parse(document).unwrap();
171
172        assert_eq!(2, defs.fragment_definitions().len());
173        assert_eq!(1, defs.operation_definitions().len());
174    }
175
176    #[test]
177    fn test_depth_limit() {
178        // Depth is bumped to 1 entering the selection set (`{`)
179        // Depth is bumped to 2 entering the field (`foo`)
180        // Depth is bumped to 3 checking for args on the field (`foo`)
181        let document = r#"query { foo }"#;
182
183        let errors = ExecutableDocument::parse_with_options(
184            document,
185            ParseOptions {
186                graphql_ruby_compatibility: false,
187                max_depth: 2,
188            },
189        )
190        .unwrap_err();
191
192        assert_eq!(1, errors.len(), "{:?}", errors);
193
194        assert_eq!("Max depth exceeded", errors[0].message());
195
196        let executable_document = ExecutableDocument::parse_with_options(
197            document,
198            ParseOptions {
199                graphql_ruby_compatibility: false,
200                max_depth: 3,
201            },
202        )
203        .unwrap();
204        assert_eq!(1, executable_document.operation_definitions().len());
205    }
206}