greentic-dev 0.5.5

Developer CLI and local tooling for Greentic flows, packs, and components
Documentation
use serde_json::Value as JsonValue;
use serde_yaml_bw::Value as YamlValue;

pub struct CompiledSchema {
    pub validator: jsonschema::Validator,
    pub schema_id: Option<String>,
}

pub fn compile_schema(schema_json: &str) -> Result<CompiledSchema, String> {
    let schema: JsonValue = serde_json::from_str(schema_json)
        .map_err(|error| format!("invalid schema JSON: {error}"))?;
    let schema_id = schema
        .get("$id")
        .and_then(|value| value.as_str())
        .map(ToOwned::to_owned);
    let validator = jsonschema::validator_for(&schema)
        .map_err(|error| format!("schema did not compile: {error}"))?;

    Ok(CompiledSchema {
        validator,
        schema_id,
    })
}

pub fn validate_yaml_against_compiled_schema(
    node: &YamlValue,
    validator: &jsonschema::Validator,
) -> Result<(), String> {
    let node_json = serde_json::to_value(node)
        .map_err(|error| format!("failed to convert YAML to JSON: {error}"))?;
    validator
        .validate(&node_json)
        .map_err(|error| error.to_string())
}

pub fn validate_yaml_against_schema(node: &YamlValue, schema_json: &str) -> Result<(), String> {
    let compiled = compile_schema(schema_json)?;
    validate_yaml_against_compiled_schema(node, &compiled.validator)
}

pub fn schema_id_from_json(schema_json: &str) -> Option<String> {
    let schema: JsonValue = serde_json::from_str(schema_json).ok()?;
    schema
        .get("$id")
        .and_then(|value| value.as_str())
        .map(|value| value.to_string())
}

#[cfg(test)]
mod tests {
    use super::{schema_id_from_json, validate_yaml_against_schema};
    use serde_yaml_bw::Value as YamlValue;

    #[test]
    fn validates_matching_document() {
        let node: YamlValue = serde_yaml_bw::from_str("name: demo\n").unwrap();
        let schema = r#"{
          "type": "object",
          "required": ["name"],
          "properties": {
            "name": { "type": "string" }
          }
        }"#;

        assert!(validate_yaml_against_schema(&node, schema).is_ok());
    }

    #[test]
    fn rejects_invalid_schema_json() {
        let node: YamlValue = serde_yaml_bw::from_str("name: demo\n").unwrap();
        let err = validate_yaml_against_schema(&node, "{").unwrap_err();
        assert!(err.contains("invalid schema JSON"));
    }

    #[test]
    fn extracts_schema_id() {
        let schema = r#"{"$id":"greentic://schema/demo","type":"object"}"#;
        assert_eq!(
            schema_id_from_json(schema).as_deref(),
            Some("greentic://schema/demo")
        );
        assert!(schema_id_from_json("not json").is_none());
    }
}