Skip to main content

secunit_core/
schemas.rs

1//! Compile-time embedded JSON Schemas. The schemas live in
2//! `crates/secunit-core/schemas/*.schema.json` and are baked into
3//! the binary so validation does not depend on the install location.
4
5use std::sync::OnceLock;
6
7use jsonschema::{Draft, JSONSchema};
8use serde_json::Value;
9
10macro_rules! schema {
11    ($name:literal, $path:literal) => {{
12        static CELL: OnceLock<JSONSchema> = OnceLock::new();
13        CELL.get_or_init(|| {
14            let raw = include_str!(concat!("../schemas/", $path));
15            let json: Value = serde_json::from_str(raw)
16                .unwrap_or_else(|e| panic!("schema {}: invalid JSON: {e}", $name));
17            JSONSchema::options()
18                .with_draft(Draft::Draft202012)
19                .compile(&json)
20                .unwrap_or_else(|e| panic!("schema {}: compile failed: {e}", $name))
21        })
22    }};
23}
24
25#[derive(Debug, Clone, Copy)]
26pub enum Schema {
27    Control,
28    Inventory,
29    Schedule,
30    State,
31    Manifest,
32    Prepare,
33    Result,
34    Config,
35    RiskEvent,
36    RiskIndex,
37}
38
39impl Schema {
40    pub fn compiled(self) -> &'static JSONSchema {
41        match self {
42            Schema::Control => schema!("control", "control.schema.json"),
43            Schema::Inventory => schema!("inventory", "inventory.schema.json"),
44            Schema::Schedule => schema!("schedule", "schedule.schema.json"),
45            Schema::State => schema!("state", "state.schema.json"),
46            Schema::Manifest => schema!("manifest", "manifest.schema.json"),
47            Schema::Prepare => schema!("prepare", "prepare.schema.json"),
48            Schema::Result => schema!("result", "result.schema.json"),
49            Schema::Config => schema!("_config", "_config.schema.json"),
50            Schema::RiskEvent => schema!("risk-event", "risk-event.schema.json"),
51            Schema::RiskIndex => schema!("risk-index", "risk-index.schema.json"),
52        }
53    }
54
55    /// Validate `value` against this schema. Returns the list of errors as
56    /// `path: message` strings; empty when valid.
57    pub fn validate(self, value: &Value) -> Vec<String> {
58        match self.compiled().validate(value) {
59            Ok(()) => Vec::new(),
60            Err(errors) => errors
61                .map(|e| format!("{}: {}", e.instance_path, e))
62                .collect(),
63        }
64    }
65}