Skip to main content

helm_schema/output_pipeline/
overrides.rs

1use std::path::{Path, PathBuf};
2
3use serde_json::Value;
4
5use crate::error::EngineResult;
6use crate::flatten;
7use crate::load_budget::read_to_end_capped;
8use crate::output_pipeline::{PolicyInputOptions, ReferenceMode};
9use crate::schema_override;
10
11/// Output policy inputs loaded from the filesystem before final schema
12/// transforms run.
13///
14/// This keeps override IO and external `$ref` retrieval out of the pure output
15/// transform path.
16#[derive(Debug, Default)]
17pub struct PolicyInputs {
18    /// Override schema documents after file loading and output-mode
19    /// preparation. The final output pipeline consumes these prepared
20    /// documents as data, so override file IO and override merge policy
21    /// stay separate.
22    prepared_override_schemas: Vec<Value>,
23}
24
25impl PolicyInputs {
26    pub(super) fn override_count(&self) -> usize {
27        self.prepared_override_schemas.len()
28    }
29
30    pub(super) fn into_prepared_override_schemas(self) -> Vec<Value> {
31        self.prepared_override_schemas
32    }
33}
34
35/// Loads and prepares override schemas according to reference and fetch policy.
36///
37/// # Errors
38///
39/// Returns an error when an override exceeds its load budget, cannot be read
40/// or decoded, or contains references that policy cannot prepare.
41#[tracing::instrument(skip_all, fields(override_count = paths.len()))]
42pub fn load_policy_inputs(
43    paths: &[PathBuf],
44    options: &PolicyInputOptions,
45) -> EngineResult<PolicyInputs> {
46    let prepared_override_schemas = paths
47        .iter()
48        .map(|path| load_prepared_override_schema(path, options))
49        .collect::<EngineResult<Vec<_>>>()?;
50    Ok(PolicyInputs {
51        prepared_override_schemas,
52    })
53}
54
55#[tracing::instrument(skip_all)]
56fn load_prepared_override_schema(path: &Path, options: &PolicyInputOptions) -> EngineResult<Value> {
57    let mut override_schema = load_json_file(path, options.load_budget.max_schema_document_bytes)?;
58
59    // Tag every subtree that carries `$ref` with an internal "replace on
60    // merge" marker. The marker survives reference preparation and tells
61    // override merge to swap the prepared content into the base instead of
62    // deep-merging it with inferred constraints for the same path.
63    schema_override::mark_refs_for_replacement(&mut override_schema);
64
65    prepare_override_schema(override_schema, path, options)
66}
67
68#[tracing::instrument(skip_all, fields(reference_mode = ?options.reference_mode))]
69fn prepare_override_schema(
70    schema: Value,
71    override_path: &Path,
72    options: &PolicyInputOptions,
73) -> EngineResult<Value> {
74    let override_base = override_path.parent().unwrap_or_else(|| Path::new("."));
75
76    match options.reference_mode {
77        ReferenceMode::SelfContained => flatten::bundle_refs(
78            schema,
79            override_base,
80            options.fetch_policy,
81            options.load_budget,
82        ),
83        ReferenceMode::FullyInlinedExport => flatten::flatten_refs(
84            &schema,
85            override_base,
86            options.fetch_policy,
87            options.load_budget,
88        ),
89        ReferenceMode::PreserveRefs => Ok(schema),
90    }
91}
92
93fn load_json_file(path: &Path, max_bytes: usize) -> EngineResult<Value> {
94    let mut file = std::fs::File::open(path)?;
95    let bytes = read_to_end_capped(&mut file, max_bytes, path.display().to_string())?;
96    let value: Value = serde_json::from_slice(&bytes)?;
97    Ok(value)
98}
99
100#[cfg(test)]
101#[path = "tests/overrides.rs"]
102mod tests;