assay-cli 3.10.0

CLI for Assay
use std::sync::OnceLock;

use anyhow::{anyhow, Result};
use jsonschema::Draft;
use serde_json::Value;

const COVERAGE_REPORT_V1_SCHEMA_JSON: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/schemas/coverage_report_v1.schema.json"
));

static VALIDATOR: OnceLock<jsonschema::Validator> = OnceLock::new();
static VALIDATOR_RESULT: OnceLock<Result<jsonschema::Validator, String>> = OnceLock::new();

fn compiled_validator() -> Result<&'static jsonschema::Validator> {
    let init = VALIDATOR_RESULT
        .get_or_init(|| {
            let schema: Value =
                serde_json::from_str(COVERAGE_REPORT_V1_SCHEMA_JSON).map_err(|e| {
                    format!("failed to parse embedded coverage_report_v1 schema JSON: {e}")
                })?;

            jsonschema::options()
                .with_draft(Draft::Draft202012)
                .build(&schema)
                .map_err(|e| format!("failed to compile coverage_report_v1 schema: {e}"))
        })
        .as_ref()
        .map_err(|e| anyhow!("{e}"))?;

    Ok(VALIDATOR.get_or_init(|| init.clone()))
}

pub fn validate_coverage_report_v1(instance: &Value) -> Result<()> {
    let validator = compiled_validator()?;
    if validator.is_valid(instance) {
        return Ok(());
    }

    const MAX_ERRORS: usize = 10;
    let mut lines = Vec::new();
    for (i, e) in validator.iter_errors(instance).take(MAX_ERRORS).enumerate() {
        lines.push(format!("{:02}: {}", i + 1, e));
    }

    let mut msg = String::from("coverage_report_v1 schema validation failed");
    if !lines.is_empty() {
        msg.push_str(":\n");
        msg.push_str(&lines.join("\n"));
    }
    Err(anyhow!(msg))
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn schema_compiles() {
        let _ = compiled_validator().expect("schema should compile");
    }

    #[test]
    fn empty_object_is_invalid() {
        let v = json!({});
        assert!(validate_coverage_report_v1(&v).is_err());
    }
}