helm-schema-ir 0.0.4

Generate an accurate JSON schema for any helm chart
Documentation
use std::collections::HashMap;
use test_util::prelude::sim_assert_eq;

use super::{
    BoundValueContext, GetBinding, GetBindingPlan, parse_get_binding_from_exprs,
    parse_literal_list_range_expr,
};
use crate::eval_env::EvalEnv;
use crate::expr_eval::eval_exprs_effects;
use crate::fragment_assignment::AssignmentKind;
use helm_schema_ast::parse_expr_text;

fn parse_literal_list_range(header: &str) -> Option<(String, Vec<String>)> {
    let header = header.trim();
    let exprs = parse_expr_text(header);
    let [expr] = exprs.as_slice() else {
        return None;
    };
    parse_literal_list_range_expr(expr)
}

fn parse_get_binding(text: &str) -> Option<GetBindingPlan> {
    parse_get_binding_from_exprs(&parse_expr_text(text))
}

fn extract_bound_values(
    text: &str,
    range_domains: &HashMap<String, Vec<String>>,
    get_bindings: &HashMap<String, GetBinding>,
) -> Vec<String> {
    let env = EvalEnv {
        bound_values: BoundValueContext::new(range_domains, get_bindings),
        ..EvalEnv::default()
    };
    eval_exprs_effects(&parse_expr_text(text), &env)
        .bound_output_paths
        .into_iter()
        .collect()
}

#[test]
fn parse_get_binding_detects_declaration_from_ast() {
    sim_assert_eq!(
        have: parse_get_binding(r"{{- $value := get $.Values.config $key -}}"),
        want: Some(GetBindingPlan {
            variable: "value".to_string(),
            kind: AssignmentKind::Declaration,
            binding: GetBinding {
                base: "config".to_string(),
                key_var: "key".to_string(),
            },
        })
    );
}

#[test]
fn parse_get_binding_detects_assignment_from_ast() {
    sim_assert_eq!(
        have: parse_get_binding(r"{{- $value = get .Values.config $key -}}"),
        want: Some(GetBindingPlan {
            variable: "value".to_string(),
            kind: AssignmentKind::Assignment,
            binding: GetBinding {
                base: "config".to_string(),
                key_var: "key".to_string(),
            },
        })
    );
}

#[test]
fn parse_literal_list_range_detects_variable_definition_from_ast() {
    sim_assert_eq!(
        have: parse_literal_list_range(r#"$scope := list "frontend" "backend""#),
        want: Some((
            "scope".to_string(),
            vec!["frontend".to_string(), "backend".to_string()]
        ))
    );
}

#[test]
fn extract_bound_values_resolves_selector_reads_from_ast() {
    let mut range_domains = HashMap::new();
    range_domains.insert(
        "scope".to_string(),
        vec!["frontend".to_string(), "backend".to_string()],
    );
    let mut get_bindings = HashMap::new();
    get_bindings.insert(
        "config".to_string(),
        GetBinding {
            base: "config".to_string(),
            key_var: "scope".to_string(),
        },
    );

    sim_assert_eq!(
        have: extract_bound_values(
            r#"{{- printf "%s" $config.port -}}"#,
            &range_domains,
            &get_bindings
        ),
        want: vec![
            "config.backend.port".to_string(),
            "config.frontend.port".to_string()
        ]
    );
}

#[test]
fn extract_bound_values_respects_or_short_circuit_eq_predicate() {
    let mut range_domains = HashMap::new();
    range_domains.insert(
        "protocol".to_string(),
        vec!["nats".to_string(), "websocket".to_string()],
    );
    let mut get_bindings = HashMap::new();
    get_bindings.insert(
        "config".to_string(),
        GetBinding {
            base: "config".to_string(),
            key_var: "protocol".to_string(),
        },
    );

    sim_assert_eq!(
        have: extract_bound_values(
            r#"or (eq $protocol "nats") $config.enabled"#,
            &range_domains,
            &get_bindings
        ),
        want: vec!["config.websocket.enabled".to_string()]
    );
}

#[test]
fn extract_bound_values_respects_and_short_circuit_eq_predicate() {
    let mut range_domains = HashMap::new();
    range_domains.insert(
        "protocol".to_string(),
        vec!["nats".to_string(), "websocket".to_string()],
    );
    let mut get_bindings = HashMap::new();
    get_bindings.insert(
        "config".to_string(),
        GetBinding {
            base: "config".to_string(),
            key_var: "protocol".to_string(),
        },
    );

    sim_assert_eq!(
        have: extract_bound_values(
            r#"and (eq $protocol "nats") $config.enabled"#,
            &range_domains,
            &get_bindings
        ),
        want: vec!["config.nats.enabled".to_string()]
    );
}