use std::collections::HashMap;
use serde_json::Value;
use crate::schema::{ArgumentDefinition, CompiledSchema, EnumDefinition, FieldType};
const MAX_COERCION_DEPTH: usize = 64;
fn has_stored_values(schema: &CompiledSchema) -> bool {
schema.enums.iter().any(EnumDefinition::has_stored_values)
}
pub fn coerce_enum_arguments(
schema: &CompiledSchema,
declared: &[ArgumentDefinition],
arguments: &mut HashMap<String, Value>,
) {
if !has_stored_values(schema) {
return;
}
for arg in declared {
if let Some(value) = arguments.get_mut(&arg.name) {
coerce_field_type(schema, &arg.arg_type, value, 0);
}
}
}
#[must_use]
pub fn coerce_enum_argument_object(
schema: &CompiledSchema,
declared: &[ArgumentDefinition],
arguments: Option<&Value>,
) -> Option<Value> {
if !has_stored_values(schema) {
return None;
}
let mut arguments = arguments.filter(|a| a.is_object())?.clone();
if let Some(map) = arguments.as_object_mut() {
for arg in declared {
if let Some(value) = map.get_mut(&arg.name) {
coerce_field_type(schema, &arg.arg_type, value, 0);
}
}
}
Some(arguments)
}
fn coerce_field_type(
schema: &CompiledSchema,
declared: &FieldType,
value: &mut Value,
depth: usize,
) {
if value.is_null() || depth > MAX_COERCION_DEPTH {
return;
}
match declared {
FieldType::List { item: inner, .. } => {
each_item(value, |item| coerce_field_type(schema, inner, item, depth + 1));
},
FieldType::Enum(name) => coerce_member(schema, name, value),
FieldType::Object(name) | FieldType::Input(name) => {
coerce_input_object(schema, name, value, depth);
},
_ => {},
}
}
fn coerce_type_ref(schema: &CompiledSchema, declared: &str, value: &mut Value, depth: usize) {
if depth > MAX_COERCION_DEPTH {
return;
}
let declared = declared.trim();
if let Some(inner) = declared.strip_suffix('!') {
return coerce_type_ref(schema, inner, value, depth);
}
if let Some(inner) = declared.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
return each_item(value, |item| coerce_type_ref(schema, inner, item, depth + 1));
}
if value.is_null() {
return;
}
if schema.find_enum(declared).is_some() {
return coerce_member(schema, declared, value);
}
coerce_input_object(schema, declared, value, depth);
}
fn each_item(value: &mut Value, mut coerce: impl FnMut(&mut Value)) {
match value.as_array_mut() {
Some(items) => items.iter_mut().for_each(coerce),
None => coerce(value),
}
}
fn coerce_input_object(schema: &CompiledSchema, name: &str, value: &mut Value, depth: usize) {
let (Some(input_type), Some(object)) = (schema.find_input_type(name), value.as_object_mut())
else {
return;
};
for field in &input_type.fields {
let surface = schema.display_name(&field.name);
for key in [surface.as_str(), field.name.as_str()] {
if let Some(value) = object.get_mut(key) {
coerce_type_ref(schema, &field.field_type, value, depth + 1);
}
if surface == field.name {
break;
}
}
}
}
fn coerce_member(schema: &CompiledSchema, enum_name: &str, value: &mut Value) {
let Some(def) = schema.find_enum(enum_name) else {
return;
};
let Some(stored) = value.as_str().and_then(|name| def.stored_value_of(name)) else {
return;
};
*value = stored;
}
#[cfg(test)]
#[path = "enum_coercion_tests.rs"]
mod enum_coercion_tests;