1use graphql_parser::query::{
7 self, Definition, Directive as GraphQLDirective, Document, OperationDefinition, Selection,
8};
9
10use crate::graphql::types::{
11 Directive, FieldSelection, GraphQLArgument, GraphQLType, ParsedQuery, VariableDefinition,
12};
13
14#[derive(Debug, thiserror::Error)]
16pub enum GraphQLParseError {
17 #[error("Failed to parse GraphQL query: {0}")]
19 Syntax(String),
20
21 #[error("No query or mutation operation found")]
23 MissingOperation,
24
25 #[error("No fields in selection set")]
27 EmptySelection,
28}
29
30pub fn parse_query(source: &str) -> Result<ParsedQuery, GraphQLParseError> {
49 let doc: Document<String> =
51 query::parse_query(source).map_err(|e| GraphQLParseError::Syntax(e.to_string()))?;
52
53 let operation = doc
55 .definitions
56 .iter()
57 .find_map(|def| match def {
58 query::Definition::Operation(op) => Some(op),
59 query::Definition::Fragment(_) => None,
60 })
61 .ok_or(GraphQLParseError::MissingOperation)?;
62
63 let (operation_type, operation_name, root_field, selections, variables) =
65 extract_operation(operation)?;
66
67 let fragments = extract_fragments(&doc)?;
69
70 Ok(ParsedQuery {
71 operation_type,
72 operation_name,
73 root_field,
74 selections,
75 variables,
76 fragments,
77 source: source.to_string(),
78 })
79}
80
81fn extract_fragments(
83 doc: &Document<String>,
84) -> Result<Vec<crate::graphql::types::FragmentDefinition>, GraphQLParseError> {
85 let mut fragments = Vec::new();
86
87 for def in &doc.definitions {
88 if let Definition::Fragment(fragment) = def {
89 let selections = parse_selection_set(&fragment.selection_set)?;
90
91 let fragment_spreads = extract_fragment_spreads(&fragment.selection_set);
93
94 let type_condition = match &fragment.type_condition {
96 query::TypeCondition::On(type_name) => type_name.clone(),
97 };
98
99 fragments.push(crate::graphql::types::FragmentDefinition {
100 name: fragment.name.clone(),
101 type_condition,
102 selections,
103 fragment_spreads,
104 });
105 }
106 }
107
108 Ok(fragments)
109}
110
111fn extract_fragment_spreads(selection_set: &query::SelectionSet<String>) -> Vec<String> {
113 let mut spreads = Vec::new();
114
115 for selection in &selection_set.items {
116 match selection {
117 Selection::FragmentSpread(spread) => {
118 spreads.push(spread.fragment_name.clone());
119 },
120 Selection::InlineFragment(inline) => {
121 spreads.extend(extract_fragment_spreads(&inline.selection_set));
123 },
124 Selection::Field(field) => {
125 spreads.extend(extract_fragment_spreads(&field.selection_set));
127 },
128 }
129 }
130
131 spreads
132}
133
134fn extract_operation(
136 operation: &OperationDefinition<String>,
137) -> Result<
138 (String, Option<String>, String, Vec<FieldSelection>, Vec<VariableDefinition>),
139 GraphQLParseError,
140> {
141 let operation_type = match operation {
142 OperationDefinition::Query(_) | OperationDefinition::SelectionSet(_) => "query",
143 OperationDefinition::Mutation(_) => "mutation",
144 OperationDefinition::Subscription(_) => "subscription",
145 }
146 .to_string();
147
148 let (name, selection_set, var_defs) = match operation {
149 OperationDefinition::Query(q) => (&q.name, &q.selection_set, &q.variable_definitions),
150 OperationDefinition::Mutation(m) => (&m.name, &m.selection_set, &m.variable_definitions),
151 OperationDefinition::Subscription(s) => {
152 (&s.name, &s.selection_set, &s.variable_definitions)
153 },
154 OperationDefinition::SelectionSet(sel_set) => (&None, sel_set, &Vec::new()),
155 };
156
157 let selections = parse_selection_set(selection_set)?;
159
160 let root_field = selections
162 .first()
163 .map(|s| s.name.clone())
164 .ok_or(GraphQLParseError::EmptySelection)?;
165
166 let variables = var_defs
168 .iter()
169 .map(|var_def| VariableDefinition {
170 name: var_def.name.clone(),
171 var_type: parse_graphql_type(&var_def.var_type),
172 default_value: var_def.default_value.as_ref().map(|v| serialize_value(v)),
173 })
174 .collect();
175
176 Ok((operation_type, name.clone(), root_field, selections, variables))
177}
178
179fn parse_selection_set(
183 selection_set: &query::SelectionSet<String>,
184) -> Result<Vec<FieldSelection>, GraphQLParseError> {
185 let mut fields = Vec::new();
186
187 for selection in &selection_set.items {
188 match selection {
189 Selection::Field(field) => {
190 let arguments = field
192 .arguments
193 .iter()
194 .map(|(name, value)| GraphQLArgument {
195 name: name.clone(),
196 value_type: value_type_string(value),
197 value_json: serialize_value(value),
198 })
199 .collect();
200
201 let nested_fields = parse_selection_set(&field.selection_set)?;
203
204 let directives = field.directives.iter().map(parse_directive).collect();
205
206 fields.push(FieldSelection {
207 name: field.name.clone(),
208 alias: field.alias.clone(),
209 arguments,
210 nested_fields,
211 directives,
212 });
213 },
214 Selection::FragmentSpread(spread) => {
215 let directives = spread.directives.iter().map(parse_directive).collect();
218
219 fields.push(FieldSelection {
220 name: format!("...{}", spread.fragment_name),
221 alias: None,
222 arguments: vec![],
223 nested_fields: vec![],
224 directives,
225 });
226 },
227 Selection::InlineFragment(inline) => {
228 let type_condition =
231 inline.type_condition.as_ref().map_or_else(String::new, |tc| match tc {
232 query::TypeCondition::On(name) => name.clone(),
233 });
234
235 let nested_fields = parse_selection_set(&inline.selection_set)?;
236 let directives = inline.directives.iter().map(parse_directive).collect();
237
238 fields.push(FieldSelection {
239 name: format!("...on {type_condition}"),
240 alias: None,
241 arguments: vec![],
242 nested_fields,
243 directives,
244 });
245 },
246 }
247 }
248
249 Ok(fields)
250}
251
252fn value_type_string(value: &query::Value<String>) -> String {
254 match value {
255 query::Value::String(_) => "string".to_string(),
256 query::Value::Int(_) => "int".to_string(),
257 query::Value::Float(_) => "float".to_string(),
258 query::Value::Boolean(_) => "boolean".to_string(),
259 query::Value::Null => "null".to_string(),
260 query::Value::Enum(_) => "enum".to_string(),
261 query::Value::List(_) => "list".to_string(),
262 query::Value::Object(_) => "object".to_string(),
263 query::Value::Variable(_) => "variable".to_string(),
264 }
265}
266
267fn serialize_value(value: &query::Value<String>) -> String {
269 match value {
270 query::Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
271 query::Value::Int(i) => {
272 i.as_i64().map_or_else(|| "0".to_string(), |n| n.to_string())
274 },
275 query::Value::Float(f) => format!("{f}"),
276 query::Value::Boolean(b) => b.to_string(),
277 query::Value::Null => "null".to_string(),
278 query::Value::Enum(e) => format!("\"{e}\""),
279 query::Value::List(items) => {
280 let serialized: Vec<_> = items.iter().map(serialize_value).collect();
281 format!("[{}]", serialized.join(","))
282 },
283 query::Value::Object(obj) => {
284 let pairs: Vec<_> =
285 obj.iter().map(|(k, v)| format!("\"{}\":{}", k, serialize_value(v))).collect();
286 format!("{{{}}}", pairs.join(","))
287 },
288 query::Value::Variable(v) => format!("\"${v}\""),
289 }
290}
291
292fn parse_directive(directive: &GraphQLDirective<String>) -> Directive {
294 let arguments = directive
295 .arguments
296 .iter()
297 .map(|(name, value)| GraphQLArgument {
298 name: name.clone(),
299 value_type: value_type_string(value),
300 value_json: serialize_value(value),
301 })
302 .collect();
303
304 Directive {
305 name: directive.name.clone(),
306 arguments,
307 }
308}
309
310fn parse_graphql_type(graphql_type: &query::Type<String>) -> GraphQLType {
312 match graphql_type {
313 query::Type::NamedType(name) => GraphQLType {
314 name: name.clone(),
315 nullable: true, list: false,
317 list_nullable: false,
318 },
319 query::Type::ListType(inner) => GraphQLType {
320 name: format!("[{}]", parse_graphql_type(inner).name),
321 nullable: true,
322 list: true,
323 list_nullable: true, },
325 query::Type::NonNullType(inner) => {
326 let mut parsed = parse_graphql_type(inner);
327 parsed.nullable = false;
328 if parsed.list {
329 parsed.list_nullable = false;
330 }
331 parsed
332 },
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_parse_simple_query() {
342 let query = "query { users { id name } }";
343 let parsed = parse_query(query).unwrap();
344
345 assert_eq!(parsed.operation_type, "query");
346 assert_eq!(parsed.root_field, "users");
347 assert_eq!(parsed.selections.len(), 1);
348 assert_eq!(parsed.selections[0].nested_fields.len(), 2);
349 }
350
351 #[test]
352 fn test_parse_query_with_arguments() {
353 let query = r#"
354 query {
355 users(where: {status: "active"}, limit: 10) {
356 id
357 name
358 }
359 }
360 "#;
361 let parsed = parse_query(query).unwrap();
362
363 let first_field = &parsed.selections[0];
364 assert_eq!(first_field.arguments.len(), 2);
365 assert_eq!(first_field.arguments[0].name, "where");
366 assert_eq!(first_field.arguments[1].name, "limit");
367 }
368
369 #[test]
370 fn test_parse_mutation() {
371 let query = "mutation { createUser(input: {}) { id } }";
372 let parsed = parse_query(query).unwrap();
373
374 assert_eq!(parsed.operation_type, "mutation");
375 assert_eq!(parsed.root_field, "createUser");
376 }
377
378 #[test]
379 fn test_parse_query_with_variables() {
380 let query = r"
381 query GetUsers($where: UserWhere!) {
382 users(where: $where) {
383 id
384 }
385 }
386 ";
387 let parsed = parse_query(query).unwrap();
388
389 assert_eq!(parsed.variables.len(), 1);
390 assert_eq!(parsed.variables[0].name, "where");
391 }
392
393 #[test]
394 fn test_parse_query_with_integer_argument() {
395 let query = r"
396 query {
397 users(limit: 42, offset: 100) {
398 id
399 }
400 }
401 ";
402 let parsed = parse_query(query).unwrap();
403
404 let first_field = &parsed.selections[0];
405 assert_eq!(first_field.arguments.len(), 2);
406
407 assert_eq!(first_field.arguments[0].name, "limit");
408 assert_eq!(first_field.arguments[0].value_type, "int");
409 assert_eq!(first_field.arguments[0].value_json, "42");
410
411 assert_eq!(first_field.arguments[1].name, "offset");
412 assert_eq!(first_field.arguments[1].value_type, "int");
413 assert_eq!(first_field.arguments[1].value_json, "100");
414 }
415
416 #[test]
417 fn test_parse_query_with_fragment() {
418 let query = r"
419 fragment UserFields on User {
420 id
421 name
422 email
423 }
424
425 query {
426 users {
427 ...UserFields
428 }
429 }
430 ";
431 let parsed = parse_query(query).unwrap();
432
433 assert_eq!(parsed.fragments.len(), 1);
435 assert_eq!(parsed.fragments[0].name, "UserFields");
436 assert_eq!(parsed.fragments[0].type_condition, "User");
437 assert_eq!(parsed.fragments[0].selections.len(), 3);
438
439 assert_eq!(parsed.selections[0].nested_fields.len(), 1);
441 assert_eq!(parsed.selections[0].nested_fields[0].name, "...UserFields");
442 }
443
444 #[test]
445 fn test_parse_query_with_directives() {
446 let query = r"
447 query($skipEmail: Boolean!) {
448 users {
449 id
450 email @skip(if: $skipEmail)
451 name @include(if: true)
452 }
453 }
454 ";
455 let parsed = parse_query(query).unwrap();
456
457 let user_fields = &parsed.selections[0].nested_fields;
458 assert_eq!(user_fields.len(), 3);
459
460 assert!(user_fields[0].directives.is_empty());
462
463 assert_eq!(user_fields[1].directives.len(), 1);
465 assert_eq!(user_fields[1].directives[0].name, "skip");
466
467 assert_eq!(user_fields[2].directives.len(), 1);
469 assert_eq!(user_fields[2].directives[0].name, "include");
470 }
471
472 #[test]
473 fn test_parse_query_with_alias() {
474 let query = r"
475 query {
476 users {
477 id
478 writer: author {
479 name
480 }
481 }
482 }
483 ";
484 let parsed = parse_query(query).unwrap();
485
486 let user_fields = &parsed.selections[0].nested_fields;
487 assert_eq!(user_fields.len(), 2);
488
489 let aliased_field = &user_fields[1];
491 assert_eq!(aliased_field.name, "author");
492 assert_eq!(aliased_field.alias, Some("writer".to_string()));
493 }
494
495 #[test]
496 fn test_parse_inline_fragment() {
497 let query = r"
498 query {
499 users {
500 id
501 ... on Admin {
502 permissions
503 }
504 }
505 }
506 ";
507 let parsed = parse_query(query).unwrap();
508
509 let user_fields = &parsed.selections[0].nested_fields;
510 assert_eq!(user_fields.len(), 2);
511
512 assert_eq!(user_fields[1].name, "...on Admin");
514 assert_eq!(user_fields[1].nested_fields.len(), 1);
515 assert_eq!(user_fields[1].nested_fields[0].name, "permissions");
516 }
517}