helm-schema-k8s 0.0.4

Generate an accurate JSON schema for any helm chart
Documentation
use serde_json::{Map, Value};

pub(crate) fn enrich_root_metadata_schema(mut root: Value) -> Value {
    let Some(obj) = root.as_object_mut() else {
        return root;
    };
    let Some(properties) = obj.get_mut("properties").and_then(Value::as_object_mut) else {
        return root;
    };

    match properties.get_mut("metadata") {
        Some(metadata) => enrich_metadata_object(metadata),
        None => {
            properties.insert("metadata".to_string(), metadata_object_schema());
        }
    }

    root
}

pub(crate) fn enriched_metadata_schema(root: &Value) -> Value {
    let mut metadata = root
        .get("properties")
        .and_then(Value::as_object)
        .and_then(|properties| properties.get("metadata"))
        .cloned()
        .unwrap_or_else(metadata_object_schema);
    enrich_metadata_object(&mut metadata);
    metadata
}

fn enrich_metadata_object(metadata: &mut Value) {
    let Some(obj) = metadata.as_object_mut() else {
        *metadata = metadata_object_schema();
        return;
    };

    obj.entry("type".to_string())
        .or_insert_with(|| Value::String("object".to_string()));
    if obj.get("properties").and_then(Value::as_object).is_none() {
        obj.insert("properties".to_string(), Value::Object(Map::new()));
    }

    let Some(properties) = obj.get_mut("properties").and_then(Value::as_object_mut) else {
        return;
    };

    strengthen_property(properties, "name", string_schema);
    strengthen_property(properties, "namespace", string_schema);
    strengthen_property(properties, "labels", string_map_schema);
    strengthen_property(properties, "annotations", string_map_schema);
}

fn strengthen_property(properties: &mut Map<String, Value>, key: &str, desired: fn() -> Value) {
    match properties.get_mut(key) {
        Some(existing) if is_weak_metadata_leaf(existing) => {
            *existing = desired();
        }
        Some(_) => {}
        None => {
            properties.insert(key.to_string(), desired());
        }
    }
}

fn is_weak_metadata_leaf(schema: &Value) -> bool {
    let Some(obj) = schema.as_object() else {
        return false;
    };
    if obj.is_empty() {
        return true;
    }

    let has_properties = obj
        .get("properties")
        .and_then(Value::as_object)
        .is_some_and(|map| !map.is_empty());
    if has_properties {
        return false;
    }

    obj.get("additionalProperties")
        .and_then(Value::as_object)
        .is_some_and(Map::is_empty)
}

fn metadata_object_schema() -> Value {
    Value::Object(
        [
            ("type".to_string(), Value::String("object".to_string())),
            (
                "properties".to_string(),
                Value::Object(
                    [
                        ("name".to_string(), string_schema()),
                        ("namespace".to_string(), string_schema()),
                        ("labels".to_string(), string_map_schema()),
                        ("annotations".to_string(), string_map_schema()),
                    ]
                    .into_iter()
                    .collect(),
                ),
            ),
        ]
        .into_iter()
        .collect(),
    )
}

fn string_schema() -> Value {
    Value::Object(
        [("type".to_string(), Value::String("string".to_string()))]
            .into_iter()
            .collect(),
    )
}

fn string_map_schema() -> Value {
    Value::Object(
        [
            ("type".to_string(), Value::String("object".to_string())),
            ("additionalProperties".to_string(), string_schema()),
        ]
        .into_iter()
        .collect(),
    )
}