use serde_json::Value;
const DRIFTED_INJECT_KEYS: &[(&str, &str)] = &[("inject", "inject_params")];
const DRIFTED_SHAPE_KEYS: &[(&str, &str)] = &[("return_array", "returns_list")];
const DRIFTED_SCOPE_KEYS: &[&str] = &[
"scope",
"scopes",
"requiresScope",
"requiresScopes",
"requires_scopes",
];
const CANONICAL_SCOPE_KEY: &str = "requires_scope";
pub fn reject_drifted_keys(raw: &Value) -> Result<(), String> {
for collection in ["queries", "mutations"] {
for operation in raw.get(collection).and_then(Value::as_array).into_iter().flatten() {
let name = operation.get("name").and_then(Value::as_str).unwrap_or("<unnamed>");
for (found, canonical) in DRIFTED_INJECT_KEYS {
if operation.get(*found).is_some() {
return Err(format!(
"{collection} → '{name}' declares server-side parameter injection under \
`{found}`, which the compiler does not read — it would compile to a query \
with no injected filter and no diagnostic. Rename it to `{canonical}`. \
Values may be either \"jwt:<claim>\" or {{\"source\": \"jwt\", \"claim\": \
\"<claim>\"}}."
));
}
}
for (found, canonical) in DRIFTED_SHAPE_KEYS {
if operation.get(*found).is_some() {
return Err(format!(
"{collection} → '{name}' declares list-ness under `{found}`, which is the \
TOML `[queries.*]` spelling — this authoring surface reads `{canonical}`. \
Rename it to `{canonical}`. The two surfaces name one concept two ways; \
only `{canonical}` binds here."
));
}
}
}
}
for type_def in raw.get("types").and_then(Value::as_array).into_iter().flatten() {
let type_name = type_def.get("name").and_then(Value::as_str).unwrap_or("<unnamed>");
for field in type_def.get("fields").and_then(Value::as_array).into_iter().flatten() {
let field_name = field.get("name").and_then(Value::as_str).unwrap_or("<unnamed>");
for found in DRIFTED_SCOPE_KEYS {
if field.get(*found).is_some() {
return Err(format!(
"field '{type_name}.{field_name}' declares a scope requirement under \
`{found}`, which the compiler does not read — the compiled field would \
carry no scope at all and the runtime would serve it to callers with no \
scopes. Rename it to `{CANONICAL_SCOPE_KEY}` (a single scope string; \
multiple required scopes are not supported by the runtime field filter)."
));
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests;