use graphql_parser::query::{
self, Definition, Directive as GraphQLDirective, Document, OperationDefinition, Selection,
};
use crate::graphql::types::{
Directive, FieldSelection, GraphQLArgument, GraphQLType, ParsedQuery, VariableDefinition,
};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum GraphQLParseError {
#[error("Failed to parse GraphQL query: {0}")]
Syntax(String),
#[error("No query or mutation operation found")]
MissingOperation,
#[error("No fields in selection set")]
EmptySelection,
#[error("GraphQL value nesting exceeds maximum depth ({0} levels)")]
ValueNestingTooDeep(usize),
}
pub(crate) const MAX_SERIALIZE_DEPTH: usize = 64;
pub fn parse_query(source: &str) -> Result<ParsedQuery, GraphQLParseError> {
let doc: Document<String> =
query::parse_query(source).map_err(|e| GraphQLParseError::Syntax(e.to_string()))?;
let operation = doc
.definitions
.iter()
.find_map(|def| match def {
query::Definition::Operation(op) => Some(op),
query::Definition::Fragment(_) => None,
})
.ok_or(GraphQLParseError::MissingOperation)?;
let (operation_type, operation_name, root_field, selections, variables) =
extract_operation(operation)?;
let fragments = extract_fragments(&doc)?;
Ok(ParsedQuery {
operation_type,
operation_name,
root_field,
selections,
variables,
fragments,
source: std::sync::Arc::from(source),
})
}
fn extract_fragments(
doc: &Document<String>,
) -> Result<Vec<crate::graphql::types::FragmentDefinition>, GraphQLParseError> {
let mut fragments = Vec::new();
for def in &doc.definitions {
if let Definition::Fragment(fragment) = def {
let selections = parse_selection_set(&fragment.selection_set)?;
let fragment_spreads = extract_fragment_spreads(&fragment.selection_set);
let type_condition = match &fragment.type_condition {
query::TypeCondition::On(type_name) => type_name.clone(),
};
fragments.push(crate::graphql::types::FragmentDefinition {
name: fragment.name.clone(),
type_condition,
selections,
fragment_spreads,
});
}
}
Ok(fragments)
}
fn extract_fragment_spreads(selection_set: &query::SelectionSet<String>) -> Vec<String> {
let mut spreads = Vec::new();
for selection in &selection_set.items {
match selection {
Selection::FragmentSpread(spread) => {
spreads.push(spread.fragment_name.clone());
},
Selection::InlineFragment(inline) => {
spreads.extend(extract_fragment_spreads(&inline.selection_set));
},
Selection::Field(field) => {
spreads.extend(extract_fragment_spreads(&field.selection_set));
},
}
}
spreads
}
fn extract_operation(
operation: &OperationDefinition<String>,
) -> Result<
(String, Option<String>, String, Vec<FieldSelection>, Vec<VariableDefinition>),
GraphQLParseError,
> {
let operation_type = match operation {
OperationDefinition::Query(_) | OperationDefinition::SelectionSet(_) => "query",
OperationDefinition::Mutation(_) => "mutation",
OperationDefinition::Subscription(_) => "subscription",
}
.to_string();
let (name, selection_set, var_defs) = match operation {
OperationDefinition::Query(q) => (&q.name, &q.selection_set, &q.variable_definitions),
OperationDefinition::Mutation(m) => (&m.name, &m.selection_set, &m.variable_definitions),
OperationDefinition::Subscription(s) => {
(&s.name, &s.selection_set, &s.variable_definitions)
},
OperationDefinition::SelectionSet(sel_set) => (&None, sel_set, &Vec::new()),
};
let selections = parse_selection_set(selection_set)?;
let root_field = selections
.first()
.map(|s| s.name.clone())
.ok_or(GraphQLParseError::EmptySelection)?;
let variables = var_defs
.iter()
.map(|var_def| VariableDefinition {
name: var_def.name.clone(),
var_type: parse_graphql_type(&var_def.var_type),
default_value: var_def.default_value.as_ref().map(|v| serialize_value(v)),
})
.collect();
Ok((operation_type, name.clone(), root_field, selections, variables))
}
fn parse_selection_set(
selection_set: &query::SelectionSet<String>,
) -> Result<Vec<FieldSelection>, GraphQLParseError> {
let mut fields = Vec::new();
for selection in &selection_set.items {
match selection {
Selection::Field(field) => {
let arguments = field
.arguments
.iter()
.map(|(name, value)| GraphQLArgument {
name: name.clone(),
value_type: value_type_string(value),
value_json: serialize_value(value),
})
.collect();
let nested_fields = parse_selection_set(&field.selection_set)?;
let directives = field.directives.iter().map(parse_directive).collect();
fields.push(FieldSelection {
name: field.name.clone(),
alias: field.alias.clone(),
arguments,
nested_fields,
directives,
});
},
Selection::FragmentSpread(spread) => {
let directives = spread.directives.iter().map(parse_directive).collect();
fields.push(FieldSelection {
name: format!("...{}", spread.fragment_name),
alias: None,
arguments: vec![],
nested_fields: vec![],
directives,
});
},
Selection::InlineFragment(inline) => {
let type_condition =
inline.type_condition.as_ref().map_or_else(String::new, |tc| match tc {
query::TypeCondition::On(name) => name.clone(),
});
let nested_fields = parse_selection_set(&inline.selection_set)?;
let directives = inline.directives.iter().map(parse_directive).collect();
fields.push(FieldSelection {
name: format!("...on {type_condition}"),
alias: None,
arguments: vec![],
nested_fields,
directives,
});
},
}
}
Ok(fields)
}
fn value_type_string(value: &query::Value<String>) -> String {
match value {
query::Value::String(_) => "string".to_string(),
query::Value::Int(_) => "int".to_string(),
query::Value::Float(_) => "float".to_string(),
query::Value::Boolean(_) => "boolean".to_string(),
query::Value::Null => "null".to_string(),
query::Value::Enum(_) => "enum".to_string(),
query::Value::List(_) => "list".to_string(),
query::Value::Object(_) => "object".to_string(),
query::Value::Variable(_) => "variable".to_string(),
}
}
fn serialize_value_inner(value: &query::Value<String>, depth: usize) -> Option<String> {
if depth > MAX_SERIALIZE_DEPTH {
return None;
}
let s = match value {
query::Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
query::Value::Int(i) => {
i.as_i64().map_or_else(|| "0".to_string(), |n| n.to_string())
},
query::Value::Float(f) => format!("{f}"),
query::Value::Boolean(b) => b.to_string(),
query::Value::Null => "null".to_string(),
query::Value::Enum(e) => format!("\"{e}\""),
query::Value::List(items) => {
let mut parts = Vec::with_capacity(items.len());
for item in items {
parts.push(serialize_value_inner(item, depth + 1)?);
}
format!("[{}]", parts.join(","))
},
query::Value::Object(obj) => {
let mut pairs = Vec::with_capacity(obj.len());
for (k, v) in obj {
let serialized = serialize_value_inner(v, depth + 1)?;
pairs.push(format!("\"{}\":{serialized}", k));
}
format!("{{{}}}", pairs.join(","))
},
query::Value::Variable(v) => format!("\"${v}\""),
};
Some(s)
}
pub(crate) fn serialize_value(value: &query::Value<String>) -> String {
serialize_value_inner(value, 0).unwrap_or_else(|| "null".to_string())
}
fn parse_directive(directive: &GraphQLDirective<String>) -> Directive {
let arguments = directive
.arguments
.iter()
.map(|(name, value)| GraphQLArgument {
name: name.clone(),
value_type: value_type_string(value),
value_json: serialize_value(value),
})
.collect();
Directive {
name: directive.name.clone(),
arguments,
}
}
fn parse_graphql_type(graphql_type: &query::Type<String>) -> GraphQLType {
match graphql_type {
query::Type::NamedType(name) => GraphQLType {
name: name.clone(),
nullable: true, list: false,
list_nullable: false,
},
query::Type::ListType(inner) => GraphQLType {
name: format!("[{}]", parse_graphql_type(inner).name),
nullable: true,
list: true,
list_nullable: true, },
query::Type::NonNullType(inner) => {
let mut parsed = parse_graphql_type(inner);
parsed.nullable = false;
if parsed.list {
parsed.list_nullable = false;
}
parsed
},
}
}