use std::collections::{BTreeMap, BTreeSet};
use crate::{Guard, ProviderSchemaUse, ValueKind, contract::ContractUse};
use helm_schema_core::{
ConditionalGuard, ConditionalOverlayEvidence, ConditionalPathOverlay, ContractFailImplication,
ContractPathSchemaEvidence, ContractRequirednessEvidence, ContractRequirementTarget,
ContractSchemaSignals, ContractValuePathFacts, FailValueRequirement, GuardValue,
MetadataFieldKind, Predicate,
};
#[tracing::instrument(skip_all)]
#[expect(
clippy::too_many_arguments,
reason = "each parameter is one interpreter fact channel; a struct would mirror the same nine fields without adding an invariant"
)]
pub(crate) fn derive_schema_signals_from_contract_parts(
uses: &[ContractUse],
type_hints: &BTreeMap<String, BTreeSet<String>>,
guarded_type_hints: &BTreeMap<String, BTreeSet<String>>,
fallback_type_hints: &BTreeMap<String, BTreeSet<String>>,
guarded_fallback_type_hints: &BTreeMap<String, BTreeSet<String>>,
shape_erased_value_paths: &BTreeSet<String>,
string_contract_value_paths: &BTreeSet<String>,
range_modes: &crate::range_modes::RangeModes,
fail_conditions: &[crate::eval_effect::FailCapture],
dependency_values_root_fragments: &BTreeSet<String>,
) -> ContractSchemaSignals {
let mut paths = BTreeMap::new();
let mut terminal_clauses = Vec::new();
for contract_use in uses {
record_contract_use(&mut paths, contract_use, range_modes);
}
for capture in fail_conditions {
record_fail_conjunction(&mut paths, &mut terminal_clauses, capture, range_modes);
}
for value_path in dependency_values_root_fragments {
if !value_path.trim().is_empty() {
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.facts.record_facts(ContractValuePathFacts {
accepted_values_root_fragment: true,
accepted_dependency_values_root_fragment: true,
..ContractValuePathFacts::default()
});
let requires_table = ContractFailImplication {
outer_guards: Vec::new(),
target: ContractRequirementTarget::Value,
requirements: vec![FailValueRequirement::SchemaType("object".to_string())],
};
if !acc.fail_implications.contains(&requires_table) {
acc.fail_implications.push(requires_table);
}
}
}
for value_path in shape_erased_value_paths {
if value_path.trim().is_empty() {
continue;
}
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.facts.facts.used_as_serialized = true;
}
for value_path in string_contract_value_paths {
if value_path.trim().is_empty() {
continue;
}
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.facts.facts.has_string_contract = true;
acc.type_hints.insert("string".to_string());
}
for (value_path, schema_types) in type_hints {
let schema_types = schema_types
.iter()
.filter(|schema_type| !schema_type.trim().is_empty())
.cloned()
.collect::<BTreeSet<_>>();
if !value_path.trim().is_empty() && !schema_types.is_empty() {
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.type_hints.extend(schema_types);
}
}
for (value_path, schema_types) in guarded_type_hints {
let schema_types = schema_types
.iter()
.filter(|schema_type| !schema_type.trim().is_empty())
.cloned()
.collect::<BTreeSet<_>>();
if !value_path.trim().is_empty() && !schema_types.is_empty() {
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.guarded_type_hints.extend(schema_types);
}
}
for (value_path, schema_types) in fallback_type_hints {
let schema_types = schema_types
.iter()
.filter(|schema_type| !schema_type.trim().is_empty())
.cloned()
.collect::<BTreeSet<_>>();
if !value_path.trim().is_empty() && !schema_types.is_empty() {
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.fallback_type_hints.extend(schema_types);
}
}
for (value_path, schema_types) in guarded_fallback_type_hints {
let schema_types = schema_types
.iter()
.filter(|schema_type| !schema_type.trim().is_empty())
.cloned()
.collect::<BTreeSet<_>>();
if !value_path.trim().is_empty() && !schema_types.is_empty() {
let acc = path_accumulator(&mut paths, value_path);
acc.referenced = true;
acc.guarded_fallback_type_hints.extend(schema_types);
}
}
finish_schema_signals(paths, terminal_clauses)
}
#[derive(Default)]
struct ContractPathAccumulator {
referenced: bool,
guard_predicates: Vec<ConditionalGuard>,
facts: PathSchemaFactsAccumulator,
requiredness: ContractRequirednessEvidence,
guarded_provider_schema_uses: Vec<ProviderSchemaUse>,
guarded_metadata_field_kinds: BTreeSet<MetadataFieldKind>,
type_hints: BTreeSet<String>,
guarded_type_hints: BTreeSet<String>,
fallback_type_hints: BTreeSet<String>,
guarded_fallback_type_hints: BTreeSet<String>,
conditional_overlay_branches: BTreeMap<Vec<ConditionalGuard>, PathSchemaFactsAccumulator>,
has_unconditional_overlay_peer: bool,
saw_unsupported_overlay: bool,
fail_implications: Vec<ContractFailImplication>,
member_access_guard_sets: BTreeMap<Vec<String>, BTreeSet<Vec<ConditionalGuard>>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct PathSchemaFactsAccumulator {
metadata_field_kinds: BTreeSet<MetadataFieldKind>,
provider_schema_uses: Vec<ProviderSchemaUse>,
facts: ContractValuePathFacts,
all_uses_nullable: bool,
}
impl Default for PathSchemaFactsAccumulator {
fn default() -> Self {
Self {
metadata_field_kinds: BTreeSet::new(),
provider_schema_uses: Vec::new(),
facts: ContractValuePathFacts {
all_render_uses_self_guarded: true,
all_render_uses_falsy_tolerant: true,
..ContractValuePathFacts::default()
},
all_uses_nullable: true,
}
}
}
impl PathSchemaFactsAccumulator {
fn record_nullable_observation(&mut self, nullable: bool) {
self.all_uses_nullable &= nullable;
}
fn record_metadata_field_kind(&mut self, field_kind: Option<MetadataFieldKind>) {
if let Some(field_kind) = field_kind {
self.metadata_field_kinds.insert(field_kind);
}
}
fn record_facts(&mut self, facts: ContractValuePathFacts) {
self.facts.used_as_fragment |= facts.used_as_fragment;
self.facts.used_as_serialized |= facts.used_as_serialized;
self.facts.used_as_yaml_serialized |= facts.used_as_yaml_serialized;
self.facts.has_string_contract |= facts.has_string_contract;
self.facts.has_string_contract_items |= facts.has_string_contract_items;
self.facts.used_as_pathless_fragment |= facts.used_as_pathless_fragment;
self.facts.accepted_values_root_fragment |= facts.accepted_values_root_fragment;
self.facts.accepted_dependency_values_root_fragment |=
facts.accepted_dependency_values_root_fragment;
self.facts.is_ranged_source |= facts.is_ranged_source;
self.facts.is_direct_ranged_source |= facts.is_direct_ranged_source;
self.facts.has_destructured_range_use |= facts.has_destructured_range_use;
self.facts.has_json_decoded_range_use |= facts.has_json_decoded_range_use;
self.facts.is_partial_scalar_value_path |= facts.is_partial_scalar_value_path;
self.facts.is_nullable |= facts.is_nullable;
self.facts.merge_render_use_facts(facts);
}
fn record_provider_schema_use(&mut self, provider_schema_use: ProviderSchemaUse) {
if !self.provider_schema_uses.contains(&provider_schema_use) {
self.provider_schema_uses.push(provider_schema_use);
}
}
fn merge_union(&mut self, other: Self) {
for provider_schema_use in other.provider_schema_uses {
self.record_provider_schema_use(provider_schema_use);
}
self.metadata_field_kinds.extend(other.metadata_field_kinds);
self.record_facts(other.facts);
self.all_uses_nullable &= other.all_uses_nullable;
}
fn facts(
&self,
has_referenced_descendants: bool,
has_item_descendants: bool,
has_structured_item_descendants: bool,
) -> ContractValuePathFacts {
let mut facts = self.facts;
facts.has_referenced_descendants = has_referenced_descendants;
facts.has_item_descendants = has_item_descendants;
facts.has_structured_item_descendants = has_structured_item_descendants;
facts.is_nullable &= self.all_uses_nullable;
facts
}
fn conditional_overlay_evidence(
self,
global_facts: ContractValuePathFacts,
type_hints: BTreeSet<String>,
) -> ConditionalOverlayEvidence {
let mut facts = self.facts(
global_facts.has_referenced_descendants,
global_facts.has_item_descendants,
global_facts.has_structured_item_descendants,
);
facts.has_destructured_range_use |= global_facts.has_destructured_range_use;
facts.has_json_decoded_range_use |= global_facts.has_json_decoded_range_use;
let mut type_hints = type_hints;
if facts.has_string_contract {
type_hints.insert("string".to_string());
}
ConditionalOverlayEvidence {
facts,
metadata_field_kinds: self.metadata_field_kinds,
type_hints,
provider_schema_uses: self.provider_schema_uses,
}
}
}
fn partition_compatible_hints(
hints: &BTreeSet<String>,
guards: &[ConditionalGuard],
value_path: &str,
) -> BTreeSet<String> {
let mut compatible = hints.clone();
for guard in guards {
match guard {
ConditionalGuard::TypeIs { path, schema_type } if path == value_path => {
compatible.retain(|hint| hint == schema_type);
}
ConditionalGuard::Not(inner) => {
if let ConditionalGuard::TypeIs { path, schema_type } = inner.as_ref()
&& path == value_path
{
compatible.retain(|hint| hint != schema_type);
}
}
_ => {}
}
}
compatible
}
fn record_contract_use(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
contract_use: &ContractUse,
range_modes: &crate::range_modes::RangeModes,
) {
if contract_use.range_key {
record_range_key_slot_use(paths, contract_use, range_modes);
return;
}
let disjuncts = contract_use.condition.disjuncts();
let has_approximate_disjunct = disjuncts
.iter()
.any(|conjunction| conjunction.iter().any(Predicate::contains_approximation));
let conjunctions = disjuncts
.iter()
.map(|conjunction| conjunction.iter().cloned().collect::<Vec<_>>())
.collect::<Vec<_>>();
for predicates in conjunctions {
if has_approximate_disjunct
&& contract_use.path.0.is_empty()
&& !predicates.is_empty()
&& predicates
.iter()
.all(|predicate| predicate_is_self_presence(predicate, &contract_use.source_expr))
{
continue;
}
let predicates: Vec<Predicate> = if let Some(merge) = &contract_use.merge_layers {
predicates
.into_iter()
.filter(|predicate| {
!matches!(
predicate,
Predicate::Guard(Guard::Truthy { path } | Guard::With { path })
if path != &contract_use.source_expr
&& merge.layers.contains(path)
)
})
.collect()
} else {
predicates
};
let predicates: Vec<Predicate> = predicates
.into_iter()
.map(|predicate| match &predicate {
Predicate::Approximate { sound_subset, .. }
if !sound_subset.is_empty()
&& sound_subset
.iter()
.all(|guard| matches!(guard, Guard::AtMostOneMember { .. })) =>
{
Predicate::all(sound_subset.iter().cloned().map(Predicate::from).collect())
}
_ => predicate,
})
.collect();
record_contract_use_conjunction(paths, contract_use, &predicates, range_modes);
}
}
fn record_range_key_slot_use(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
contract_use: &ContractUse,
range_modes: &crate::range_modes::RangeModes,
) {
if contract_use.path.0.is_empty() || !range_modes.mode(&contract_use.source_expr).direct {
return;
}
let Some(provider_use) = provider_schema_use(contract_use, false) else {
return;
};
for conjunction in contract_use.condition.disjuncts() {
let predicates: Vec<Predicate> = conjunction.iter().cloned().collect();
if predicates.iter().any(Predicate::contains_approximation) {
continue;
}
let Some(guards) = lowerable_conditional_guard_set(contract_use, &predicates) else {
continue;
};
let acc = path_accumulator(paths, &contract_use.source_expr);
acc.referenced = true;
if guards.is_empty() {
acc.facts.record_provider_schema_use(provider_use.clone());
} else {
let branch = acc.conditional_overlay_branches.entry(guards).or_default();
branch.record_provider_schema_use(provider_use.clone());
}
}
}
fn kind_branch_resolved_use(
contract_use: &ContractUse,
predicates: &[Predicate],
) -> Option<ContractUse> {
let resource = contract_use.resource.as_ref()?;
if resource.kind_branches.is_empty() {
return None;
}
let conjuncts: Vec<&Predicate> = predicates.iter().flat_map(flattened_conjuncts).collect();
let mut selected = resource.kind_branches.iter().filter(|branch| {
flattened_conjuncts(&branch.predicate)
.iter()
.all(|conjunct| matches!(conjunct, Predicate::True) || conjuncts.contains(conjunct))
});
let selected_kind = match (selected.next(), selected.next()) {
(Some(branch), None) => Some(branch.kind.clone()),
_ => None,
};
let mut resolved = contract_use.clone();
if let Some(resource) = resolved.resource.as_mut() {
if let Some(kind) = selected_kind {
resource.kind = kind;
resource.kind_candidates.clear();
}
resource.kind_branches.clear();
}
Some(resolved)
}
fn flattened_conjuncts(predicate: &Predicate) -> Vec<&Predicate> {
match predicate {
Predicate::And(items) => items.iter().flat_map(flattened_conjuncts).collect(),
other => vec![other],
}
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn record_contract_use_conjunction(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
contract_use: &ContractUse,
predicates: &[Predicate],
range_modes: &crate::range_modes::RangeModes,
) {
let kind_resolved = kind_branch_resolved_use(contract_use, predicates);
let contract_use = kind_resolved.as_ref().unwrap_or(contract_use);
let has_approximate = predicates.iter().any(Predicate::contains_approximation);
if ranged_member_parent(&contract_use.source_expr).is_some_and(|parent| {
!range_modes.mode(parent).direct
&& !predicates.is_empty()
&& predicates.iter().all(|predicate| {
matches!(
predicate,
Predicate::Guard(Guard::Range { path })
if !range_modes.mode(path).direct
)
})
}) {
return;
}
let lowerable_guards =
lowerable_conditional_guard_set(contract_use, predicates).or_else(|| {
(contract_use.path.0.is_empty() && range_modes.mode(&contract_use.source_expr).direct)
.then(|| lowerable_range_outer_guards(&contract_use.source_expr, predicates))
.flatten()
});
let merge_layered = contract_use
.merge_layers
.as_ref()
.filter(|merge| merge.layers.get(merge.position) == Some(&contract_use.source_expr))
.filter(|merge| {
!merge.via_binding || merge.nil_scrubbed_layers.iter().any(|scrubbed| *scrubbed)
})
.filter(|merge| {
lowerable_guards.is_some()
|| predicates.iter().all(|predicate| {
let mut guards = Vec::new();
if extend_lowerable_predicate(predicate, &contract_use.source_expr, &mut guards)
.is_some()
{
return true;
}
let mut negated_paths = BTreeSet::new();
hard_negation_paths(predicate, &mut negated_paths);
negated_paths.iter().all(|path| {
merge.layers.iter().any(|layer| {
path == layer
|| helm_schema_core::values_path_is_descendant(path, layer)
|| helm_schema_core::values_path_is_descendant(layer, path)
})
})
})
});
let merge_outer_guards = merge_layered.map(|merge| {
let guards = lowerable_guards
.clone()
.unwrap_or_else(|| lowerable_conditional_guard_subset(contract_use, predicates));
collapse_layered_truthy_gates(guards, &merge.layers)
});
let lowerable_guards = if merge_layered.is_some() {
Some(vec![ConditionalGuard::Truthy {
path: contract_use.source_expr.clone(),
}])
} else {
lowerable_guards
};
if ranged_member_parent(&contract_use.source_expr).is_some()
&& predicates
.iter()
.any(|predicate| !matches!(predicate, Predicate::Guard(Guard::Range { .. })))
&& lowerable_guards.is_none()
{
return;
}
let has_source = !contract_use.source_expr.trim().is_empty();
let path_is_empty = contract_use.path.0.is_empty();
let range_guard_paths = predicates
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::Range { path }) => Some(path.clone()),
_ => None,
})
.collect::<BTreeSet<_>>();
let member_range_parent = contract_use.source_expr.strip_suffix(".*");
let self_range_guarded = range_guard_paths.contains(contract_use.source_expr.as_str());
let has_matching_self_guard = predicates
.iter()
.any(|predicate| predicate_is_self_guarding(predicate, &contract_use.source_expr));
let pathless_self_default_guarded = path_is_empty
&& predicates.iter().any(|predicate| {
matches!(predicate, Predicate::Guard(Guard::Default { path }) if path == &contract_use.source_expr)
});
let type_dispatched = has_source
&& predicates
.iter()
.any(|predicate| predicate_tests_source_type(predicate, &contract_use.source_expr));
let complement_dispatched = type_dispatched
&& predicates.iter().all(|predicate| {
!predicate_tests_source_type(predicate, &contract_use.source_expr)
|| matches!(
predicate,
Predicate::Not(inner)
if predicate_tests_source_type(inner, &contract_use.source_expr)
)
});
if has_source && has_approximate {
let serialized_tolerant = matches!(contract_use.kind, ValueKind::Serialized)
|| (contract_use.kind == ValueKind::PartialScalar && !path_is_empty)
|| type_dispatched;
if serialized_tolerant {
let acc = path_accumulator(paths, &contract_use.source_expr);
acc.referenced = true;
acc.facts.facts.used_as_serialized = true;
}
}
if has_source && !has_approximate {
let mut facts = ContractValuePathFacts {
used_as_fragment: matches!(
contract_use.kind,
ValueKind::Fragment | ValueKind::YamlSerialized
),
used_as_serialized: matches!(contract_use.kind, ValueKind::Serialized)
|| (contract_use.kind == ValueKind::PartialScalar && !path_is_empty)
|| type_dispatched,
used_as_yaml_serialized: contract_use.kind == ValueKind::YamlSerialized,
has_string_contract: contract_use.has_string_contract && !type_dispatched,
used_as_pathless_fragment: matches!(
contract_use.kind,
ValueKind::Fragment | ValueKind::YamlSerialized
) && path_is_empty,
is_partial_scalar_value_path: contract_use.kind == ValueKind::PartialScalar,
is_nullable: !path_is_empty
|| self_range_guarded
|| matches!(
contract_use.kind,
ValueKind::Fragment | ValueKind::YamlSerialized
)
|| pathless_self_default_guarded,
..ContractValuePathFacts::default()
};
if !path_is_empty {
let falsy_tolerant_use =
merge_layered.is_some() || contract_use.digest || contract_use.merge_operand;
facts.record_render_use(
self_range_guarded,
Some(has_matching_self_guard),
Some(has_matching_self_guard || falsy_tolerant_use),
);
facts.has_unconditional_render_use = predicates.is_empty();
}
let positive_header = contract_use.kind == ValueKind::Scalar
&& path_is_empty
&& !predicates.is_empty()
&& predicates.iter().all(|predicate| {
predicate_is_positive_header(predicate, &contract_use.source_expr)
});
let metadata_field_kind = if matches!(
contract_use.kind,
ValueKind::PartialScalar | ValueKind::Serialized
) || type_dispatched
{
None
} else {
metadata_field_kind_from_yaml_path(&contract_use.path.0)
};
let acc = path_accumulator(paths, &contract_use.source_expr);
acc.requiredness.is_positive_header |= positive_header;
let own_iteration_only = predicates.iter().all(|predicate| {
member_range_parent.is_some_and(|parent| {
matches!(
predicate,
Predicate::Guard(Guard::Range { path }) if path == parent
)
})
});
if contract_use.has_string_contract && own_iteration_only {
acc.type_hints.insert("string".to_string());
}
let structural_dispatch_arm = type_dispatched
&& matches!(
contract_use.kind,
ValueKind::Fragment | ValueKind::YamlSerialized
)
&& lowerable_guards.as_ref().is_some_and(|guards| {
guards.iter().any(|guard| {
matches!(
guard,
ConditionalGuard::TypeIs { schema_type, .. }
if schema_type == "object" || schema_type == "array"
)
})
});
let provider_use = (!type_dispatched || complement_dispatched || structural_dispatch_arm)
.then(|| provider_schema_use(contract_use, self_range_guarded))
.flatten()
.map(|mut provider_use| {
let keeps_layer_arms = provider_use.merge_layers.as_ref().is_some_and(|merge| {
!merge.via_binding || merge.nil_scrubbed_layers.iter().any(|scrubbed| *scrubbed)
});
if !keeps_layer_arms {
provider_use.merge_layers = None;
}
provider_use
});
let (branch_provider_use, merge_layer_provider_use) = if merge_layered.is_some() {
(None, provider_use)
} else {
(provider_use, None)
};
let layer_arms_carry_sink_typing = merge_layer_provider_use.is_some();
if let Some(mut layered) = merge_layer_provider_use {
layered.outer_guards = merge_outer_guards.unwrap_or_default();
acc.facts.facts.has_merge_layered_use = true;
acc.facts.record_provider_schema_use(layered);
}
let metadata_field_kind = if merge_layered.is_some() && layer_arms_carry_sink_typing {
None
} else {
metadata_field_kind
};
let (path_facts, branch_facts) = if structural_dispatch_arm {
let mut path_facts = facts;
path_facts.used_as_fragment = false;
path_facts.used_as_yaml_serialized = false;
path_facts.used_as_pathless_fragment = false;
let mut branch_facts = facts;
branch_facts.used_as_serialized = false;
(path_facts, branch_facts)
} else if contract_use.digest {
let mut path_facts = facts;
path_facts.used_as_serialized = false;
(path_facts, facts)
} else {
(facts, facts)
};
acc.record_source_use(
&SourceUseFactSplit {
path: path_facts,
branch: branch_facts,
},
path_is_empty || has_matching_self_guard,
lowerable_guards,
branch_provider_use,
metadata_field_kind,
predicates.iter().all(|predicate| {
predicate.value_paths().iter().all(|path| {
path == &contract_use.source_expr
|| contract_use.source_expr.strip_suffix(".*") == Some(path)
})
}),
);
}
for path in predicates
.iter()
.flat_map(Predicate::conditionally_optional_paths)
{
path_accumulator(paths, &path)
.requiredness
.is_conditionally_optional = true;
}
for path in predicates.iter().filter_map(|predicate| match predicate {
Predicate::Guard(Guard::Default { path }) => Some(path),
_ => None,
}) {
path_accumulator(paths, path)
.requiredness
.has_default_fallback = true;
}
if has_source {
for predicate in conditional_guard_predicates(predicates) {
for path in predicate.value_paths() {
let acc = path_accumulator(paths, &path);
if !acc.guard_predicates.contains(&predicate) {
acc.guard_predicates.push(predicate.clone());
}
}
}
}
for path in predicates.iter().flat_map(Predicate::value_paths) {
if has_source && path == contract_use.source_expr.as_str() {
continue;
}
let acc = path_accumulator(paths, &path);
acc.referenced |= has_source;
if !path_is_empty {
let mut facts = ContractValuePathFacts::default();
facts.record_render_use(range_guard_paths.contains(&path), None, None);
acc.facts.record_facts(facts);
}
}
if has_source && !has_approximate {
for path in range_guard_paths {
let direct = range_modes.mode(&path).direct;
let outer_guards = (direct && !has_selection_chain_marker_stamp(predicates))
.then(|| lowerable_range_outer_guards(&path, predicates))
.flatten();
let unconditional = outer_guards.as_ref().is_some_and(Vec::is_empty);
let facts = ContractValuePathFacts {
is_ranged_source: direct && unconditional,
is_direct_ranged_source: direct && unconditional,
has_destructured_range_use: direct && range_modes.mode(&path).destructured,
has_json_decoded_range_use: direct && range_modes.mode(&path).json_decoded,
is_nullable: true,
..ContractValuePathFacts::default()
};
path_accumulator(paths, &path).facts.record_facts(facts);
if direct {
if let Some(parent) = path.strip_suffix(".*")
&& !path_contains_wildcard(parent)
{
let parent_mode = range_modes.mode(parent);
let path_mode = range_modes.mode(&path);
record_member_range_requirement(
paths,
parent,
predicates,
!parent_mode.destructured && !parent_mode.json_decoded,
!path_mode.destructured && !path_mode.json_decoded,
);
} else if let Some(guards) = outer_guards {
let mode = range_modes.mode(&path);
record_guarded_range_requirement(
paths,
&path,
guards,
mode.destructured,
mode.json_decoded,
);
}
}
}
}
}
fn has_selection_chain_marker_stamp(predicates: &[Predicate]) -> bool {
let with_marker_paths: BTreeSet<&str> = predicates
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::With { path }) => Some(path.as_str()),
_ => None,
})
.collect();
let disjunction_paths = |predicate: &Predicate| -> Option<Vec<String>> {
match predicate {
Predicate::Guard(Guard::Or { paths }) => Some(paths.clone()),
Predicate::Or(alternatives) => alternatives
.iter()
.map(|alternative| match alternative {
Predicate::Guard(Guard::Truthy { path }) => Some(path.clone()),
_ => None,
})
.collect(),
_ => None,
}
};
with_marker_paths.len() > 1
&& predicates.iter().any(|predicate| {
disjunction_paths(predicate).is_some_and(|paths| {
with_marker_paths
.iter()
.all(|marker| paths.iter().any(|path| path == marker))
})
})
}
fn lowerable_range_outer_guards(
ranged_path: &str,
predicates: &[Predicate],
) -> Option<Vec<ConditionalGuard>> {
let mut guards = Vec::new();
for predicate in predicates {
if matches!(
predicate,
Predicate::Guard(Guard::Range { path }) if path == ranged_path
) || matches!(
predicate,
Predicate::Guard(Guard::Range { path })
if range_guard_is_iteration_ancestor(ranged_path, path)
) || predicate_is_structural_ancestor_guard(predicate, ranged_path)
{
continue;
}
if matches!(
predicate,
Predicate::Guard(Guard::Default { path }) if path == ranged_path
) {
continue;
}
let guard = predicate_to_guard(predicate, None)?;
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return None;
}
guards.push(guard);
}
guards.sort();
guards.dedup();
Some(guards)
}
fn record_guarded_range_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
ranged_path: &str,
outer_guards: Vec<ConditionalGuard>,
destructured: bool,
json_decoded: bool,
) {
if !outer_guards.is_empty() {
let branch = path_accumulator(paths, ranged_path)
.conditional_overlay_branches
.entry(outer_guards.clone())
.or_default();
branch.facts.is_nullable = true;
branch.record_facts(ContractValuePathFacts {
is_ranged_source: true,
has_destructured_range_use: destructured,
has_json_decoded_range_use: json_decoded,
is_nullable: true,
..ContractValuePathFacts::default()
});
}
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Value,
requirements: vec![FailValueRequirement::Iterable {
allow_integer: !destructured && !json_decoded,
}],
};
let acc = path_accumulator(paths, ranged_path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn remove_redundant_approximate_conditions(conjunction: &[Predicate]) -> Vec<Predicate> {
let exact = conjunction
.iter()
.filter(|predicate| !predicate.contains_approximation())
.collect::<BTreeSet<_>>();
conjunction
.iter()
.filter(|predicate| {
if !predicate.contains_approximation() {
return true;
}
!matches!(predicate, Predicate::Or(alternatives) if alternatives.iter().any(|alternative| {
match alternative {
Predicate::And(items) => items.iter().all(|item| exact.contains(item)),
item => exact.contains(item),
}
}))
})
.cloned()
.collect()
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn record_fail_conjunction(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
terminal_clauses: &mut Vec<Vec<ConditionalGuard>>,
capture: &crate::eval_effect::FailCapture,
range_modes: &crate::range_modes::RangeModes,
) {
if let crate::eval_effect::CaptureKind::RangeKeyStrings {
paths: range_key_string_paths,
} = &capture.kind
{
record_range_key_string_requirements(paths, capture, range_key_string_paths, range_modes);
return;
}
if let crate::eval_effect::CaptureKind::CollectionItems {
paths: collection_paths,
schema_type,
pattern,
} = &capture.kind
{
record_collection_item_requirements(
paths,
capture,
collection_paths,
schema_type,
pattern.as_deref(),
);
return;
}
if let crate::eval_effect::CaptureKind::IndexAccess { path, index } = &capture.kind {
record_index_access_requirement(paths, capture, path, *index);
return;
}
if let crate::eval_effect::CaptureKind::SplitIndexAccess {
paths: source_paths,
separator,
index,
total_text_preimage,
} = &capture.kind
{
record_split_index_access_requirement(
paths,
capture,
source_paths,
separator,
*index,
*total_text_preimage,
);
return;
}
if let crate::eval_effect::CaptureKind::ValueType { path, schema_type } = &capture.kind {
record_value_requirement_capture(
paths,
capture,
path,
FailValueRequirement::SchemaType(schema_type.clone()),
);
return;
}
if let crate::eval_effect::CaptureKind::RangeSelection {
path,
chain,
allow_integer,
} = &capture.kind
{
let mut capture = capture.clone();
capture.conjunction.retain(|predicate| {
!matches!(
predicate,
Predicate::Guard(Guard::With { path }) if chain.contains(path)
)
});
record_value_requirement_capture(
paths,
&capture,
path,
FailValueRequirement::Iterable {
allow_integer: *allow_integer,
},
);
return;
}
if let crate::eval_effect::CaptureKind::DigSubject { path } = &capture.kind {
record_value_requirement_capture(
paths,
capture,
path,
FailValueRequirement::SchemaTypeEvenNull("object".to_string()),
);
return;
}
if let crate::eval_effect::CaptureKind::RequiredPresence { path } = &capture.kind {
let mut segments = helm_schema_core::split_value_path(path);
let Some(member) = segments.pop() else {
return;
};
if segments.is_empty() {
return;
}
let parent = segments.join(".");
record_value_requirement_capture(
paths,
capture,
&parent,
FailValueRequirement::HasMemberEvenDefaulted(member),
);
return;
}
if let crate::eval_effect::CaptureKind::ComparableKind { path, schema_type } = &capture.kind {
record_value_requirement_capture(
paths,
capture,
path,
FailValueRequirement::ComparableKind(schema_type.clone()),
);
return;
}
if let crate::eval_effect::CaptureKind::ValuePattern {
path,
pattern,
templated,
} = &capture.kind
{
record_value_requirement_capture(
paths,
capture,
path,
FailValueRequirement::MatchesPattern {
pattern: pattern.clone(),
templated: *templated,
},
);
return;
}
if let crate::eval_effect::CaptureKind::QuotedSerialization { path, style } = &capture.kind {
record_value_requirement_capture(
paths,
capture,
path,
FailValueRequirement::QuotedSerializationSafe { style: *style },
);
return;
}
let conjunction = remove_redundant_approximate_conditions(&capture.conjunction);
if conjunction.iter().any(|predicate| {
predicate.contains_approximation() && fail_outer_guard(predicate).is_none()
}) {
return;
}
if conjunction
.iter()
.flat_map(Predicate::value_paths)
.any(|path| path.starts_with('$'))
{
return;
}
if record_range_key_prefix_requirement(paths, &capture.kind, &conjunction) {
return;
}
if record_range_key_matches_requirement(paths, &capture.kind, &conjunction) {
return;
}
if let crate::eval_effect::CaptureKind::MemberAccess { handled_kinds } = &capture.kind {
record_member_access_capture(paths, capture, handled_kinds, range_modes);
return;
}
let or_covered: BTreeSet<&str> = capture
.conjunction
.iter()
.filter_map(|predicate| match predicate {
Predicate::Or(items) => Some(items.iter().filter_map(|item| match item {
Predicate::Guard(Guard::Truthy { path } | Guard::With { path }) => {
Some(path.as_str())
}
_ => None,
})),
_ => None,
})
.flatten()
.collect();
let conjunction: Vec<Predicate> = conjunction
.iter()
.filter(|predicate| {
!matches!(
predicate,
Predicate::Guard(Guard::With { path }) if or_covered.contains(path.as_str())
)
})
.cloned()
.collect();
let conjunction = &conjunction;
let ranged: Vec<&str> = conjunction
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::Range { path }) => Some(path.as_str()),
_ => None,
})
.collect();
let test_candidate_paths = conjunction
.iter()
.filter(|predicate| predicate_is_negatable_test(predicate))
.flat_map(Predicate::value_paths)
.collect::<BTreeSet<_>>();
let ranged = ranged
.iter()
.copied()
.filter(|path| range_modes.mode(path).direct || capture.ranged.mode(path).direct)
.filter(|path| {
let member = format!("{path}.*");
test_candidate_paths.iter().any(|candidate| {
candidate == &member
|| helm_schema_core::values_path_is_descendant(candidate, &member)
})
})
.max_by_key(|path| helm_schema_core::split_value_path(path).len());
let member_scope = ranged.map(|path| format!("{path}.*"));
let mut outer_guards = Vec::new();
let mut member_tests: Vec<&Predicate> = Vec::new();
let mut requirements = Vec::new();
let mut test_paths: BTreeSet<String> = BTreeSet::new();
for predicate in conjunction {
if let Predicate::Guard(Guard::Range { path }) = predicate {
if ranged == Some(path.as_str()) {
continue;
}
if range_modes.mode(path).direct || capture.ranged.mode(path).direct {
outer_guards.push(ConditionalGuard::Truthy { path: path.clone() });
continue;
}
return;
}
let paths_of = predicate.value_paths();
if let Some(scope) = &member_scope {
if !paths_of.is_empty()
&& paths_of
.iter()
.all(|path| path == scope || path.starts_with(&format!("{scope}.")))
{
member_tests.push(predicate);
continue;
}
} else if paths_of.len() == 1 && predicate_is_negatable_test(predicate) {
let path = paths_of.iter().next().cloned().unwrap_or_default();
if let Some(required) =
requirements_from_negation(predicate, &path).filter(|required| !required.is_empty())
{
requirements.extend(required);
test_paths.insert(path);
continue;
}
}
let Some(guard) = fail_outer_guard(predicate) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
let mut member_field: Option<Vec<String>> = None;
if let Some(scope) = &member_scope
&& !member_tests.is_empty()
{
let requirements_at = |at: &str| -> Option<Vec<Vec<FailValueRequirement>>> {
member_tests
.iter()
.map(|predicate| {
requirements_from_negation(predicate, at)
.filter(|required| !required.is_empty())
})
.collect::<Option<Vec<_>>>()
};
let combine = |mut alternatives: Vec<Vec<FailValueRequirement>>| {
if alternatives.len() == 1 {
alternatives.remove(0)
} else {
alternatives.sort();
alternatives.dedup();
vec![FailValueRequirement::AnyOf(alternatives)]
}
};
if let Some(required) = requirements_at(scope) {
requirements.extend(combine(required));
test_paths.insert(scope.clone());
} else {
let field_path = {
let mut paths: BTreeSet<String> = member_tests
.iter()
.flat_map(|predicate| predicate.value_paths())
.collect();
match paths.pop_first() {
Some(path) if paths.is_empty() && !path[scope.len()..].contains('*') => {
Some(path)
}
_ => None,
}
};
let Some(field_path) = field_path.filter(|path| path != scope) else {
return;
};
let Some(required) = requirements_at(&field_path) else {
return;
};
if required
.iter()
.flatten()
.any(|requirement| matches!(requirement, FailValueRequirement::HelmFalsy))
{
return;
}
member_field = Some(helm_schema_core::split_value_path(
&field_path[scope.len() + 1..],
));
requirements.extend(combine(required));
test_paths.insert(field_path);
}
}
if requirements.is_empty() || test_paths.len() != 1 {
if ranged.is_none()
&& !conjunction
.iter()
.any(|predicate| matches!(predicate, Predicate::Guard(Guard::Range { .. })))
&& !conjunction.is_empty()
{
let clause = conjunction
.iter()
.map(terminal_clause_guard)
.collect::<Option<Vec<_>>>();
if let Some(mut clause) = clause {
clause.sort();
clause.dedup();
if !clause.is_empty() && !terminal_clauses.contains(&clause) {
terminal_clauses.push(clause);
}
}
}
return;
}
let target = if let Some(path) = ranged {
path.to_string()
} else {
let Some(path) = test_paths.into_iter().next() else {
return;
};
path
};
requirements.sort();
requirements.dedup();
let contradictory = requirements.iter().any(|requirement| {
matches!(
requirement,
FailValueRequirement::SchemaType(schema_type)
if requirements
.contains(&FailValueRequirement::NotSchemaType(schema_type.clone()))
)
});
if contradictory {
return;
}
outer_guards.sort();
outer_guards.dedup();
let implication = ContractFailImplication {
outer_guards,
target: ranged.map_or(ContractRequirementTarget::Value, |path| {
let mode = range_modes.mode(path);
let allow_integer = !mode.destructured && !mode.json_decoded;
match member_field {
Some(target_path) => ContractRequirementTarget::MembersAt {
target_path,
allow_integer,
},
None => ContractRequirementTarget::Members { allow_integer },
}
}),
requirements,
};
let acc = path_accumulator(paths, &target);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn record_range_key_prefix_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
kind: &crate::eval_effect::CaptureKind,
conjunction: &[Predicate],
) -> bool {
if !matches!(kind, crate::eval_effect::CaptureKind::Fail) {
return false;
}
let prefixes = conjunction
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::RangeKeyPrefix { path, prefix }) => {
Some((path.as_str(), prefix.as_str()))
}
_ => None,
})
.collect::<Vec<_>>();
let [(collection_path, prefix)] = prefixes.as_slice() else {
return !prefixes.is_empty();
};
let member_scope = format!("{collection_path}.*");
let has_matching_range = conjunction.iter().any(|predicate| {
matches!(predicate, Predicate::Guard(Guard::Range { path }) if path == collection_path)
});
if !has_matching_range {
return true;
}
let mut outer_guards = Vec::new();
let mut requirements = Vec::new();
for predicate in conjunction {
match predicate {
Predicate::Guard(Guard::RangeKeyPrefix {
path,
prefix: candidate,
}) if path == collection_path && candidate == prefix => {}
Predicate::Guard(Guard::Range { path }) if path == collection_path => {}
_ if {
let predicate_paths = predicate.value_paths();
!predicate_paths.is_empty()
&& predicate_paths.iter().all(|path| {
path == &member_scope
|| helm_schema_core::values_path_is_descendant(path, &member_scope)
})
} =>
{
let Some(mut required) = requirements_from_negation(predicate, &member_scope)
else {
return true;
};
requirements.append(&mut required);
}
_ => {
let Some(guard) = fail_outer_guard(predicate) else {
return true;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return true;
}
outer_guards.push(guard);
}
}
}
if requirements.is_empty() {
return true;
}
outer_guards.sort();
outer_guards.dedup();
requirements.sort();
requirements.dedup();
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::MembersMatchingPrefix {
prefix: (*prefix).to_string(),
},
requirements,
};
let acc = path_accumulator(paths, collection_path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
true
}
fn record_range_key_matches_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
kind: &crate::eval_effect::CaptureKind,
conjunction: &[Predicate],
) -> bool {
fn key_match(predicate: &Predicate) -> Option<(bool, &str, &str)> {
match predicate {
Predicate::Guard(Guard::RangeKeyMatches { path, pattern }) => {
Some((false, path.as_str(), pattern.as_str()))
}
Predicate::Not(inner) => match inner.as_ref() {
Predicate::Guard(Guard::RangeKeyMatches { path, pattern }) => {
Some((true, path.as_str(), pattern.as_str()))
}
_ => None,
},
_ => None,
}
}
if !matches!(kind, crate::eval_effect::CaptureKind::Fail) {
return false;
}
let matches: Vec<(bool, &str, &str)> = conjunction.iter().filter_map(key_match).collect();
let [(negated, collection_path, pattern)] = matches.as_slice() else {
return !matches.is_empty();
};
let has_matching_range = conjunction.iter().any(|predicate| {
matches!(predicate, Predicate::Guard(Guard::Range { path }) if path == collection_path)
});
if !has_matching_range {
return true;
}
let member_scope = format!("{collection_path}.*");
let mut outer_guards = Vec::new();
for predicate in conjunction {
if key_match(predicate).is_some() {
continue;
}
match predicate {
Predicate::Guard(Guard::Range { path }) if path == collection_path => {}
_ if predicate.value_paths().iter().any(|path| {
path == &member_scope
|| helm_schema_core::values_path_is_descendant(path, &member_scope)
}) =>
{
return true;
}
_ => {
let Some(guard) = fail_outer_guard(predicate) else {
return true;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return true;
}
outer_guards.push(guard);
}
}
}
outer_guards.sort();
outer_guards.dedup();
let requirement = if *negated {
FailValueRequirement::MatchesPattern {
pattern: (*pattern).to_string(),
templated: false,
}
} else {
FailValueRequirement::NotMatchesPattern {
pattern: (*pattern).to_string(),
}
};
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Keys,
requirements: vec![requirement],
};
let acc = path_accumulator(paths, collection_path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
true
}
fn capture_outer_guards(
capture: &crate::eval_effect::FailCapture,
) -> Option<Vec<ConditionalGuard>> {
let conjunction = remove_redundant_approximate_conditions(&capture.conjunction);
let key_equals_ranges: BTreeSet<&str> = conjunction
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::RangeKeyEquals { path, .. }) => Some(path.as_str()),
_ => None,
})
.collect();
let mut guards = conjunction
.iter()
.filter(|predicate| {
!matches!(
predicate,
Predicate::Guard(Guard::Range { path }) if key_equals_ranges.contains(path.as_str())
)
})
.map(|predicate| match predicate {
Predicate::Guard(Guard::Range { path }) => capture
.ranged
.mode(path)
.direct
.then(|| ConditionalGuard::Truthy { path: path.clone() }),
predicate => fail_outer_guard(predicate),
})
.collect::<Option<Vec<_>>>()?;
if guards
.iter()
.flat_map(ConditionalGuard::value_paths)
.any(|path| path_contains_wildcard(&path))
{
return None;
}
guards.sort();
guards.dedup();
Some(guards)
}
fn fail_outer_guard(predicate: &Predicate) -> Option<ConditionalGuard> {
if !predicate.contains_approximation() {
return predicate_to_guard(predicate, None);
}
match predicate {
Predicate::Approximate { sound_subset, .. } if !sound_subset.is_empty() => {
let guards = sound_subset
.iter()
.map(|guard| guard_to_conditional_guard(guard, None))
.collect::<Option<Vec<_>>>()?;
match guards.as_slice() {
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
}
}
Predicate::Not(inner) => {
let Predicate::And(items) = inner.as_ref() else {
return None;
};
let mut decodable: Vec<ConditionalGuard> = items
.iter()
.filter(|item| !item.contains_approximation())
.filter_map(|item| predicate_to_guard(item, None))
.collect();
decodable.sort();
decodable.dedup();
let inner = match decodable.as_slice() {
[] => return None,
[guard] => guard.clone(),
_ => ConditionalGuard::AllOf(decodable),
};
Some(ConditionalGuard::Not(Box::new(inner)))
}
Predicate::Or(items) => {
let mut guards = items
.iter()
.filter_map(fail_outer_guard)
.collect::<Vec<_>>();
guards.sort();
guards.dedup();
match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AnyOf(guards)),
}
}
Predicate::And(items) => {
let mut guards = items
.iter()
.map(fail_outer_guard)
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
}
}
_ => None,
}
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn record_value_requirement_capture(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
path: &str,
requirement: FailValueRequirement,
) {
if path.trim().is_empty() {
return;
}
let member_field_split = path.split_once(".*.").filter(|(collection, suffix)| {
!collection.contains('*') && !suffix.is_empty() && !suffix.contains('*')
});
if let Some((collection_path, member_suffix)) = member_field_split {
let conjunction = remove_redundant_approximate_conditions(&capture.conjunction);
let key_equals_ranges: BTreeSet<&str> = conjunction
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::RangeKeyEquals { path, .. }) => Some(path.as_str()),
_ => None,
})
.collect();
let mut outer_guards = Vec::new();
let mut self_truthy_selected = false;
for predicate in &conjunction {
match predicate {
Predicate::Guard(Guard::Range { path })
if path == collection_path || key_equals_ranges.contains(path.as_str()) => {}
Predicate::Guard(Guard::Truthy { path: guard_path }) if guard_path == path => {
self_truthy_selected = true;
}
_ => {
let Some(guard) = fail_outer_guard(predicate) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
}
}
let requirement = if self_truthy_selected {
match requirement {
FailValueRequirement::SchemaType(schema_type) => {
FailValueRequirement::TruthyImpliesSchemaType(schema_type)
}
_ => return,
}
} else {
requirement
};
outer_guards.sort();
outer_guards.dedup();
let allow_integer = {
let mode = capture.ranged.mode(collection_path);
mode.direct && !mode.destructured && !mode.json_decoded
};
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::MembersAt {
target_path: helm_schema_core::split_value_path(member_suffix),
allow_integer,
},
requirements: vec![requirement],
};
let acc = path_accumulator(paths, collection_path);
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
return;
}
let (target_path, target, outer_guards) = if let Some(collection_path) = path.strip_suffix(".*")
{
let mut outer_guards = Vec::new();
let mut prefix = None;
for predicate in &capture.conjunction {
match predicate {
Predicate::Guard(Guard::Range { path }) if path == collection_path => {}
Predicate::Guard(Guard::RangeKeyPrefix {
path,
prefix: candidate,
}) if path == collection_path => {
if prefix.replace(candidate.clone()).is_some() {
return;
}
}
_ => {
let Some(guard) = predicate_to_guard(predicate, None) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
}
}
outer_guards.sort();
outer_guards.dedup();
let allow_integer = {
let mode = capture.ranged.mode(collection_path);
mode.direct && !mode.destructured && !mode.json_decoded
};
let target = prefix.map_or(
ContractRequirementTarget::Members { allow_integer },
|prefix| ContractRequirementTarget::MembersMatchingPrefix { prefix },
);
(collection_path, target, outer_guards)
} else {
if path_contains_wildcard(path) {
return;
}
let Some(outer_guards) = capture_outer_guards(capture) else {
return;
};
(path, ContractRequirementTarget::Value, outer_guards)
};
let implication = ContractFailImplication {
outer_guards,
target,
requirements: vec![requirement],
};
let acc = path_accumulator(paths, target_path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn record_collection_item_requirements(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
collection_paths: &BTreeSet<String>,
schema_type: &str,
pattern: Option<&str>,
) {
let Some(outer_guards) = capture_outer_guards(capture) else {
return;
};
for path in collection_paths {
if path_contains_wildcard(path) {
continue;
}
let mut requirements = vec![FailValueRequirement::SchemaType(schema_type.to_string())];
if let Some(pattern) = pattern {
requirements.push(FailValueRequirement::MatchesPattern {
pattern: pattern.to_string(),
templated: false,
});
}
let implication = ContractFailImplication {
outer_guards: outer_guards.clone(),
target: ContractRequirementTarget::Members {
allow_integer: false,
},
requirements,
};
let acc = path_accumulator(paths, path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
}
fn record_index_access_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
path: &str,
index: usize,
) {
if path.trim().is_empty() || path_contains_wildcard(path) {
return;
}
let Some(outer_guards) = capture_outer_guards(capture) else {
return;
};
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Value,
requirements: vec![FailValueRequirement::IndexableAt(index)],
};
let acc = path_accumulator(paths, path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn record_split_index_access_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
source_paths: &BTreeSet<String>,
separator: &str,
index: usize,
allow_non_string: bool,
) {
if index == 0 || separator.is_empty() {
return;
}
let outer_guards = capture_outer_guards(capture);
for path in source_paths {
if path.trim().is_empty() {
continue;
}
if path_contains_wildcard(path) {
record_member_relative_split_requirement(
paths,
capture,
path,
separator,
index,
allow_non_string,
);
continue;
}
let Some(outer_guards) = outer_guards.clone() else {
continue;
};
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Value,
requirements: vec![FailValueRequirement::SplitSegmentsAtLeast {
separator: separator.to_string(),
segments: index + 1,
allow_non_string,
}],
};
let acc = path_accumulator(paths, path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
}
fn record_member_relative_split_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
source_path: &str,
separator: &str,
index: usize,
allow_non_string: bool,
) {
let segments = helm_schema_core::split_value_path(source_path);
let Some(member_index) = segments.iter().rposition(|segment| segment == "*") else {
return;
};
if member_index == 0 || member_index + 1 >= segments.len() {
return;
}
let Some(collection_segments) = segments.get(..member_index) else {
return;
};
let Some(member_segments) = segments.get(..=member_index) else {
return;
};
let Some(target_path) = segments.get(member_index + 1..) else {
return;
};
let collection_path = helm_schema_core::join_value_path(collection_segments.to_vec());
let member_scope = helm_schema_core::join_value_path(member_segments.to_vec());
let target_path = target_path.to_vec();
let mut member_guards = Vec::new();
let mut outer_guards = Vec::new();
for predicate in &capture.conjunction {
if matches!(predicate, Predicate::Guard(Guard::Range { path })
if path == &collection_path
|| helm_schema_core::values_path_is_descendant(&member_scope, path))
{
continue;
}
if let Predicate::Guard(Guard::Eq { path, value }) = predicate
&& let Some(relative) =
helm_schema_core::split_value_path(path).strip_prefix(member_segments)
&& !relative.is_empty()
&& !relative.iter().any(|segment| segment == "*")
{
member_guards.push((relative.to_vec(), value.clone()));
continue;
}
let Some(guard) = predicate_to_guard(predicate, None) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
let [(guard_path, value)] = member_guards.as_slice() else {
return;
};
outer_guards.sort();
outer_guards.dedup();
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::MembersWhereEquals {
guard_path: guard_path.clone(),
value: value.clone(),
target_path,
},
requirements: vec![FailValueRequirement::SplitSegmentsAtLeast {
separator: separator.to_string(),
segments: index + 1,
allow_non_string,
}],
};
let acc = path_accumulator(paths, &collection_path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn record_range_key_string_requirements(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
range_key_string_paths: &BTreeSet<String>,
range_modes: &crate::range_modes::RangeModes,
) {
if capture.contains_approximation() {
return;
}
for path in range_key_string_paths {
if path_contains_wildcard(path)
|| (!range_modes.mode(path).direct && !capture.ranged.mode(path).direct)
|| has_selection_chain_marker_stamp(&capture.conjunction)
{
continue;
}
let Some(outer_guards) = lowerable_range_outer_guards(path, &capture.conjunction) else {
continue;
};
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Keys,
requirements: vec![FailValueRequirement::SchemaType("string".to_string())],
};
let acc = path_accumulator(paths, path);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
}
fn predicate_is_negatable_test(predicate: &Predicate) -> bool {
match predicate {
Predicate::Not(inner) => !matches!(inner.as_ref(), Predicate::Guard(Guard::Range { .. })),
Predicate::Guard(Guard::TypeIs { .. } | Guard::Absent { .. } | Guard::Eq { .. }) => true,
Predicate::Guard(Guard::NotEq { path, .. } | Guard::Truthy { path }) => path.contains(".*"),
Predicate::Or(items) => items.iter().all(predicate_is_negatable_test),
_ => false,
}
}
fn requirements_from_negation(
predicate: &Predicate,
scope: &str,
) -> Option<Vec<FailValueRequirement>> {
match predicate {
Predicate::Not(inner) => requirements_from_holding(inner, scope),
Predicate::Or(items) => {
let mut requirements = Vec::new();
for item in items {
requirements.append(&mut requirements_from_negation(item, scope)?);
}
Some(requirements)
}
Predicate::Guard(Guard::TypeIs { path, schema_type }) if path == scope => {
Some(vec![FailValueRequirement::NotSchemaType(
schema_type.clone(),
)])
}
Predicate::Guard(Guard::Absent { path }) => {
let member = path.strip_prefix(&format!("{scope}."))?;
(!member.contains('.'))
.then(|| vec![FailValueRequirement::HasMember(member.to_string())])
}
Predicate::Guard(Guard::Truthy { path }) if scope.contains(".*") => {
if path == scope {
return Some(vec![FailValueRequirement::HelmFalsy]);
}
let field = path.strip_prefix(&format!("{scope}."))?;
(!field.contains('*')).then(|| {
vec![FailValueRequirement::FieldHelmFalsy {
path: helm_schema_core::split_value_path(field),
}]
})
}
Predicate::Guard(Guard::NotEq { path, value }) if scope.contains(".*") => {
let field = path.strip_prefix(&format!("{scope}."))?;
(!field.contains('*')).then(|| {
vec![FailValueRequirement::FieldEquals {
path: helm_schema_core::split_value_path(field),
value: value.clone(),
}]
})
}
Predicate::Guard(Guard::Eq { path, value }) => match value {
GuardValue::String(text)
if path == scope && scope.contains(".*") && !text.is_empty() =>
{
Some(vec![FailValueRequirement::NotEquals(value.clone())])
}
GuardValue::Int(_) | GuardValue::Bool(_) if path == scope && scope.contains(".*") => {
Some(vec![FailValueRequirement::NotEquals(value.clone())])
}
GuardValue::String(text) if scope.contains(".*") && path != scope => {
if text.is_empty() {
return Some(Vec::new());
}
let field = path.strip_prefix(&format!("{scope}."))?;
(!field.contains('*')).then(|| {
vec![FailValueRequirement::FieldNotEquals {
path: helm_schema_core::split_value_path(field),
value: value.clone(),
}]
})
}
GuardValue::Int(_) | GuardValue::Bool(_) if scope.contains(".*") && path != scope => {
let field = path.strip_prefix(&format!("{scope}."))?;
(!field.contains('*')).then(|| {
vec![FailValueRequirement::FieldNotEquals {
path: helm_schema_core::split_value_path(field),
value: value.clone(),
}]
})
}
_ => Some(Vec::new()),
},
_ => None,
}
}
fn requirements_from_holding(
predicate: &Predicate,
scope: &str,
) -> Option<Vec<FailValueRequirement>> {
match predicate {
Predicate::Guard(Guard::TypeIs { path, schema_type }) if path == scope => {
Some(vec![FailValueRequirement::SchemaType(schema_type.clone())])
}
Predicate::Guard(Guard::MatchesPattern {
path,
pattern,
templated,
}) if path == scope => Some(vec![FailValueRequirement::MatchesPattern {
pattern: pattern.clone(),
templated: *templated,
}]),
Predicate::Guard(Guard::Absent { path }) if path == scope => Some(Vec::new()),
Predicate::Guard(Guard::Truthy { path }) if path == scope => {
if scope.contains(".*") {
Some(vec![FailValueRequirement::HelmTruthy])
} else {
Some(Vec::new())
}
}
Predicate::Guard(Guard::Truthy { path }) => {
let member = path.strip_prefix(&format!("{scope}."))?;
if scope.contains(".*") {
return (!member.contains('*')).then(|| {
vec![FailValueRequirement::FieldHelmTruthy {
path: helm_schema_core::split_value_path(member),
}]
});
}
(!member.contains('.'))
.then(|| vec![FailValueRequirement::HasMember(member.to_string())])
}
Predicate::Guard(Guard::Eq { path, value }) if scope.contains(".*") => {
let field = path.strip_prefix(&format!("{scope}."))?;
(!field.contains('*')).then(|| {
vec![FailValueRequirement::FieldEquals {
path: helm_schema_core::split_value_path(field),
value: value.clone(),
}]
})
}
Predicate::And(items) => {
let mut requirements = Vec::new();
for item in items {
requirements.append(&mut requirements_from_holding(item, scope)?);
}
Some(requirements)
}
Predicate::Not(inner) => match inner.as_ref() {
Predicate::Guard(Guard::Absent { path }) => {
let member = path.strip_prefix(&format!("{scope}."))?;
(!member.contains('.'))
.then(|| vec![FailValueRequirement::HasMember(member.to_string())])
}
_ => requirements_from_negation(inner, scope),
},
_ => None,
}
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn record_member_access_capture(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
capture: &crate::eval_effect::FailCapture,
handled_kinds: &BTreeSet<String>,
range_modes: &crate::range_modes::RangeModes,
) {
if capture.contains_approximation() {
return;
}
let mut target = None;
for predicate in &capture.conjunction {
if let Predicate::Not(inner) = predicate
&& let Predicate::Guard(Guard::TypeIs { path, schema_type }) = inner.as_ref()
&& schema_type == "object"
{
target = Some(path.clone());
}
}
let Some(target) = target else {
return;
};
if let Some(parent) = target.strip_suffix(".*")
&& !path_contains_wildcard(parent)
{
if !capture.ranged.mode(parent).direct {
return;
}
let mut outer_guards = Vec::new();
for predicate in &capture.conjunction {
if matches!(
predicate,
Predicate::Guard(Guard::Range { path }) if path == parent
) || matches!(
predicate,
Predicate::Not(inner)
if matches!(
inner.as_ref(),
Predicate::Guard(Guard::TypeIs { path, schema_type })
if path == &target && schema_type == "object"
)
) {
continue;
}
let Some(guard) = predicate_to_guard(predicate, None) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
outer_guards.sort();
outer_guards.dedup();
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Members {
allow_integer: {
let mode = range_modes.mode(parent);
let capture_mode = capture.ranged.mode(parent);
!mode.destructured
&& !capture_mode.destructured
&& !mode.json_decoded
&& !capture_mode.json_decoded
},
},
requirements: vec![FailValueRequirement::SchemaType("object".to_string())],
};
let acc = path_accumulator(paths, parent);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
return;
}
if path_contains_wildcard(&target) {
return;
}
let mut outer = Vec::new();
for predicate in &capture.conjunction {
match predicate {
Predicate::Not(inner)
if matches!(
inner.as_ref(),
Predicate::Guard(Guard::TypeIs { path, schema_type })
if path == &target && schema_type == "object"
) =>
{
continue;
}
Predicate::Guard(Guard::With { path }) if !path_contains_wildcard(path) => {
outer.push(ConditionalGuard::Truthy { path: path.clone() });
continue;
}
_ => {}
}
let Some(guard) = predicate_to_guard(predicate, None) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer.push(guard);
}
outer.sort();
outer.dedup();
path_accumulator(paths, &target)
.member_access_guard_sets
.entry(handled_kinds.iter().cloned().collect())
.or_default()
.insert(outer);
}
type MemberAccessGuardSets = BTreeMap<Vec<String>, BTreeSet<Vec<ConditionalGuard>>>;
fn factor_guard_sets(sets: BTreeSet<Vec<ConditionalGuard>>) -> BTreeSet<Vec<ConditionalGuard>> {
let mut sets: Vec<Vec<ConditionalGuard>> = sets.into_iter().collect();
loop {
let mut merged = None;
'search: for left in 0..sets.len() {
for right in left + 1..sets.len() {
let Some(left_set) = sets.get(left) else {
continue;
};
let Some(right_set) = sets.get(right) else {
continue;
};
if let Some(folded) = fold_activation_pair(left_set, right_set) {
merged = Some((left, right, folded));
break 'search;
}
}
}
let Some((left, right, folded)) = merged else {
break;
};
sets.remove(right);
if let Some(left_set) = sets.get_mut(left) {
*left_set = folded;
}
}
sets.into_iter().collect()
}
fn fold_activation_pair(
left: &[ConditionalGuard],
right: &[ConditionalGuard],
) -> Option<Vec<ConditionalGuard>> {
let only_left: Vec<&ConditionalGuard> =
left.iter().filter(|guard| !right.contains(guard)).collect();
let only_right: Vec<&ConditionalGuard> =
right.iter().filter(|guard| !left.contains(guard)).collect();
let ([left_guard], [right_guard]) = (only_left.as_slice(), only_right.as_slice()) else {
return None;
};
let activation_pair = |a: &ConditionalGuard, b: &ConditionalGuard| match (a, b) {
(
ConditionalGuard::Truthy { path: truthy_path },
ConditionalGuard::Absent { path: absent_path },
) => (truthy_path == absent_path).then(|| {
let mut alternatives = vec![a.clone(), b.clone()];
alternatives.sort();
ConditionalGuard::AnyOf(alternatives)
}),
_ => None,
};
let folded_guard = activation_pair(left_guard, right_guard)
.or_else(|| activation_pair(right_guard, left_guard))?;
let mut folded: Vec<ConditionalGuard> = left
.iter()
.filter(|guard| guard != left_guard)
.cloned()
.collect();
folded.push(folded_guard);
folded.sort();
folded.dedup();
Some(folded)
}
fn guard_implies_present(guard: &ConditionalGuard, path: &str) -> bool {
match guard {
ConditionalGuard::Truthy { path: guarded } | ConditionalGuard::With { path: guarded } => {
guarded == path
}
ConditionalGuard::TypeIs {
path: guarded,
schema_type,
} => guarded == path && schema_type != "null",
ConditionalGuard::HasKey { path: host, key } => {
helm_schema_core::append_value_path(host, key) == path
}
ConditionalGuard::Not(inner) => {
matches!(inner.as_ref(), ConditionalGuard::Absent { path: guarded } if guarded == path)
}
ConditionalGuard::AllOf(set) => set.iter().any(|guard| guard_implies_present(guard, path)),
ConditionalGuard::AnyOf(set) => set.iter().all(|guard| guard_implies_present(guard, path)),
_ => false,
}
}
fn record_member_access_implications(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
terminal_clauses: &mut Vec<Vec<ConditionalGuard>>,
) {
const MEMBER_ACCESS_GUARD_FANOUT: usize = 8;
let dependency_roots: BTreeSet<String> = paths
.iter()
.filter(|(_, acc)| acc.facts.facts.accepted_dependency_values_root_fragment)
.map(|(path, _)| path.clone())
.collect();
let pending: Vec<(String, MemberAccessGuardSets)> = paths
.iter()
.filter(|(path, acc)| {
!acc.member_access_guard_sets.is_empty() && !path_contains_wildcard(path)
})
.map(|(path, acc)| {
let factored = acc
.member_access_guard_sets
.iter()
.map(|(kinds, sets)| (kinds.clone(), factor_guard_sets(sets.clone())))
.collect();
(path.clone(), factored)
})
.collect();
for (path, grouped_guard_sets) in pending {
let capped = |guard_sets: &BTreeSet<Vec<ConditionalGuard>>| {
!guard_sets.contains(&Vec::new()) && guard_sets.len() > MEMBER_ACCESS_GUARD_FANOUT
};
let fold_guards = |guard_sets: BTreeSet<Vec<ConditionalGuard>>| {
let mut outer_guards = Vec::new();
if guard_sets.contains(&Vec::new()) {
return outer_guards;
}
let mut arms: Vec<ConditionalGuard> = guard_sets
.into_iter()
.map(|mut set| {
if set.len() == 1 {
set.remove(0)
} else {
ConditionalGuard::AllOf(set)
}
})
.collect();
if arms.len() == 1 {
match arms.remove(0) {
ConditionalGuard::AllOf(set) => outer_guards.extend(set),
guard => outer_guards.push(guard),
}
} else {
outer_guards.push(ConditionalGuard::AnyOf(arms));
}
outer_guards.sort();
outer_guards.dedup();
outer_guards
};
for (handled_kinds, guard_sets) in &grouped_guard_sets {
if capped(guard_sets) {
continue;
}
let outer_guards = fold_guards(guard_sets.clone());
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Value,
requirements: vec![FailValueRequirement::MemberHost {
handled_kinds: handled_kinds.clone(),
}],
};
let acc = path_accumulator(paths, &path);
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
if dependency_roots
.iter()
.any(|root| *root == path || path.starts_with(&format!("{root}.")))
{
continue;
}
let absent_abort_sets = grouped_guard_sets
.into_values()
.flatten()
.filter(|guards| {
!guards
.iter()
.any(|guard| guard_implies_present(guard, &path))
})
.collect::<BTreeSet<_>>();
if absent_abort_sets.is_empty() || capped(&absent_abort_sets) {
continue;
}
let mut clause = fold_guards(absent_abort_sets);
clause.push(ConditionalGuard::Absent { path });
clause.sort();
clause.dedup();
if !terminal_clauses.contains(&clause) {
terminal_clauses.push(clause);
}
}
}
fn finish_schema_signals(
mut paths: BTreeMap<String, ContractPathAccumulator>,
mut terminal_clauses: Vec<Vec<ConditionalGuard>>,
) -> ContractSchemaSignals {
record_member_access_implications(&mut paths, &mut terminal_clauses);
let referenced_paths = paths
.iter()
.filter_map(|(path, acc)| acc.referenced.then_some(path.clone()))
.collect();
let (
paths_with_referenced_descendants,
paths_with_item_descendants,
paths_with_structured_item_descendants,
) = collect_paths_with_descendants(&referenced_paths);
for path in &paths_with_referenced_descendants {
path_accumulator(&mut paths, path);
}
let string_contract_item_parents: Vec<String> = paths
.iter()
.filter_map(|(path, acc)| {
let parent = path.strip_suffix(".*")?;
(acc.facts.facts.has_string_contract || acc.type_hints.contains("string"))
.then(|| parent.to_string())
})
.collect();
for parent in string_contract_item_parents {
path_accumulator(&mut paths, &parent)
.facts
.facts
.has_string_contract_items = true;
}
let schema_evidence_by_value_path = paths
.into_iter()
.map(|(value_path, acc)| {
let has_descendants = paths_with_referenced_descendants.contains(&value_path);
let has_item_descendants = paths_with_item_descendants.contains(&value_path);
let has_structured_item_descendants =
paths_with_structured_item_descendants.contains(&value_path);
let evidence = acc.into_schema_evidence(
value_path.clone(),
has_descendants,
has_item_descendants,
has_structured_item_descendants,
);
(value_path, evidence)
})
.collect();
terminal_clauses.sort();
terminal_clauses.dedup();
ContractSchemaSignals::new(schema_evidence_by_value_path, terminal_clauses)
}
fn path_accumulator<'a>(
paths: &'a mut BTreeMap<String, ContractPathAccumulator>,
path: &str,
) -> &'a mut ContractPathAccumulator {
paths.entry(path.to_string()).or_default()
}
struct SourceUseFactSplit {
path: ContractValuePathFacts,
branch: ContractValuePathFacts,
}
impl ContractPathAccumulator {
fn record_source_use(
&mut self,
facts: &SourceUseFactSplit,
source_null_tolerant: bool,
lowerable_guards: Option<Vec<ConditionalGuard>>,
provider_schema_use: Option<ProviderSchemaUse>,
metadata_field_kind: Option<MetadataFieldKind>,
self_scoped: bool,
) {
self.referenced = true;
if lowerable_guards.is_none() {
self.saw_unsupported_overlay = true;
return;
}
self.facts.record_facts(facts.path);
let row_forms_overlay_branch = facts.path.has_render_use
&& !facts.path.has_unconditional_render_use
&& lowerable_guards
.as_ref()
.is_some_and(|guards| !guards.is_empty());
if row_forms_overlay_branch {
if self_scoped && let Some(provider_use) = provider_schema_use.clone() {
self.guarded_provider_schema_uses.push(provider_use);
}
if self_scoped && let Some(field_kind) = metadata_field_kind {
self.guarded_metadata_field_kinds.insert(field_kind);
}
} else {
if let Some(provider_use) = provider_schema_use.clone() {
self.facts.record_provider_schema_use(provider_use);
}
self.facts.record_metadata_field_kind(metadata_field_kind);
}
if facts.path.has_render_use {
if facts.path.has_unconditional_render_use
|| lowerable_guards.as_ref().is_some_and(Vec::is_empty)
{
self.has_unconditional_overlay_peer = true;
} else if let Some(guards) = lowerable_guards {
let branch = self.conditional_overlay_branches.entry(guards).or_default();
branch.facts.is_nullable = true;
branch.record_nullable_observation(source_null_tolerant);
branch.record_metadata_field_kind(metadata_field_kind);
branch.record_facts(facts.branch);
if let Some(provider_schema_use) = provider_schema_use {
branch.record_provider_schema_use(provider_schema_use);
}
}
}
self.facts.record_nullable_observation(source_null_tolerant);
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn into_schema_evidence(
self,
value_path: String,
has_referenced_descendants: bool,
has_item_descendants: bool,
has_structured_item_descendants: bool,
) -> ContractPathSchemaEvidence {
let facts = self.facts.facts(
has_referenced_descendants,
has_item_descendants,
has_structured_item_descendants,
);
let ContractPathAccumulator {
referenced,
guard_predicates,
facts: mut path_facts,
requiredness,
type_hints,
guarded_type_hints,
fallback_type_hints,
guarded_fallback_type_hints,
guarded_provider_schema_uses,
guarded_metadata_field_kinds,
conditional_overlay_branches,
mut has_unconditional_overlay_peer,
saw_unsupported_overlay,
mut fail_implications,
member_access_guard_sets: _,
} = self;
if !facts.used_as_serialized {
for provider_use in guarded_provider_schema_uses {
path_facts.record_provider_schema_use(provider_use);
}
path_facts
.metadata_field_kinds
.extend(guarded_metadata_field_kinds);
}
let overlay_type_hints: BTreeSet<String> = type_hints
.iter()
.chain(guarded_type_hints.iter())
.chain(fallback_type_hints.iter())
.chain(guarded_fallback_type_hints.iter())
.cloned()
.collect();
let contract_type_hints: BTreeSet<String> = type_hints
.iter()
.chain(guarded_type_hints.iter())
.cloned()
.collect();
let mut evidence_groups: Vec<(PathSchemaFactsAccumulator, Vec<Vec<ConditionalGuard>>)> =
Vec::new();
for (guards, branch) in conditional_overlay_branches {
if let Some((_, guard_sets)) = evidence_groups
.iter_mut()
.find(|(evidence, _)| evidence == &branch)
{
guard_sets.push(guards);
} else {
evidence_groups.push((branch, vec![guards]));
}
}
let mut conditional_overlay_branches: BTreeMap<
Vec<ConditionalGuard>,
PathSchemaFactsAccumulator,
> = BTreeMap::new();
for (branch, guard_sets) in evidence_groups {
for guards in
helm_schema_core::GuardDnf::normalize_conditional_guard_disjunction(guard_sets)
{
if guards.is_empty() {
has_unconditional_overlay_peer = true;
continue;
}
match conditional_overlay_branches.entry(guards) {
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().merge_union(branch.clone());
}
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(branch.clone());
}
}
}
}
let conditional_overlays = conditional_overlay_branches
.into_iter()
.map(|(guards, branch)| {
let branch_hint_pool =
if branch.facts.used_as_serialized && !branch.facts.has_string_contract {
&contract_type_hints
} else {
&overlay_type_hints
};
let branch_hints =
partition_compatible_hints(branch_hint_pool, &guards, value_path.as_str());
ConditionalPathOverlay {
guards,
evidence: branch.conditional_overlay_evidence(facts, branch_hints),
preserve_base_schema: has_unconditional_overlay_peer || saw_unsupported_overlay,
}
})
.collect();
fail_implications.sort();
fail_implications.dedup();
let mut guarded_type_hints = guarded_type_hints;
guarded_type_hints.extend(guarded_fallback_type_hints);
ContractPathSchemaEvidence {
value_path,
is_referenced_value_path: referenced,
facts,
guard_predicates,
metadata_field_kinds: path_facts.metadata_field_kinds,
type_hints,
guarded_type_hints,
fallback_type_hints,
provider_schema_uses: path_facts.provider_schema_uses,
requiredness,
conditional_overlays,
fail_implications,
}
}
}
fn metadata_field_kind_from_yaml_path(path: &[String]) -> Option<MetadataFieldKind> {
if path.get(path.len().checked_sub(2)?)?.as_str() != "metadata" {
return None;
}
match path.last()?.as_str() {
"labels" | "annotations" => Some(MetadataFieldKind::StringMap),
"name" => Some(MetadataFieldKind::Name),
"namespace" => Some(MetadataFieldKind::Namespace),
_ => None,
}
}
fn conditional_guard_predicates(predicates: &[Predicate]) -> Vec<ConditionalGuard> {
let mut guards = predicates
.iter()
.filter_map(|predicate| predicate_to_guard(predicate, None))
.collect::<Vec<_>>();
guards.sort();
guards.dedup();
guards
}
fn hard_negation_paths(predicate: &Predicate, out: &mut BTreeSet<String>) {
match predicate {
Predicate::Not(inner) => {
if !matches!(inner.as_ref(), Predicate::Guard(Guard::Absent { .. })) {
out.extend(inner.value_paths());
}
}
Predicate::And(items) | Predicate::Or(items) => {
for item in items {
hard_negation_paths(item, out);
}
}
_ => {}
}
}
fn lowerable_conditional_guard_set(
contract_use: &ContractUse,
predicates: &[Predicate],
) -> Option<Vec<ConditionalGuard>> {
let key_equals_ranges: BTreeSet<&str> = predicates
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::RangeKeyEquals { path, .. }) => Some(path.as_str()),
_ => None,
})
.collect();
let mut guards = Vec::new();
for predicate in predicates {
if matches!(
predicate,
Predicate::Guard(Guard::Range { path })
if path == &contract_use.source_expr
|| range_guard_is_iteration_ancestor(&contract_use.source_expr, path)
|| key_equals_ranges.contains(path.as_str())
) {
continue;
}
extend_lowerable_predicate(predicate, &contract_use.source_expr, &mut guards)?;
}
guards.sort();
guards.dedup();
Some(guards)
}
fn collapse_layered_truthy_gates(
guards: Vec<ConditionalGuard>,
layers: &[String],
) -> Vec<ConditionalGuard> {
let split: Vec<Vec<&str>> = layers
.iter()
.map(|layer| layer.split('.').collect())
.collect();
let Some(first) = split.first() else {
return guards;
};
let mut common = 0;
'suffix: while common < first.len() {
let candidate = first
.len()
.checked_sub(1 + common)
.and_then(|index| first.get(index));
let Some(candidate) = candidate else {
break;
};
for segments in &split {
let segment = segments
.len()
.checked_sub(1 + common)
.filter(|&index| index > 0)
.and_then(|index| segments.get(index));
if segment != Some(candidate) {
break 'suffix;
}
}
common += 1;
}
let roots: Vec<String> = split
.iter()
.map(|segments| {
let keep = segments.len().saturating_sub(common);
segments.get(..keep).unwrap_or_default().join(".")
})
.collect();
let concrete_layers: Vec<&String> = roots
.iter()
.filter(|layer| !layer.split('.').any(|segment| segment == "*"))
.collect();
let has_wildcard_layer = concrete_layers.len() != roots.len();
let mut suffix_members: BTreeMap<&str, BTreeSet<usize>> = BTreeMap::new();
for (index, guard) in guards.iter().enumerate() {
let ConditionalGuard::Truthy { path } = guard else {
continue;
};
for layer in &concrete_layers {
let Some(suffix) = path
.strip_prefix(layer.as_str())
.and_then(|rest| rest.strip_prefix('.'))
else {
continue;
};
if !suffix.is_empty() {
suffix_members.entry(suffix).or_default().insert(index);
}
}
}
let mut dropped = BTreeSet::new();
let mut replacements = Vec::new();
for members in suffix_members.into_values() {
if members.len() < 2 || members.iter().any(|index| dropped.contains(index)) {
continue;
}
dropped.extend(members.iter().copied());
if !has_wildcard_layer {
let mut arms: Vec<ConditionalGuard> = members
.iter()
.filter_map(|&index| guards.get(index).cloned())
.collect();
arms.sort();
arms.dedup();
replacements.push(ConditionalGuard::AnyOf(arms));
}
}
if dropped.is_empty() {
return guards;
}
let mut out: Vec<ConditionalGuard> = guards
.into_iter()
.enumerate()
.filter(|(index, _)| !dropped.contains(index))
.map(|(_, guard)| guard)
.collect();
out.extend(replacements);
out.sort();
out.dedup();
out
}
fn lowerable_conditional_guard_subset(
contract_use: &ContractUse,
predicates: &[Predicate],
) -> Vec<ConditionalGuard> {
let key_equals_ranges: BTreeSet<&str> = predicates
.iter()
.filter_map(|predicate| match predicate {
Predicate::Guard(Guard::RangeKeyEquals { path, .. }) => Some(path.as_str()),
_ => None,
})
.collect();
let mut guards = Vec::new();
for predicate in predicates {
if matches!(
predicate,
Predicate::Guard(Guard::Range { path })
if path == &contract_use.source_expr
|| range_guard_is_iteration_ancestor(&contract_use.source_expr, path)
|| key_equals_ranges.contains(path.as_str())
) {
continue;
}
let mut lowered = Vec::new();
if extend_lowerable_predicate(predicate, &contract_use.source_expr, &mut lowered).is_some()
{
guards.extend(lowered);
}
}
guards.sort();
guards.dedup();
guards
}
fn provider_schema_use(
contract_use: &ContractUse,
self_range_guarded: bool,
) -> Option<ProviderSchemaUse> {
let nil_omitting_ranged_leaf = contract_use.kind == ValueKind::Serialized
&& contract_use.nil_omitting
&& contract_use
.source_expr
.split('.')
.any(|segment| segment == "*");
if contract_use.source_expr.trim().is_empty()
|| (matches!(
contract_use.kind,
ValueKind::PartialScalar | ValueKind::Serialized
) && !nil_omitting_ranged_leaf)
|| contract_use.path.0.is_empty()
|| (contract_use.has_string_contract
&& contract_use.kind == ValueKind::Scalar
&& contract_use.split_segment.is_none())
{
return None;
}
let resource = contract_use.resource.clone()?;
Some(ProviderSchemaUse {
value_path: contract_use.source_expr.clone(),
path: contract_use.path.clone(),
kind: contract_use.kind,
resource,
template_supplied_member_keys: contract_use.template_supplied_member_keys.clone(),
split_segment: contract_use.split_segment.clone(),
merge_layers: contract_use.merge_layers.clone(),
range_key: contract_use.range_key,
nil_omitting: contract_use.nil_omitting,
omitted_members: contract_use
.omitted_members
.iter()
.map(|(key, retain_guards)| {
let guards = retain_guards
.iter()
.map(|guard| guard_to_conditional_guard(guard, None))
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
(key.clone(), guards)
})
.collect(),
is_self_range_collection: self_range_guarded
&& contract_use
.path
.0
.last()
.is_none_or(|segment| !segment.ends_with("[*]")),
outer_guards: Vec::new(),
})
}
fn predicate_to_guard(
predicate: &Predicate,
target_value_path: Option<&str>,
) -> Option<ConditionalGuard> {
match predicate {
Predicate::True | Predicate::False | Predicate::Approximate { .. } => None,
Predicate::Guard(guard) => guard_to_conditional_guard(guard, target_value_path),
Predicate::Not(inner) => {
if matches!(
inner.as_ref(),
Predicate::Guard(Guard::RangeKeyEquals { .. })
) {
return None;
}
Some(ConditionalGuard::Not(Box::new(predicate_to_guard(
inner, None,
)?)))
}
Predicate::And(predicates) => {
if let Some(contains) = existential_member_guard(predicates) {
return Some(contains);
}
let mut guards = predicates
.iter()
.map(|predicate| predicate_to_guard(predicate, target_value_path))
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
}
}
Predicate::Or(predicates) => {
let mut guards = predicates
.iter()
.map(|predicate| predicate_to_guard(predicate, None))
.collect::<Option<Vec<_>>>()?;
if guards
.iter()
.flat_map(ConditionalGuard::value_paths)
.any(|path| path_contains_wildcard(&path))
{
return None;
}
guards.sort();
guards.dedup();
(target_value_path.is_some() || !guards.is_empty())
.then_some(ConditionalGuard::AnyOf(guards))
}
}
}
fn extend_lowerable_predicate(
predicate: &Predicate,
target_value_path: &str,
out: &mut Vec<ConditionalGuard>,
) -> Option<()> {
match predicate {
Predicate::True
| Predicate::False
| Predicate::Approximate { .. }
| Predicate::Guard(Guard::Range { .. }) => return None,
Predicate::Guard(Guard::With { path }) if path == target_value_path => {}
Predicate::Guard(Guard::With { .. }) => {
out.push(predicate_to_guard(predicate, None)?);
}
Predicate::And(predicates) => {
for predicate in predicates {
extend_lowerable_predicate(predicate, target_value_path, out)?;
}
}
Predicate::Guard(Guard::Default { path }) if path == target_value_path => {}
Predicate::Guard(Guard::Truthy { path }) if path == target_value_path => {}
Predicate::Not(inner)
if matches!(
inner.as_ref(),
Predicate::Guard(Guard::Truthy { path }) if path == target_value_path
) => {}
other if predicate_is_self_type_partition(other, target_value_path) => {
let target = if path_contains_wildcard(target_value_path) {
None
} else {
Some(target_value_path)
};
out.push(predicate_to_guard(other, target)?);
}
other => {
out.push(predicate_to_guard(other, Some(target_value_path))?);
}
}
Some(())
}
fn existential_member_guard(predicates: &[Predicate]) -> Option<ConditionalGuard> {
fn flatten<'a>(predicates: &'a [Predicate], out: &mut Vec<&'a Predicate>) {
for predicate in predicates {
match predicate {
Predicate::True => {}
Predicate::And(inner) => flatten(inner, out),
other => out.push(other),
}
}
}
let mut conjuncts = Vec::new();
flatten(predicates, &mut conjuncts);
let [a, b] = conjuncts.as_slice() else {
return None;
};
let ((
Predicate::Guard(Guard::Range { path: range_path }),
Predicate::Guard(Guard::Eq {
path: eq_path,
value,
}),
)
| (
Predicate::Guard(Guard::Eq {
path: eq_path,
value,
}),
Predicate::Guard(Guard::Range { path: range_path }),
)) = (a, b)
else {
return None;
};
let member = eq_path
.strip_prefix(range_path.as_str())?
.strip_prefix(".*.")?;
if member.is_empty() || member.contains('.') || member.contains('*') {
return None;
}
Some(ConditionalGuard::ContainsMemberEquals {
path: range_path.clone(),
member: member.to_string(),
value: value.clone(),
})
}
fn terminal_clause_guard(predicate: &Predicate) -> Option<ConditionalGuard> {
if let Predicate::Approximate { sound_subset, .. } = predicate
&& !sound_subset.is_empty()
{
let mut guards = sound_subset
.iter()
.map(|guard| guard_to_conditional_guard(guard, None))
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
return match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
};
}
if let Predicate::Or(items) = predicate
&& predicate.contains_approximation()
{
let mut guards = items
.iter()
.map(terminal_clause_guard)
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
return match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AnyOf(guards)),
};
}
if let Predicate::And(items) = predicate
&& predicate.contains_approximation()
{
let mut guards = items
.iter()
.map(terminal_clause_guard)
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
return match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
};
}
predicate_to_guard(predicate, None)
}
#[expect(
clippy::too_many_lines,
reason = "keeping this semantic operation together makes its state transitions easier to audit"
)]
fn guard_to_conditional_guard(
guard: &Guard,
target_value_path: Option<&str>,
) -> Option<ConditionalGuard> {
let path = |path: &str| match target_value_path {
Some(target_value_path) => lowerable_guard_path(path, target_value_path),
None => Some(path.to_string()),
};
match guard {
Guard::Truthy { path: value_path } => Some(ConditionalGuard::Truthy {
path: path(value_path)?,
}),
Guard::With { path: value_path } if target_value_path.is_none() => {
Some(ConditionalGuard::With {
path: path(value_path)?,
})
}
Guard::Eq {
path: value_path,
value,
} => Some(ConditionalGuard::Eq {
path: path(value_path)?,
value: value.clone(),
}),
Guard::NotEq {
path: value_path,
value,
} => Some(ConditionalGuard::NotEq {
path: path(value_path)?,
value: value.clone(),
}),
Guard::Absent { path: value_path } => Some(ConditionalGuard::Absent {
path: path(value_path)?,
}),
Guard::HasKey {
path: value_path,
key,
} => Some(ConditionalGuard::HasKey {
path: path(value_path)?,
key: key.clone(),
}),
Guard::ContainsEquals {
path: value_path,
value,
} => Some(ConditionalGuard::ContainsEquals {
path: path(value_path)?,
value: value.clone(),
}),
Guard::MatchesPattern {
path: value_path,
pattern,
templated: false,
} => Some(ConditionalGuard::MatchesPattern {
path: path(value_path)?,
pattern: pattern.clone(),
}),
Guard::MatchesPattern { .. }
| Guard::RangeKeyPrefix { .. }
| Guard::RangeKeyMatches { .. }
| Guard::Range { .. }
| Guard::With { .. }
| Guard::Default { .. } => None,
Guard::AtMostOneMember { path: value_path } => Some(ConditionalGuard::AtMostOneMember {
path: path(value_path)?,
}),
Guard::MinMembers {
path: value_path,
bound,
} => {
let path = if target_value_path == Some(value_path.as_str()) {
(!path_contains_wildcard(value_path)).then(|| value_path.clone())?
} else {
path(value_path)?
};
Some(ConditionalGuard::MinMembers {
path,
bound: *bound,
})
}
Guard::TypeIs {
path: value_path,
schema_type,
} => {
let path = if target_value_path == Some(value_path.as_str()) {
(!path_contains_wildcard(value_path)).then(|| value_path.clone())?
} else {
path(value_path)?
};
Some(ConditionalGuard::TypeIs {
path,
schema_type: schema_type.clone(),
})
}
Guard::NotTypeIs {
path: value_path,
schema_type,
} => {
let path = if target_value_path == Some(value_path.as_str()) {
(!path_contains_wildcard(value_path)).then(|| value_path.clone())?
} else {
path(value_path)?
};
Some(ConditionalGuard::Not(Box::new(ConditionalGuard::TypeIs {
path,
schema_type: schema_type.clone(),
})))
}
Guard::IntGt {
path: value_path,
bound,
} => Some(ConditionalGuard::IntGt {
path: path(value_path)?,
bound: *bound,
}),
Guard::IntLt {
path: value_path,
bound,
} => Some(ConditionalGuard::IntLt {
path: path(value_path)?,
bound: *bound,
}),
Guard::RangeKeyEquals {
path: value_path,
key,
} => {
if key.is_empty() {
return None;
}
Some(ConditionalGuard::HasKey {
path: path(value_path)?,
key: key.clone(),
})
}
Guard::Not { path: value_path } => {
Some(ConditionalGuard::Not(Box::new(ConditionalGuard::Truthy {
path: path(value_path)?,
})))
}
Guard::Or { paths } => Some(ConditionalGuard::AnyOf(
paths
.iter()
.map(|value_path| {
Some(ConditionalGuard::Truthy {
path: path(value_path)?,
})
})
.collect::<Option<Vec<_>>>()?,
)),
Guard::AnyOf { alternatives } => Some(ConditionalGuard::AnyOf(
alternatives
.iter()
.map(|alternative| {
let mut guards = alternative
.iter()
.map(|guard| guard_to_conditional_guard(guard, target_value_path))
.collect::<Option<Vec<_>>>()?;
guards.sort();
guards.dedup();
match guards.as_slice() {
[] => None,
[guard] => Some(guard.clone()),
_ => Some(ConditionalGuard::AllOf(guards)),
}
})
.collect::<Option<Vec<_>>>()?,
)),
}
}
fn predicate_is_self_guarding(predicate: &Predicate, source_expr: &str) -> bool {
matches!(
predicate,
Predicate::Guard(
Guard::Truthy { path }
| Guard::Eq { path, .. }
| Guard::Range { path }
| Guard::With { path }
| Guard::Default { path }
) if path == source_expr
)
}
fn predicate_is_self_presence(predicate: &Predicate, source_expr: &str) -> bool {
matches!(
predicate,
Predicate::Not(inner)
if matches!(
inner.as_ref(),
Predicate::Guard(Guard::Absent { path }) if path == source_expr
)
)
}
fn record_member_range_requirement(
paths: &mut BTreeMap<String, ContractPathAccumulator>,
parent: &str,
predicates: &[Predicate],
outer_allows_integer: bool,
inner_allows_integer: bool,
) {
let mut outer_guards = Vec::new();
for predicate in predicates {
if matches!(
predicate,
Predicate::Guard(Guard::Range { path }) if path == parent || path == &format!("{parent}.*")
) {
continue;
}
let Some(guard) = predicate_to_guard(predicate, None) else {
return;
};
if guard
.value_paths()
.iter()
.any(|path| path_contains_wildcard(path))
{
return;
}
outer_guards.push(guard);
}
outer_guards.sort();
outer_guards.dedup();
let implication = ContractFailImplication {
outer_guards,
target: ContractRequirementTarget::Members {
allow_integer: outer_allows_integer,
},
requirements: vec![FailValueRequirement::Iterable {
allow_integer: inner_allows_integer,
}],
};
let acc = path_accumulator(paths, parent);
acc.referenced = true;
if !acc.fail_implications.contains(&implication) {
acc.fail_implications.push(implication);
}
}
fn predicate_tests_source_type(predicate: &Predicate, source_expr: &str) -> bool {
match predicate {
Predicate::Guard(Guard::TypeIs { path, .. }) => path == source_expr,
Predicate::Not(inner) => predicate_tests_source_type(inner, source_expr),
Predicate::And(items) | Predicate::Or(items) => items
.iter()
.any(|item| predicate_tests_source_type(item, source_expr)),
Predicate::True
| Predicate::False
| Predicate::Approximate { .. }
| Predicate::Guard(_) => false,
}
}
fn predicate_is_self_type_partition(predicate: &Predicate, target_value_path: &str) -> bool {
match predicate {
Predicate::Guard(Guard::TypeIs { path, .. }) => path == target_value_path,
Predicate::Not(inner) => predicate_is_self_type_partition(inner, target_value_path),
Predicate::And(items) | Predicate::Or(items) => {
!items.is_empty()
&& items
.iter()
.all(|item| predicate_is_self_type_partition(item, target_value_path))
}
Predicate::True
| Predicate::False
| Predicate::Approximate { .. }
| Predicate::Guard(_) => false,
}
}
fn predicate_is_positive_header(predicate: &Predicate, source_expr: &str) -> bool {
matches!(
predicate,
Predicate::Guard(Guard::Truthy { path }
| Guard::Eq { path, .. }
| Guard::TypeIs { path, .. }) if path == source_expr
)
}
fn lowerable_guard_path(path: &str, target_value_path: &str) -> Option<String> {
(!path_contains_wildcard(path) && path != target_value_path).then(|| path.to_string())
}
fn path_contains_wildcard(path: &str) -> bool {
helm_schema_core::split_value_path(path)
.iter()
.any(|segment| segment == "*")
}
fn ranged_member_parent(path: &str) -> Option<&str> {
path.strip_suffix(".*")
.or_else(|| path.split_once(".*.").map(|(parent, _)| parent))
}
fn range_guard_is_iteration_ancestor(source_path: &str, guard_path: &str) -> bool {
let source_segments = helm_schema_core::split_value_path(source_path);
let guard_segments = helm_schema_core::split_value_path(guard_path);
source_segments.len() > guard_segments.len()
&& source_segments.starts_with(&guard_segments)
&& source_segments
.get(guard_segments.len())
.is_some_and(|segment| segment == "*")
}
fn predicate_is_structural_ancestor_guard(predicate: &Predicate, source_path: &str) -> bool {
let Predicate::Guard(Guard::Truthy { path } | Guard::With { path }) = predicate else {
return false;
};
let source_segments = helm_schema_core::split_value_path(source_path);
let guard_segments = helm_schema_core::split_value_path(path);
source_segments.len() > guard_segments.len() && source_segments.starts_with(&guard_segments)
}
fn collect_paths_with_descendants(
paths: &BTreeSet<String>,
) -> (BTreeSet<String>, BTreeSet<String>, BTreeSet<String>) {
let mut ancestors = BTreeSet::new();
let mut item_ancestors = BTreeSet::new();
let mut structured_item_ancestors = BTreeSet::new();
for path in paths {
let segments = helm_schema_core::split_value_path(path);
for prefix_len in 1..segments.len() {
let Some(prefix) = segments.get(..prefix_len) else {
continue;
};
let Some(segment) = segments.get(prefix_len) else {
continue;
};
let ancestor = helm_schema_core::join_value_path(prefix);
if segment == "*" {
item_ancestors.insert(ancestor.clone());
if prefix_len + 1 < segments.len() {
structured_item_ancestors.insert(ancestor.clone());
}
}
ancestors.insert(ancestor);
}
}
(ancestors, item_ancestors, structured_item_ancestors)
}