use anyhow::bail;
use fraiseql_core::schema::{FieldType, TypeDefinition};
enum IdDefect {
WrongType(String),
Missing,
}
fn id_defect(ty: &TypeDefinition) -> Option<IdDefect> {
match ty.fields.iter().find(|f| f.name == "id") {
Some(f) if f.field_type == FieldType::Id => None,
Some(f) => Some(IdDefect::WrongType(f.field_type.to_graphql_string())),
None => Some(IdDefect::Missing),
}
}
pub(super) fn enforce_node_id_conformance<'a>(
entities: impl Iterator<Item = &'a TypeDefinition>,
lead: &str,
remedy: &str,
annotate: impl Fn(&TypeDefinition) -> Option<String>,
) -> anyhow::Result<()> {
let offenders: Vec<String> = entities
.filter_map(|ty| id_defect(ty).map(|defect| (ty, defect)))
.map(|(ty, defect)| {
let name = ty.name.to_string();
let mut line = match defect {
IdDefect::WrongType(actual) => {
format!(" - Type '{name}': `id` is {actual}, not ID")
},
IdDefect::Missing => format!(" - Type '{name}': no `id` field"),
};
if let Some(note) = annotate(ty) {
line.push_str("\n ");
line.push_str(¬e);
}
line
})
.collect();
if offenders.is_empty() {
return Ok(());
}
bail!(
"{lead}:\n{}\nFix: expose `id: ID!` on each type, or {remedy}.",
offenders.join("\n"),
);
}