1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::collections::{BTreeMap, BTreeSet};
use serde_yaml::Value as YamlValue;
/// Structural evidence extracted from the composed values.yaml in one parse.
#[derive(Debug, Default)]
pub(crate) struct ValuesRoots {
/// The explicit top-level keys present in the composed values.yaml.
///
/// These keys are structural evidence that the chart accepts the root
/// value path, but they do not imply that any nested key should exist.
pub(crate) top_level_paths: BTreeSet<String>,
/// The subset of top-level keys whose default value is a mapping.
pub(crate) top_level_mapping_paths: BTreeSet<String>,
/// Every explicit mapping-backed values path present in the composed
/// values.yaml.
///
/// This includes nested object keys such as `controller.kind` and
/// `controller.admissionWebhooks.enabled`, which are structural evidence
/// that the chart already ships a default at that path and therefore must
/// not infer it as user-required later.
pub(crate) explicit_paths: BTreeSet<String>,
/// Literal string defaults keyed by their composed values path.
///
/// These remain chart-authored facts rather than accepted-input facts:
/// consumers use them only when a template explicitly evaluates the
/// selected default as a program (for example, `tpl .Values.query .`).
pub(crate) string_defaults: BTreeMap<String, String>,
}
impl ValuesRoots {
pub(crate) fn from_values_yaml(values_yaml: Option<&str>) -> Self {
let mut roots = Self::default();
let Some(values_yaml) = values_yaml else {
return roots;
};
let Ok(doc) = serde_yaml::from_str::<YamlValue>(values_yaml) else {
return roots;
};
if let YamlValue::Mapping(mapping) = &doc {
for (key, value) in mapping {
let Some(key) = key.as_str() else {
continue;
};
let key = key.trim();
if key.is_empty() {
continue;
}
let path = helm_schema_core::join_value_path([key]);
roots.top_level_paths.insert(path.clone());
if matches!(value, YamlValue::Mapping(_)) {
roots.top_level_mapping_paths.insert(path);
}
}
}
collect_values_facts(
&doc,
&mut Vec::new(),
&mut roots.explicit_paths,
&mut roots.string_defaults,
);
roots
}
pub(crate) fn string_defaults_for_prefix(&self, prefix: &[String]) -> BTreeMap<String, String> {
self.string_defaults
.iter()
.filter_map(|(path, value)| {
let segments = helm_schema_core::split_value_path(path);
segments
.strip_prefix(prefix)
.filter(|relative| !relative.is_empty())
.map(|relative| {
(
helm_schema_core::join_value_path(relative.iter().cloned()),
value.clone(),
)
})
})
.collect()
}
}
fn collect_values_facts(
value: &YamlValue,
current_path: &mut Vec<String>,
explicit_paths: &mut BTreeSet<String>,
string_defaults: &mut BTreeMap<String, String>,
) {
if !current_path.is_empty() {
let path = helm_schema_core::join_value_path(&*current_path);
explicit_paths.insert(path.clone());
if let YamlValue::String(value) = value {
string_defaults.insert(path, value.clone());
}
}
let YamlValue::Mapping(mapping) = value else {
return;
};
for (key, child) in mapping {
let Some(key) = key.as_str() else {
continue;
};
let key = key.trim();
if key.is_empty() {
continue;
}
current_path.push(key.to_string());
collect_values_facts(child, current_path, explicit_paths, string_defaults);
current_path.pop();
}
}
#[cfg(test)]
#[path = "tests/values_roots.rs"]
mod tests;