use serde_json::Value;
use super::{
super::{ArgumentDefinition, CompiledSchema, InputFieldDefinition},
field_resolver::arg_type_ref,
type_resolver::input_field_type_ref,
types::{IntrospectionType, TypeKind},
};
pub fn input_field_default(
field: &InputFieldDefinition,
schema: &CompiledSchema,
) -> Result<Option<String>, String> {
let Some(text) = field.default_value.as_deref() else {
return Ok(None);
};
let value: Value =
serde_json::from_str(text).map_err(|e| format!("`{text}` is not JSON text: {e}"))?;
literal(&value, &input_field_type_ref(field, schema), schema).map(Some)
}
pub fn argument_default(
arg: &ArgumentDefinition,
schema: &CompiledSchema,
) -> Result<Option<String>, String> {
arg.default_value
.as_ref()
.map(|v| literal(&v.to_json(), &arg_type_ref(arg), schema))
.transpose()
}
fn literal(
value: &Value,
ty: &IntrospectionType,
schema: &CompiledSchema,
) -> Result<String, String> {
let inner = || {
ty.of_type
.as_deref()
.ok_or_else(|| "a wrapper type names no inner type".to_string())
};
match ty.kind {
TypeKind::NonNull if value.is_null() => {
Err("`null` is not a value of a non-null type".to_string())
},
TypeKind::NonNull => literal(value, inner()?, schema),
TypeKind::List => match value {
Value::Null => Ok("null".to_string()),
Value::Array(items) => {
let item_type = inner()?;
let items = items
.iter()
.map(|v| literal(v, item_type, schema))
.collect::<Result<Vec<_>, _>>()?;
Ok(format!("[{}]", items.join(", ")))
},
one => literal(one, inner()?, schema),
},
_ => named(value, ty.name.as_deref().unwrap_or_default(), schema),
}
}
fn named(value: &Value, name: &str, schema: &CompiledSchema) -> Result<String, String> {
if value.is_null() {
return Ok("null".to_string());
}
if let Some(enum_def) = schema.find_enum(name) {
return match value {
Value::String(v) if enum_def.values.iter().any(|e| &e.name == v) => Ok(v.clone()),
Value::String(v) => Err(format!("`{v}` is not a value of enum `{name}`")),
other => Err(format!("enum `{name}` takes a value name, not `{other}`")),
};
}
if let Some(input) = schema.find_input_type(name) {
let Value::Object(entries) = value else {
return Err(format!("input `{name}` takes an object, not `{value}`"));
};
let fields = entries
.iter()
.map(|(key, v)| {
let field = input
.fields
.iter()
.find(|f| &f.name == key)
.ok_or_else(|| format!("`{key}` is not a field of input `{name}`"))?;
Ok(format!("{key}: {}", literal(v, &input_field_type_ref(field, schema), schema)?))
})
.collect::<Result<Vec<_>, String>>()?;
return Ok(format!("{{{}}}", fields.join(", ")));
}
scalar(value)
}
fn scalar(value: &Value) -> Result<String, String> {
match value {
Value::Null => Ok("null".to_string()),
Value::Bool(b) => Ok(b.to_string()),
Value::Number(n) => Ok(n.to_string()),
Value::String(s) => serde_json::to_string(s).map_err(|e| e.to_string()),
Value::Array(items) => {
let items = items.iter().map(scalar).collect::<Result<Vec<_>, _>>()?;
Ok(format!("[{}]", items.join(", ")))
},
Value::Object(entries) => {
let fields = entries
.iter()
.map(|(key, v)| {
if !is_name(key) {
return Err(format!(
"key `{key}` is not a GraphQL name, so it has no literal"
));
}
Ok(format!("{key}: {}", scalar(v)?))
})
.collect::<Result<Vec<_>, String>>()?;
Ok(format!("{{{}}}", fields.join(", ")))
},
}
}
fn is_name(s: &str) -> bool {
let mut chars = s.chars();
chars.next().is_some_and(|c| c == '_' || c.is_ascii_alphabetic())
&& chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}