use serde_json::Value;
use crate::domain::config::Config;
pub const SCHEMA_URL: &str =
"https://raw.githubusercontent.com/nickderobertis/llmlint/main/assets/llmlint.schema.json";
pub fn modeline() -> String {
format!("# yaml-language-server: $schema={SCHEMA_URL}\n")
}
pub fn build() -> Value {
let mut schema =
serde_json::to_value(schemars::schema_for!(Config)).expect("config schema serializes");
if let Value::Object(map) = &mut schema {
map.insert("$id".to_string(), Value::String(SCHEMA_URL.to_string()));
}
schema
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::assets;
#[test]
fn schema_describes_the_top_level_config_shape() {
let s = build();
assert_eq!(s["$id"], SCHEMA_URL);
assert_eq!(s["type"], "object");
let props = s["properties"].as_object().unwrap();
for key in [
"version",
"prompt_template",
"files",
"oneharness",
"plugins",
"agents",
"rules",
] {
assert!(props.contains_key(key), "missing property {key}");
}
let rule_ref = s["properties"]["rules"]["items"]["$ref"]
.as_str()
.expect("rules.items is a $ref");
let rule_def = rule_ref.rsplit('/').next().unwrap();
let rule = &s["$defs"][rule_def];
let required = rule["required"].as_array().unwrap();
assert!(required.iter().any(|v| v == "name"));
assert!(!required.iter().any(|v| v == "description"));
assert!(rule["properties"]
.as_object()
.unwrap()
.contains_key("override"));
}
#[test]
fn modeline_targets_the_published_schema() {
let line = modeline();
assert!(line.starts_with("# yaml-language-server: $schema="));
assert!(line.contains(SCHEMA_URL));
assert!(line.ends_with('\n'));
}
#[test]
fn committed_asset_matches_generated_schema() {
let generated = build();
let committed: Value =
serde_json::from_str(assets::CONFIG_SCHEMA).expect("asset is valid JSON");
if committed != generated {
if std::env::var_os("LLMLINT_UPDATE_SCHEMA").is_some() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/llmlint.schema.json");
let mut body = serde_json::to_string_pretty(&generated).unwrap();
body.push('\n');
std::fs::write(path, body).unwrap();
return;
}
panic!(
"assets/llmlint.schema.json is out of sync with config_schema::build(); \
regenerate with `LLMLINT_UPDATE_SCHEMA=1 cargo test -p llmlint config_schema`"
);
}
}
}