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("Unknown operation named '{0}'")]
29 UnknownOperation(String),
30
31 #[error(
34 "Document defines {0} operations but no `operationName` was provided — the request must \
35 name the operation to execute"
36 )]
37 AmbiguousOperation(usize),
38
39 #[error("No fields in selection set")]
41 EmptySelection,
42
43 #[error("GraphQL value nesting exceeds maximum depth ({0} levels)")]
45 ValueNestingTooDeep(usize),
46}
47
48impl GraphQLParseError {
49 #[must_use]
58 pub const fn is_operation_selection(&self) -> bool {
59 matches!(self, Self::UnknownOperation(_) | Self::AmbiguousOperation(_))
60 }
61}
62
63pub fn parse_query(source: &str) -> Result<ParsedQuery, GraphQLParseError> {
82 parse_query_with_operation_name(source, None)
83}
84
85pub fn parse_query_with_operation_name(
110 source: &str,
111 operation_name: Option<&str>,
112) -> Result<ParsedQuery, GraphQLParseError> {
113 let doc: Document<String> = crate::graphql::complexity::parse_graphql_document(source)
116 .map_err(|e| GraphQLParseError::Syntax(e.to_string()))?;
117
118 let operation = select_operation(&doc, operation_name)?;
119 parse_selected_operation(operation, &doc, source)
120}
121
122fn select_operation<'d>(
124 doc: &'d Document<'d, String>,
125 operation_name: Option<&str>,
126) -> Result<&'d OperationDefinition<'d, String>, GraphQLParseError> {
127 let mut operations = doc.definitions.iter().filter_map(|def| match def {
128 query::Definition::Operation(op) => Some(op),
129 query::Definition::Fragment(_) => None,
130 });
131
132 let Some(requested) = operation_name else {
133 let first = operations.next().ok_or(GraphQLParseError::MissingOperation)?;
135 let extra = operations.count();
136 if extra > 0 {
137 return Err(GraphQLParseError::AmbiguousOperation(extra + 1));
138 }
139 return Ok(first);
140 };
141
142 let mut saw_any = false;
143 for op in operations {
144 saw_any = true;
145 if operation_definition_name(op).is_some_and(|name| name == requested) {
146 return Ok(op);
147 }
148 }
149
150 if saw_any {
151 Err(GraphQLParseError::UnknownOperation(requested.to_string()))
152 } else {
153 Err(GraphQLParseError::MissingOperation)
154 }
155}
156
157fn operation_definition_name<'a>(
162 operation: &'a OperationDefinition<'a, String>,
163) -> Option<&'a str> {
164 match operation {
165 OperationDefinition::Query(q) => q.name.as_deref(),
166 OperationDefinition::Mutation(m) => m.name.as_deref(),
167 OperationDefinition::Subscription(s) => s.name.as_deref(),
168 OperationDefinition::SelectionSet(_) => None,
169 }
170}
171
172pub fn parse_selected_operation(
186 operation: &OperationDefinition<String>,
187 doc: &Document<String>,
188 source: &str,
189) -> Result<ParsedQuery, GraphQLParseError> {
190 let (operation_type, operation_name, root_field, selections, variables) =
191 extract_operation(operation)?;
192
193 let fragments = extract_fragments(doc)?;
197
198 Ok(ParsedQuery {
199 operation_type,
200 operation_name,
201 root_field,
202 selections,
203 variables,
204 fragments,
205 source: std::sync::Arc::from(source),
210 })
211}
212
213fn extract_fragments(
215 doc: &Document<String>,
216) -> Result<Vec<crate::graphql::types::FragmentDefinition>, GraphQLParseError> {
217 let mut fragments = Vec::new();
218
219 for def in &doc.definitions {
220 if let Definition::Fragment(fragment) = def {
221 let selections = parse_selection_set(&fragment.selection_set)?;
222
223 let fragment_spreads = extract_fragment_spreads(&fragment.selection_set);
225
226 let type_condition = match &fragment.type_condition {
228 query::TypeCondition::On(type_name) => type_name.clone(),
229 };
230
231 fragments.push(crate::graphql::types::FragmentDefinition {
232 name: fragment.name.clone(),
233 type_condition,
234 selections,
235 fragment_spreads,
236 });
237 }
238 }
239
240 Ok(fragments)
241}
242
243fn extract_fragment_spreads(selection_set: &query::SelectionSet<String>) -> Vec<String> {
245 let mut spreads = Vec::new();
246
247 for selection in &selection_set.items {
248 match selection {
249 Selection::FragmentSpread(spread) => {
250 spreads.push(spread.fragment_name.clone());
251 },
252 Selection::InlineFragment(inline) => {
253 spreads.extend(extract_fragment_spreads(&inline.selection_set));
255 },
256 Selection::Field(field) => {
257 spreads.extend(extract_fragment_spreads(&field.selection_set));
259 },
260 }
261 }
262
263 spreads
264}
265
266fn extract_operation(
268 operation: &OperationDefinition<String>,
269) -> Result<
270 (String, Option<String>, String, Vec<FieldSelection>, Vec<VariableDefinition>),
271 GraphQLParseError,
272> {
273 let operation_type = match operation {
274 OperationDefinition::Query(_) | OperationDefinition::SelectionSet(_) => "query",
275 OperationDefinition::Mutation(_) => "mutation",
276 OperationDefinition::Subscription(_) => "subscription",
277 }
278 .to_string();
279
280 let (name, selection_set, var_defs) = match operation {
281 OperationDefinition::Query(q) => (&q.name, &q.selection_set, &q.variable_definitions),
282 OperationDefinition::Mutation(m) => (&m.name, &m.selection_set, &m.variable_definitions),
283 OperationDefinition::Subscription(s) => {
284 (&s.name, &s.selection_set, &s.variable_definitions)
285 },
286 OperationDefinition::SelectionSet(sel_set) => (&None, sel_set, &Vec::new()),
287 };
288
289 let selections = parse_selection_set(selection_set)?;
291
292 let root_field = selections
294 .first()
295 .map(|s| s.name.clone())
296 .ok_or(GraphQLParseError::EmptySelection)?;
297
298 let variables = var_defs
300 .iter()
301 .map(|var_def| VariableDefinition {
302 name: var_def.name.clone(),
303 var_type: parse_graphql_type(&var_def.var_type),
304 default_value: var_def.default_value.as_ref().map(|v| serialize_value(v)),
305 })
306 .collect();
307
308 Ok((operation_type, name.clone(), root_field, selections, variables))
309}
310
311fn parse_selection_set(
315 selection_set: &query::SelectionSet<String>,
316) -> Result<Vec<FieldSelection>, GraphQLParseError> {
317 let mut fields = Vec::new();
318
319 for selection in &selection_set.items {
320 match selection {
321 Selection::Field(field) => {
322 let arguments = field
324 .arguments
325 .iter()
326 .map(|(name, value)| GraphQLArgument {
327 name: name.clone(),
328 value_type: value_type_string(value),
329 value_json: serialize_value(value),
330 })
331 .collect();
332
333 let nested_fields = parse_selection_set(&field.selection_set)?;
335
336 let directives = field.directives.iter().map(parse_directive).collect();
337
338 fields.push(FieldSelection {
339 name: field.name.clone(),
340 alias: field.alias.clone(),
341 arguments,
342 nested_fields,
343 directives,
344 });
345 },
346 Selection::FragmentSpread(spread) => {
347 let directives = spread.directives.iter().map(parse_directive).collect();
350
351 fields.push(FieldSelection {
352 name: format!("...{}", spread.fragment_name),
353 alias: None,
354 arguments: vec![],
355 nested_fields: vec![],
356 directives,
357 });
358 },
359 Selection::InlineFragment(inline) => {
360 let type_condition =
363 inline.type_condition.as_ref().map_or_else(String::new, |tc| match tc {
364 query::TypeCondition::On(name) => name.clone(),
365 });
366
367 let nested_fields = parse_selection_set(&inline.selection_set)?;
368 let directives = inline.directives.iter().map(parse_directive).collect();
369
370 fields.push(FieldSelection {
371 name: format!("...on {type_condition}"),
372 alias: None,
373 arguments: vec![],
374 nested_fields,
375 directives,
376 });
377 },
378 }
379 }
380
381 Ok(fields)
382}
383
384fn value_type_string(value: &query::Value<String>) -> String {
386 match value {
387 query::Value::String(_) => "string".to_string(),
388 query::Value::Int(_) => "int".to_string(),
389 query::Value::Float(_) => "float".to_string(),
390 query::Value::Boolean(_) => "boolean".to_string(),
391 query::Value::Null => "null".to_string(),
392 query::Value::Enum(_) => "enum".to_string(),
393 query::Value::List(_) => "list".to_string(),
394 query::Value::Object(_) => "object".to_string(),
395 query::Value::Variable(_) => "variable".to_string(),
396 }
397}
398
399pub(crate) fn serialize_value(value: &query::Value<String>) -> String {
406 crate::graphql::value_json::encode(value).unwrap_or_else(|_| "null".to_string())
407}
408
409fn parse_directive(directive: &GraphQLDirective<String>) -> Directive {
411 let arguments = directive
412 .arguments
413 .iter()
414 .map(|(name, value)| GraphQLArgument {
415 name: name.clone(),
416 value_type: value_type_string(value),
417 value_json: serialize_value(value),
418 })
419 .collect();
420
421 Directive {
422 name: directive.name.clone(),
423 arguments,
424 }
425}
426
427fn parse_graphql_type(graphql_type: &query::Type<String>) -> GraphQLType {
429 match graphql_type {
430 query::Type::NamedType(name) => GraphQLType {
431 name: name.clone(),
432 nullable: true, list: false,
434 list_nullable: false,
435 },
436 query::Type::ListType(inner) => GraphQLType {
437 name: format!("[{}]", parse_graphql_type(inner).name),
438 nullable: true,
439 list: true,
440 list_nullable: true, },
442 query::Type::NonNullType(inner) => {
443 let mut parsed = parse_graphql_type(inner);
444 parsed.nullable = false;
445 if parsed.list {
446 parsed.list_nullable = false;
447 }
448 parsed
449 },
450 }
451}
452
453#[must_use]
459pub fn operation_selection_error(e: &GraphQLParseError) -> crate::error::FraiseQLError {
460 if e.is_operation_selection() {
461 crate::error::FraiseQLError::Validation {
462 message: e.to_string(),
463 path: Some("operationName".to_string()),
464 }
465 } else {
466 crate::error::FraiseQLError::Parse {
467 message: e.to_string(),
468 location: "query".to_string(),
469 }
470 }
471}