helm-schema 0.0.7

Generate an accurate JSON schema for any helm chart
Documentation
use color_eyre::eyre;
use serde_json::Value;
use test_util::prelude::sim_assert_eq;

use crate::output_pipeline::{
    EmitRequest, FinalOutputPolicy, OutputPipelineOptions, PreparedEmitRequest, ReferencePolicy,
    apply_schema_output_pipeline,
};

fn output_policy() -> FinalOutputPolicy {
    FinalOutputPolicy::for_profile(crate::generation::SchemaProfile::Full, false)
}

fn request(reference_policy: ReferencePolicy) -> PreparedEmitRequest {
    PreparedEmitRequest::empty(EmitRequest {
        reference_policy,
        output: OutputPipelineOptions {
            strip_descriptions: false,
            minimize: false,
        },
    })
}

#[test]
fn reference_mode_preserves_refs_when_requested() {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
            "fromRef": {
                "$ref": "./shared.json#/definitions/stringValue"
            }
        },
        "type": "object"
    });

    let output = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::PreserveRefs),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )
    .expect("apply output pipeline");

    sim_assert_eq!(
        have: output
            .pointer("/properties/fromRef/$ref")
            .and_then(Value::as_str),
        want: Some("./shared.json#/definitions/stringValue"),
        "reference-preserving output mode should not dereference refs"
    );
}

#[test]
fn self_contained_reference_mode_preserves_prepared_internal_refs() {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "$defs": {
            "stringValue": {
                "type": "string"
            }
        },
        "properties": {
            "fromRef": {
                "$ref": "#/$defs/stringValue"
            }
        },
        "type": "object"
    });

    let output = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::SelfContained),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )
    .expect("apply output pipeline");

    sim_assert_eq!(
        have: output
            .pointer("/properties/fromRef/$ref")
            .and_then(Value::as_str),
        want: Some("#/$defs/stringValue"),
        "self-contained final output should keep prepared internal refs"
    );
    sim_assert_eq!(
        have: output
            .pointer("/$defs/stringValue/type")
            .and_then(Value::as_str),
        want: Some("string"),
        "prepared definitions should remain available under $defs"
    );
}

#[test]
fn fully_inlined_output_prunes_generator_definitions_orphaned_by_transport() -> eyre::Result<()> {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "$defs": {
            "stringValue": {
                "type": "string"
            }
        },
        "properties": {
            "fromRef": {
                "$ref": "#/$defs/stringValue"
            }
        },
        "type": "object"
    });

    let output = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::FullyInlinedExport),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )?;

    sim_assert_eq!(have: output.get("$defs"), want: None);
    sim_assert_eq!(
        have: output.pointer("/properties/fromRef/type").and_then(Value::as_str),
        want: Some("string")
    );
    Ok(())
}

#[test]
fn self_contained_reference_mode_rejects_unprepared_external_refs() {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
            "fromRef": {
                "$ref": "./shared.json#/definitions/stringValue"
            }
        },
        "type": "object"
    });

    let err = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::SelfContained),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )
    .expect_err("unprepared external ref should fail final output transform");

    assert!(
        err.to_string()
            .contains("external $ref remained after input preparation"),
        "unexpected error: {err}"
    );
}

#[test]
fn fully_inlined_export_reference_mode_inlines_prepared_internal_refs() {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "$defs": {
            "stringValue": {
                "type": "string"
            }
        },
        "properties": {
            "fromRef": {
                "$ref": "#/$defs/stringValue"
            }
        },
        "type": "object"
    });

    let output = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::FullyInlinedExport),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )
    .expect("apply output pipeline");

    sim_assert_eq!(
        have: output
            .pointer("/properties/fromRef/type")
            .and_then(Value::as_str),
        want: Some("string"),
        "fully inlined export mode should inline prepared internal refs"
    );
    assert!(output.pointer("/properties/fromRef/$ref").is_none());
}

#[test]
fn output_pipeline_marks_final_schema_as_generated() {
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "type": "object"
    });

    let output = apply_schema_output_pipeline(
        schema,
        request(ReferencePolicy::PreserveRefs),
        std::path::Path::new("/does/not/matter"),
        output_policy(),
    )
    .expect("apply output pipeline");

    sim_assert_eq!(
        have: output
            .get("x-helm-schema-generated")
            .and_then(Value::as_bool),
        want: Some(true)
    );
}