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)]
16#[non_exhaustive]
17pub enum GraphQLParseError {
18 #[error("Failed to parse GraphQL query: {0}")]
20 Syntax(String),
21
22 #[error("No query or mutation operation found")]
24 MissingOperation,
25
26 #[error("No fields in selection set")]
28 EmptySelection,
29
30 #[error("GraphQL value nesting exceeds maximum depth ({0} levels)")]
32 ValueNestingTooDeep(usize),
33}
34
35const MAX_SERIALIZE_DEPTH: usize = 64;
41
42pub fn parse_query(source: &str) -> Result<ParsedQuery, GraphQLParseError> {
61 let doc: Document<String> =
63 query::parse_query(source).map_err(|e| GraphQLParseError::Syntax(e.to_string()))?;
64
65 let operation = doc
67 .definitions
68 .iter()
69 .find_map(|def| match def {
70 query::Definition::Operation(op) => Some(op),
71 query::Definition::Fragment(_) => None,
72 })
73 .ok_or(GraphQLParseError::MissingOperation)?;
74
75 let (operation_type, operation_name, root_field, selections, variables) =
77 extract_operation(operation)?;
78
79 let fragments = extract_fragments(&doc)?;
81
82 Ok(ParsedQuery {
83 operation_type,
84 operation_name,
85 root_field,
86 selections,
87 variables,
88 fragments,
89 source: source.to_string(),
90 })
91}
92
93fn extract_fragments(
95 doc: &Document<String>,
96) -> Result<Vec<crate::graphql::types::FragmentDefinition>, GraphQLParseError> {
97 let mut fragments = Vec::new();
98
99 for def in &doc.definitions {
100 if let Definition::Fragment(fragment) = def {
101 let selections = parse_selection_set(&fragment.selection_set)?;
102
103 let fragment_spreads = extract_fragment_spreads(&fragment.selection_set);
105
106 let type_condition = match &fragment.type_condition {
108 query::TypeCondition::On(type_name) => type_name.clone(),
109 };
110
111 fragments.push(crate::graphql::types::FragmentDefinition {
112 name: fragment.name.clone(),
113 type_condition,
114 selections,
115 fragment_spreads,
116 });
117 }
118 }
119
120 Ok(fragments)
121}
122
123fn extract_fragment_spreads(selection_set: &query::SelectionSet<String>) -> Vec<String> {
125 let mut spreads = Vec::new();
126
127 for selection in &selection_set.items {
128 match selection {
129 Selection::FragmentSpread(spread) => {
130 spreads.push(spread.fragment_name.clone());
131 },
132 Selection::InlineFragment(inline) => {
133 spreads.extend(extract_fragment_spreads(&inline.selection_set));
135 },
136 Selection::Field(field) => {
137 spreads.extend(extract_fragment_spreads(&field.selection_set));
139 },
140 }
141 }
142
143 spreads
144}
145
146fn extract_operation(
148 operation: &OperationDefinition<String>,
149) -> Result<
150 (String, Option<String>, String, Vec<FieldSelection>, Vec<VariableDefinition>),
151 GraphQLParseError,
152> {
153 let operation_type = match operation {
154 OperationDefinition::Query(_) | OperationDefinition::SelectionSet(_) => "query",
155 OperationDefinition::Mutation(_) => "mutation",
156 OperationDefinition::Subscription(_) => "subscription",
157 }
158 .to_string();
159
160 let (name, selection_set, var_defs) = match operation {
161 OperationDefinition::Query(q) => (&q.name, &q.selection_set, &q.variable_definitions),
162 OperationDefinition::Mutation(m) => (&m.name, &m.selection_set, &m.variable_definitions),
163 OperationDefinition::Subscription(s) => {
164 (&s.name, &s.selection_set, &s.variable_definitions)
165 },
166 OperationDefinition::SelectionSet(sel_set) => (&None, sel_set, &Vec::new()),
167 };
168
169 let selections = parse_selection_set(selection_set)?;
171
172 let root_field = selections
174 .first()
175 .map(|s| s.name.clone())
176 .ok_or(GraphQLParseError::EmptySelection)?;
177
178 let variables = var_defs
180 .iter()
181 .map(|var_def| VariableDefinition {
182 name: var_def.name.clone(),
183 var_type: parse_graphql_type(&var_def.var_type),
184 default_value: var_def.default_value.as_ref().map(|v| serialize_value(v)),
185 })
186 .collect();
187
188 Ok((operation_type, name.clone(), root_field, selections, variables))
189}
190
191fn parse_selection_set(
195 selection_set: &query::SelectionSet<String>,
196) -> Result<Vec<FieldSelection>, GraphQLParseError> {
197 let mut fields = Vec::new();
198
199 for selection in &selection_set.items {
200 match selection {
201 Selection::Field(field) => {
202 let arguments = field
204 .arguments
205 .iter()
206 .map(|(name, value)| GraphQLArgument {
207 name: name.clone(),
208 value_type: value_type_string(value),
209 value_json: serialize_value(value),
210 })
211 .collect();
212
213 let nested_fields = parse_selection_set(&field.selection_set)?;
215
216 let directives = field.directives.iter().map(parse_directive).collect();
217
218 fields.push(FieldSelection {
219 name: field.name.clone(),
220 alias: field.alias.clone(),
221 arguments,
222 nested_fields,
223 directives,
224 });
225 },
226 Selection::FragmentSpread(spread) => {
227 let directives = spread.directives.iter().map(parse_directive).collect();
230
231 fields.push(FieldSelection {
232 name: format!("...{}", spread.fragment_name),
233 alias: None,
234 arguments: vec![],
235 nested_fields: vec![],
236 directives,
237 });
238 },
239 Selection::InlineFragment(inline) => {
240 let type_condition =
243 inline.type_condition.as_ref().map_or_else(String::new, |tc| match tc {
244 query::TypeCondition::On(name) => name.clone(),
245 });
246
247 let nested_fields = parse_selection_set(&inline.selection_set)?;
248 let directives = inline.directives.iter().map(parse_directive).collect();
249
250 fields.push(FieldSelection {
251 name: format!("...on {type_condition}"),
252 alias: None,
253 arguments: vec![],
254 nested_fields,
255 directives,
256 });
257 },
258 }
259 }
260
261 Ok(fields)
262}
263
264fn value_type_string(value: &query::Value<String>) -> String {
266 match value {
267 query::Value::String(_) => "string".to_string(),
268 query::Value::Int(_) => "int".to_string(),
269 query::Value::Float(_) => "float".to_string(),
270 query::Value::Boolean(_) => "boolean".to_string(),
271 query::Value::Null => "null".to_string(),
272 query::Value::Enum(_) => "enum".to_string(),
273 query::Value::List(_) => "list".to_string(),
274 query::Value::Object(_) => "object".to_string(),
275 query::Value::Variable(_) => "variable".to_string(),
276 }
277}
278
279fn serialize_value_inner(value: &query::Value<String>, depth: usize) -> Option<String> {
285 if depth > MAX_SERIALIZE_DEPTH {
286 return None;
287 }
288
289 let s = match value {
290 query::Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
291 query::Value::Int(i) => {
292 i.as_i64().map_or_else(|| "0".to_string(), |n| n.to_string())
294 },
295 query::Value::Float(f) => format!("{f}"),
296 query::Value::Boolean(b) => b.to_string(),
297 query::Value::Null => "null".to_string(),
298 query::Value::Enum(e) => format!("\"{e}\""),
299 query::Value::List(items) => {
300 let mut parts = Vec::with_capacity(items.len());
301 for item in items {
302 parts.push(serialize_value_inner(item, depth + 1)?);
303 }
304 format!("[{}]", parts.join(","))
305 },
306 query::Value::Object(obj) => {
307 let mut pairs = Vec::with_capacity(obj.len());
308 for (k, v) in obj {
309 let serialized = serialize_value_inner(v, depth + 1)?;
310 pairs.push(format!("\"{}\":{serialized}", k));
311 }
312 format!("{{{}}}", pairs.join(","))
313 },
314 query::Value::Variable(v) => format!("\"${v}\""),
315 };
316
317 Some(s)
318}
319
320fn serialize_value(value: &query::Value<String>) -> String {
325 serialize_value_inner(value, 0).unwrap_or_else(|| "null".to_string())
326}
327
328fn parse_directive(directive: &GraphQLDirective<String>) -> Directive {
330 let arguments = directive
331 .arguments
332 .iter()
333 .map(|(name, value)| GraphQLArgument {
334 name: name.clone(),
335 value_type: value_type_string(value),
336 value_json: serialize_value(value),
337 })
338 .collect();
339
340 Directive {
341 name: directive.name.clone(),
342 arguments,
343 }
344}
345
346fn parse_graphql_type(graphql_type: &query::Type<String>) -> GraphQLType {
348 match graphql_type {
349 query::Type::NamedType(name) => GraphQLType {
350 name: name.clone(),
351 nullable: true, list: false,
353 list_nullable: false,
354 },
355 query::Type::ListType(inner) => GraphQLType {
356 name: format!("[{}]", parse_graphql_type(inner).name),
357 nullable: true,
358 list: true,
359 list_nullable: true, },
361 query::Type::NonNullType(inner) => {
362 let mut parsed = parse_graphql_type(inner);
363 parsed.nullable = false;
364 if parsed.list {
365 parsed.list_nullable = false;
366 }
367 parsed
368 },
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 #![allow(clippy::unwrap_used)] use super::*;
377
378 #[test]
379 fn test_parse_simple_query() {
380 let query = "query { users { id name } }";
381 let parsed = parse_query(query).unwrap();
382
383 assert_eq!(parsed.operation_type, "query");
384 assert_eq!(parsed.root_field, "users");
385 assert_eq!(parsed.selections.len(), 1);
386 assert_eq!(parsed.selections[0].nested_fields.len(), 2);
387 }
388
389 #[test]
390 fn test_parse_query_with_arguments() {
391 let query = r#"
392 query {
393 users(where: {status: "active"}, limit: 10) {
394 id
395 name
396 }
397 }
398 "#;
399 let parsed = parse_query(query).unwrap();
400
401 let first_field = &parsed.selections[0];
402 assert_eq!(first_field.arguments.len(), 2);
403 assert_eq!(first_field.arguments[0].name, "where");
404 assert_eq!(first_field.arguments[1].name, "limit");
405 }
406
407 #[test]
408 fn test_parse_mutation() {
409 let query = "mutation { createUser(input: {}) { id } }";
410 let parsed = parse_query(query).unwrap();
411
412 assert_eq!(parsed.operation_type, "mutation");
413 assert_eq!(parsed.root_field, "createUser");
414 }
415
416 #[test]
417 fn test_parse_query_with_variables() {
418 let query = r"
419 query GetUsers($where: UserWhere!) {
420 users(where: $where) {
421 id
422 }
423 }
424 ";
425 let parsed = parse_query(query).unwrap();
426
427 assert_eq!(parsed.variables.len(), 1);
428 assert_eq!(parsed.variables[0].name, "where");
429 }
430
431 #[test]
432 fn test_parse_query_with_integer_argument() {
433 let query = r"
434 query {
435 users(limit: 42, offset: 100) {
436 id
437 }
438 }
439 ";
440 let parsed = parse_query(query).unwrap();
441
442 let first_field = &parsed.selections[0];
443 assert_eq!(first_field.arguments.len(), 2);
444
445 assert_eq!(first_field.arguments[0].name, "limit");
446 assert_eq!(first_field.arguments[0].value_type, "int");
447 assert_eq!(first_field.arguments[0].value_json, "42");
448
449 assert_eq!(first_field.arguments[1].name, "offset");
450 assert_eq!(first_field.arguments[1].value_type, "int");
451 assert_eq!(first_field.arguments[1].value_json, "100");
452 }
453
454 #[test]
455 fn test_parse_query_with_fragment() {
456 let query = r"
457 fragment UserFields on User {
458 id
459 name
460 email
461 }
462
463 query {
464 users {
465 ...UserFields
466 }
467 }
468 ";
469 let parsed = parse_query(query).unwrap();
470
471 assert_eq!(parsed.fragments.len(), 1);
473 assert_eq!(parsed.fragments[0].name, "UserFields");
474 assert_eq!(parsed.fragments[0].type_condition, "User");
475 assert_eq!(parsed.fragments[0].selections.len(), 3);
476
477 assert_eq!(parsed.selections[0].nested_fields.len(), 1);
479 assert_eq!(parsed.selections[0].nested_fields[0].name, "...UserFields");
480 }
481
482 #[test]
483 fn test_parse_query_with_directives() {
484 let query = r"
485 query($skipEmail: Boolean!) {
486 users {
487 id
488 email @skip(if: $skipEmail)
489 name @include(if: true)
490 }
491 }
492 ";
493 let parsed = parse_query(query).unwrap();
494
495 let user_fields = &parsed.selections[0].nested_fields;
496 assert_eq!(user_fields.len(), 3);
497
498 assert!(user_fields[0].directives.is_empty());
500
501 assert_eq!(user_fields[1].directives.len(), 1);
503 assert_eq!(user_fields[1].directives[0].name, "skip");
504
505 assert_eq!(user_fields[2].directives.len(), 1);
507 assert_eq!(user_fields[2].directives[0].name, "include");
508 }
509
510 #[test]
511 fn test_parse_query_with_alias() {
512 let query = r"
513 query {
514 users {
515 id
516 writer: author {
517 name
518 }
519 }
520 }
521 ";
522 let parsed = parse_query(query).unwrap();
523
524 let user_fields = &parsed.selections[0].nested_fields;
525 assert_eq!(user_fields.len(), 2);
526
527 let aliased_field = &user_fields[1];
529 assert_eq!(aliased_field.name, "author");
530 assert_eq!(aliased_field.alias, Some("writer".to_string()));
531 }
532
533 #[test]
534 fn test_parse_inline_fragment() {
535 let query = r"
536 query {
537 users {
538 id
539 ... on Admin {
540 permissions
541 }
542 }
543 }
544 ";
545 let parsed = parse_query(query).unwrap();
546
547 let user_fields = &parsed.selections[0].nested_fields;
548 assert_eq!(user_fields.len(), 2);
549
550 assert_eq!(user_fields[1].name, "...on Admin");
552 assert_eq!(user_fields[1].nested_fields.len(), 1);
553 assert_eq!(user_fields[1].nested_fields[0].name, "permissions");
554 }
555
556 #[test]
559 fn test_serialize_value_flat_list_accepted() {
560 let value = query::Value::List(vec![
562 query::Value::Int(graphql_parser::query::Number::from(1_i32)),
563 query::Value::String("hello".to_string()),
564 query::Value::Boolean(true),
565 ]);
566 let result = serialize_value(&value);
567 assert_eq!(result, r#"[1,"hello",true]"#);
568 }
569
570 #[test]
571 fn test_serialize_value_nested_at_limit_accepted() {
572 let mut v: query::Value<String> = query::Value::Boolean(true);
574 for _ in 0..MAX_SERIALIZE_DEPTH {
575 v = query::Value::List(vec![v]);
576 }
577 let result = serialize_value(&v);
578 assert!(result.contains("true"), "value at limit should serialize correctly: {result}");
580 }
581
582 #[test]
583 fn test_serialize_value_exceeds_depth_returns_null() {
584 let mut v: query::Value<String> = query::Value::Boolean(true);
586 for _ in 0..=MAX_SERIALIZE_DEPTH {
587 v = query::Value::List(vec![v]);
588 }
589 let result = serialize_value(&v);
590 assert_eq!(result, "null", "over-limit value must fall back to null: {result}");
591 }
592
593 #[test]
594 fn test_serialize_value_deeply_nested_object_returns_null() {
595 let mut v: query::Value<String> = query::Value::Boolean(false);
597 for i in 0..=MAX_SERIALIZE_DEPTH {
598 let mut map = std::collections::BTreeMap::new();
599 map.insert(format!("k{i}"), v);
600 v = query::Value::Object(map);
601 }
602 let result = serialize_value(&v);
603 assert_eq!(result, "null", "over-limit object must fall back to null: {result}");
604 }
605}