bluejay_parser/ast/executable/
executable_document.rs1use 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, ParseDetails, ParseError,
8 Tokens, TryFromTokens, Value,
9};
10
11#[derive(Debug)]
12pub struct ExecutableDocument<'a> {
13 operation_definitions: Vec<OperationDefinition<'a>>,
14 fragment_definitions: Vec<FragmentDefinition<'a>>,
15}
16
17impl<'a> ExecutableDocument<'a> {
18 pub(crate) fn new(
19 operation_definitions: Vec<OperationDefinition<'a>>,
20 fragment_definitions: Vec<FragmentDefinition<'a>>,
21 ) -> Self {
22 Self {
23 operation_definitions,
24 fragment_definitions,
25 }
26 }
27
28 pub fn operation_definitions(&self) -> &[OperationDefinition<'a>] {
29 &self.operation_definitions
30 }
31
32 pub fn fragment_definitions(&self) -> &[FragmentDefinition<'a>] {
33 &self.fragment_definitions
34 }
35
36 #[inline]
37 fn is_empty(&self) -> bool {
38 self.operation_definitions.is_empty() && self.fragment_definitions.is_empty()
39 }
40}
41
42impl<'a> Parse<'a> for ExecutableDocument<'a> {
43 #[inline]
44 fn parse_from_tokens(mut tokens: impl Tokens<'a>, max_depth: usize) -> ParseDetails<Self> {
45 let mut instance: Self = Self::new(Vec::new(), Vec::new());
46 let mut errors = Vec::new();
47 let mut last_pass_had_error = false;
48
49 loop {
50 last_pass_had_error = match ExecutableDefinition::try_from_tokens(
51 &mut tokens,
52 DepthLimiter::new(max_depth),
53 ) {
54 Ok(Some(ExecutableDefinition::Operation(operation_definition))) => {
55 instance.operation_definitions.push(operation_definition);
56 false
57 }
58 Ok(Some(ExecutableDefinition::Fragment(fragment_definition))) => {
59 instance.fragment_definitions.push(fragment_definition);
60 false
61 }
62 Ok(None) => {
63 if let Some(token) = tokens.next() {
64 if !last_pass_had_error {
65 errors.push(ParseError::UnexpectedToken { span: token.into() });
66 }
67 true
68 } else {
69 break;
70 }
71 }
72 Err(ParseError::MaxDepthExceeded) => {
73 errors.push(ParseError::MaxDepthExceeded);
74 break;
76 }
77 Err(err) => {
78 if !last_pass_had_error {
79 errors.push(err);
80 }
81 true
82 }
83 }
84 }
85
86 let token_count = tokens.token_count();
87 let lex_errors = tokens.into_errors();
88
89 let errors = if lex_errors.is_empty() {
90 if errors.is_empty() && instance.is_empty() {
91 vec![ParseError::EmptyDocument.into()]
92 } else {
93 errors.into_iter().map(Into::into).collect()
94 }
95 } else {
96 lex_errors.into_iter().map(Into::into).collect()
97 };
98
99 let result = if errors.is_empty() {
100 Ok(instance)
101 } else {
102 Err(errors)
103 };
104
105 ParseDetails::new(result, token_count)
106 }
107}
108
109impl<'a> bluejay_core::executable::ExecutableDocument for ExecutableDocument<'a> {
110 type Value<const CONST: bool> = Value<'a, CONST>;
111 type VariableType = VariableType<'a>;
112 type Argument<const CONST: bool> = Argument<'a, CONST>;
113 type Arguments<const CONST: bool> = Arguments<'a, CONST>;
114 type Directive<const CONST: bool> = Directive<'a, CONST>;
115 type Directives<const CONST: bool> = Directives<'a, CONST>;
116 type FragmentSpread = FragmentSpread<'a>;
117 type Field = Field<'a>;
118 type Selection = Selection<'a>;
119 type SelectionSet = SelectionSet<'a>;
120 type InlineFragment = InlineFragment<'a>;
121 type VariableDefinition = VariableDefinition<'a>;
122 type VariableDefinitions = VariableDefinitions<'a>;
123 type ExplicitOperationDefinition = ExplicitOperationDefinition<'a>;
124 type ImplicitOperationDefinition = ImplicitOperationDefinition<'a>;
125 type OperationDefinition = OperationDefinition<'a>;
126 type FragmentDefinition = FragmentDefinition<'a>;
127 type FragmentDefinitions<'b>
128 = std::slice::Iter<'b, Self::FragmentDefinition>
129 where
130 Self: 'b;
131 type OperationDefinitions<'b>
132 = std::slice::Iter<'b, Self::OperationDefinition>
133 where
134 Self: 'b;
135
136 fn operation_definitions(&self) -> Self::OperationDefinitions<'_> {
137 self.operation_definitions.iter()
138 }
139
140 fn fragment_definitions(&self) -> Self::FragmentDefinitions<'_> {
141 self.fragment_definitions.iter()
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::{ExecutableDocument, Parse};
148 use crate::ast::ParseOptions;
149
150 #[test]
154 fn rejects_type_definition() {
155 let doc = r#"
156 type Foo {
157 bar: String
158 }
159 "#;
160 let result = ExecutableDocument::parse(doc).result;
161 assert!(
162 result.is_err(),
163 "Expected parse error for type definition in executable document"
164 );
165 }
166
167 #[test]
168 fn rejects_schema_definition() {
169 let doc = r#"
170 schema {
171 query: Query
172 }
173 "#;
174 let result = ExecutableDocument::parse(doc).result;
175 assert!(
176 result.is_err(),
177 "Expected parse error for schema definition in executable document"
178 );
179 }
180
181 #[test]
182 fn rejects_mixed_executable_and_type_definitions() {
183 let doc = r#"
184 query { foo }
185 type Bar { baz: Int }
186 "#;
187 let result = ExecutableDocument::parse(doc).result;
188 assert!(
189 result.is_err(),
190 "Expected parse error when mixing executable and type definitions"
191 );
192 }
193
194 #[test]
195 fn test_success() {
196 let document = r#"
197 {
198 dog {
199 ...fragmentOne
200 ...fragmentTwo
201 }
202 }
203
204 fragment fragmentOne on Dog {
205 name
206 }
207
208 fragment fragmentTwo on Dog {
209 owner {
210 name
211 }
212 }
213 "#;
214
215 let defs = ExecutableDocument::parse(document).result.unwrap();
216
217 assert_eq!(2, defs.fragment_definitions().len());
218 assert_eq!(1, defs.operation_definitions().len());
219 }
220
221 #[test]
222 fn test_depth_limit() {
223 let document = r#"query { foo }"#;
227
228 let errors = ExecutableDocument::parse_with_options(
229 document,
230 ParseOptions {
231 graphql_ruby_compatibility: false,
232 max_depth: 1,
233 max_tokens: None,
234 },
235 )
236 .result
237 .unwrap_err();
238
239 assert_eq!(1, errors.len(), "{errors:?}");
240
241 assert_eq!("Max depth exceeded", errors[0].message());
242
243 let executable_document = ExecutableDocument::parse_with_options(
244 document,
245 ParseOptions {
246 graphql_ruby_compatibility: false,
247 max_depth: 2,
248 max_tokens: None,
249 },
250 )
251 .result
252 .unwrap();
253 assert_eq!(1, executable_document.operation_definitions().len());
254 }
255}