Skip to main content

agent_policy/load/
schema.rs

1// JSON Schema validation — implemented in Phase 1
2
3use crate::error::{Error, Result};
4use std::sync::OnceLock;
5
6const SCHEMA_JSON: &str = include_str!("../../agent-policy.schema.json");
7
8/// Return a reference to the compiled JSON Schema validator.
9///
10/// The validator is compiled exactly once (on first call) and cached for the
11/// lifetime of the process. Compiling a JSON Schema is expensive; calling
12/// `validator_for` on every `validate()` invocation would be measurable
13/// overhead in test suites and CI pipelines that load many configs.
14#[allow(clippy::expect_used)] // panics are on invariants about bundled binary content
15fn compiled_validator() -> &'static jsonschema::Validator {
16    static VALIDATOR: OnceLock<jsonschema::Validator> = OnceLock::new();
17    VALIDATOR.get_or_init(|| {
18        let schema: serde_json::Value =
19            serde_json::from_str(SCHEMA_JSON).expect("bundled schema is always valid JSON");
20        jsonschema::validator_for(&schema).expect("bundled schema always compiles")
21    })
22}
23
24/// Validate a parsed YAML document against the bundled JSON Schema.
25///
26/// The input must be a `serde_json::Value` representation of the raw policy.
27/// Convert via `serde_json::to_value(&raw_policy)`.
28///
29/// # Errors
30///
31/// Returns [`crate::Error::Schema`] if the document violates the bundled JSON Schema.
32pub fn validate(doc: &serde_json::Value) -> Result<()> {
33    let validator = compiled_validator();
34
35    let errors: Vec<String> = validator
36        .iter_errors(doc)
37        .map(|e| format!("  - {e}"))
38        .collect();
39
40    if errors.is_empty() {
41        Ok(())
42    } else {
43        Err(Error::Schema(errors.join("\n")))
44    }
45}