1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::contract::FinalizedContract;
4use crate::contract_normalization::{
5 canonicalize_contract_uses, drop_default_guard_subsumed_duplicates,
6 drop_self_truthy_subsumed_duplicates, normalize_contract_uses,
7};
8use crate::observed_facts::{
9 ActivatedValuesDefaultSource, ActivatedValuesRootOverlay, HintGrade, ObservedFacts,
10};
11use crate::{ContractUse, Guard, ValueKind, YamlPath};
12
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
18pub struct ContractIr {
19 uses: Vec<ContractUse>,
20 dependency_uses: Vec<ContractUse>,
21 observed_facts: ObservedFacts,
22 values_program_wrappers: BTreeSet<helm_schema_core::ValuesProgramWrapper>,
23 values_program_wrapper_exclusions: BTreeSet<helm_schema_core::ValuesPath>,
28 dependency_values_root_fragments: BTreeSet<String>,
29}
30
31impl ContractIr {
32 #[must_use]
39 pub fn from_contract_uses(uses: Vec<ContractUse>) -> Self {
40 Self {
41 uses,
42 ..Self::default()
43 }
44 }
45
46 pub(crate) fn push(&mut self, contract_use: ContractUse) {
47 self.uses.push(contract_use);
48 }
49
50 pub(crate) fn push_dependency_use(&mut self, contract_use: ContractUse) {
51 self.dependency_uses.push(contract_use);
52 }
53
54 pub fn push_pathless_scalar(&mut self, source_expr: impl Into<String>) {
59 let source_expr = source_expr.into();
60 self.push(ContractUse::new(
61 helm_schema_core::ValuesPath::parse(&source_expr),
62 YamlPath(Vec::new()),
63 ValueKind::Scalar,
64 Vec::new(),
65 None,
66 ));
67 }
68
69 pub fn push_pathless_dependency_fragment(&mut self, source_expr: impl Into<String>) {
71 self.dependency_values_root_fragments
72 .insert(source_expr.into());
73 }
74
75 pub fn append(&mut self, mut other: Self) {
77 self.uses.append(&mut other.uses);
78 self.dependency_uses.append(&mut other.dependency_uses);
79 self.dependency_values_root_fragments
80 .append(&mut other.dependency_values_root_fragments);
81 self.observed_facts.absorb(&other.observed_facts);
82 self.values_program_wrappers
83 .append(&mut other.values_program_wrappers);
84 self.values_program_wrapper_exclusions
85 .append(&mut other.values_program_wrapper_exclusions);
86 }
87
88 pub fn add_terminal_fail_condition(&mut self, condition: helm_schema_core::Predicate) {
93 let capture = crate::eval_effect::FailCapture {
94 conjunction: vec![condition],
95 ranged: crate::range_modes::RangeModes::default(),
96 kind: crate::eval_effect::CaptureKind::Fail,
97 };
98 self.observed_facts.captures.insert(capture);
99 }
100
101 pub fn append_guards_to_all_uses(&mut self, guards: &[Guard]) {
107 for contract_use in self.uses.iter_mut().chain(&mut self.dependency_uses) {
108 contract_use.condition = contract_use
109 .condition
110 .conjoined_with_guards(guards.iter().cloned());
111 }
112 self.observed_facts.captures = std::mem::take(&mut self.observed_facts.captures)
116 .into_iter()
117 .map(|mut capture| {
118 capture.conjunction.splice(
119 0..0,
120 guards
121 .iter()
122 .cloned()
123 .map(helm_schema_core::Predicate::from),
124 );
125 capture
126 })
127 .collect();
128 if !guards.is_empty() {
129 let values_default_sources =
130 std::mem::take(&mut self.observed_facts.values_default_sources)
131 .into_iter()
132 .filter(|source| {
133 self.uses.iter().chain(&self.dependency_uses).any(|use_| {
134 !use_.path.0.is_empty()
135 && use_.source_expr != source.target_path
136 && use_.source_expr != source.source_path
137 && !use_.source_expr.is_descendant_of(&source.source_path)
138 })
139 })
140 .collect::<Vec<_>>();
141 let append_guards = |mut existing: Vec<Guard>| {
142 existing.extend(guards.iter().cloned());
143 existing.sort();
144 existing.dedup();
145 existing
146 };
147 self.observed_facts.activated_values_default_sources =
148 std::mem::take(&mut self.observed_facts.activated_values_default_sources)
149 .into_iter()
150 .map(|fact| ActivatedValuesDefaultSource {
151 guards: append_guards(fact.guards),
152 source: fact.source,
153 })
154 .chain(values_default_sources.into_iter().map(|source| {
155 ActivatedValuesDefaultSource {
156 guards: append_guards(Vec::new()),
157 source,
158 }
159 }))
160 .collect();
161 self.observed_facts.activated_values_root_overlays =
162 std::mem::take(&mut self.observed_facts.activated_values_root_overlays)
163 .into_iter()
164 .map(|fact| ActivatedValuesRootOverlay {
165 guards: append_guards(fact.guards),
166 target_path: fact.target_path,
167 source_path: fact.source_path,
168 })
169 .chain(
170 std::mem::take(&mut self.observed_facts.values_root_overlays)
171 .into_iter()
172 .map(|fact| ActivatedValuesRootOverlay {
173 guards: append_guards(Vec::new()),
174 target_path: fact.target_path,
175 source_path: fact.source_path,
176 }),
177 )
178 .collect();
179 }
180 }
181
182 pub fn mark_rendered_output_textual(&mut self) {
186 for contract_use in self.uses.iter_mut().chain(&mut self.dependency_uses) {
187 contract_use.kind = ValueKind::Serialized;
188 }
189 }
190
191 pub fn map_value_paths<F>(&mut self, mut map: F)
197 where
198 F: FnMut(helm_schema_core::ValuesPath) -> helm_schema_core::ValuesPath,
199 {
200 let Self {
201 uses,
202 dependency_uses,
203 observed_facts,
204 values_program_wrappers,
205 values_program_wrapper_exclusions,
206 dependency_values_root_fragments,
207 } = self;
208 for contract_use in uses.iter_mut().chain(dependency_uses) {
209 contract_use.map_value_paths(&mut map);
210 }
211 *dependency_values_root_fragments = std::mem::take(dependency_values_root_fragments)
212 .into_iter()
213 .map(|path| map(helm_schema_core::ValuesPath::parse(&path)).encode())
214 .collect();
215 observed_facts.map_value_paths(&mut map);
216 *values_program_wrappers = std::mem::take(values_program_wrappers)
217 .into_iter()
218 .map(|wrapper| helm_schema_core::ValuesProgramWrapper {
219 scope_path: map(wrapper.scope_path),
220 key: wrapper.key,
221 spread: wrapper.spread,
222 })
223 .collect();
224 *values_program_wrapper_exclusions = std::mem::take(values_program_wrapper_exclusions)
225 .into_iter()
226 .map(&mut map)
227 .collect();
228 }
229
230 pub fn project_dependency_global_contracts(&mut self, prefix: &[String]) {
233 if prefix.is_empty() {
234 return;
235 }
236 let global_sources = dependency_global_sources(prefix);
237 project_global_uses(&mut self.uses, &global_sources);
238 project_global_uses(&mut self.dependency_uses, &global_sources);
239 project_global_fail_captures(&mut self.observed_facts.captures, &global_sources);
240 project_global_range_modes(&mut self.observed_facts.range_modes, &global_sources);
241 }
242
243 pub fn add_type_hint(&mut self, path: impl Into<String>, schema_type: impl Into<String>) {
246 let path = path.into();
247 let schema_type = schema_type.into();
248 if path.trim().is_empty() || schema_type.trim().is_empty() {
249 return;
250 }
251 self.observed_facts.insert_type_hint(
252 HintGrade::DECLARED,
253 helm_schema_core::ValuesPath::parse(&path),
254 &schema_type,
255 );
256 }
257
258 pub(crate) fn absorb_observed_facts(&mut self, facts: &ObservedFacts) {
259 self.observed_facts.absorb(facts);
260 }
261
262 pub(crate) fn extend_values_program_wrappers(
263 &mut self,
264 wrappers: impl IntoIterator<Item = helm_schema_core::ValuesProgramWrapper>,
265 ) {
266 self.values_program_wrappers.extend(wrappers);
267 }
268
269 pub(crate) fn extend_values_program_wrapper_exclusions(
270 &mut self,
271 paths: impl IntoIterator<Item = helm_schema_core::ValuesPath>,
272 ) {
273 self.values_program_wrapper_exclusions.extend(paths);
274 }
275
276 pub(crate) fn scrub_program_wrapper_sentinel_evidence(&mut self) {
283 let keys: std::collections::BTreeSet<String> = self
284 .values_program_wrappers
285 .iter()
286 .map(|wrapper| wrapper.key.clone())
287 .collect();
288 if keys.is_empty() {
289 return;
290 }
291 let touches = |path: &helm_schema_core::ValuesPath| {
292 path.segments()
293 .filter_map(helm_schema_core::Segment::literal)
294 .any(|segment| keys.contains(segment))
295 };
296 self.uses
297 .retain(|contract_use| !touches(&contract_use.source_expr));
298 self.dependency_uses
299 .retain(|contract_use| !touches(&contract_use.source_expr));
300 self.observed_facts.captures.retain(|capture| {
301 let mut paths: Vec<helm_schema_core::ValuesPath> = capture
302 .conjunction
303 .iter()
304 .flat_map(helm_schema_core::Predicate::value_paths)
305 .collect();
306 let mut kind = capture.kind.clone();
307 kind.map_value_paths(&mut |path| {
308 paths.push(path.clone());
309 path
310 });
311 !paths.iter().any(touches)
312 });
313 }
314
315 #[must_use]
318 #[tracing::instrument(skip_all)]
319 pub fn finalize(mut self) -> FinalizedContract {
320 self.scrub_program_wrapper_sentinel_evidence();
321 let Self {
322 mut uses,
323 mut dependency_uses,
324 observed_facts,
325 values_program_wrappers,
326 values_program_wrapper_exclusions,
327 dependency_values_root_fragments,
328 } = self;
329 for source_expr in &dependency_values_root_fragments {
330 dependency_uses.push(ContractUse::new(
331 helm_schema_core::ValuesPath::parse(source_expr),
332 YamlPath(Vec::new()),
333 ValueKind::Fragment,
334 Vec::new(),
335 None,
336 ));
337 }
338 normalize_contract_uses(&mut uses);
339 drop_self_truthy_subsumed_duplicates(&mut dependency_uses);
340 canonicalize_contract_uses(&mut dependency_uses);
341 uses.append(&mut dependency_uses);
342 drop_default_guard_subsumed_duplicates(&mut uses);
343 drop_self_truthy_subsumed_duplicates(&mut uses);
344 canonicalize_contract_uses(&mut uses);
345 let fail_conditions = observed_facts.captures.iter().cloned().collect::<Vec<_>>();
346 lower_string_requirement_merge_sources(&mut uses, &fail_conditions);
347 canonicalize_contract_uses(&mut uses);
348 FinalizedContract::new(
349 uses,
350 &observed_facts,
351 values_program_wrappers,
352 values_program_wrapper_exclusions,
353 &dependency_values_root_fragments,
354 )
355 }
356}
357
358fn string_requirements_by_ancestor(
359 fail_conditions: &[crate::eval_effect::FailCapture],
360) -> BTreeMap<String, BTreeSet<(String, Vec<helm_schema_core::Predicate>)>> {
361 let mut requirements: BTreeMap<String, BTreeSet<(String, Vec<helm_schema_core::Predicate>)>> =
362 BTreeMap::new();
363 for capture in fail_conditions {
364 let crate::eval_effect::CaptureKind::StringRequirement {
365 path,
366 route,
367 selection,
368 } = &capture.kind
369 else {
370 continue;
371 };
372 if *route == crate::eval_effect::StringRequirementRoute::Serialized {
373 continue;
374 }
375 let mut predicates = capture.conjunction.clone();
376 predicates.extend(selection.iter().cloned());
377 predicates.sort();
378 predicates.dedup();
379 let segments = path
380 .segments()
381 .map(helm_schema_core::Segment::encode_component)
382 .collect::<Vec<_>>();
383 for end in 1..segments.len() {
384 requirements
385 .entry(helm_schema_core::join_encoded_value_path(
386 segments.get(..end).unwrap_or_default().iter().cloned(),
387 ))
388 .or_default()
389 .insert((path.encode(), predicates.clone()));
390 }
391 }
392 requirements
393}
394
395fn lower_string_requirement_merge_sources(
396 uses: &mut [ContractUse],
397 fail_conditions: &[crate::eval_effect::FailCapture],
398) {
399 let requirements_by_ancestor = string_requirements_by_ancestor(fail_conditions);
400
401 let mut overlap_cache = BTreeMap::new();
402
403 for contract_use in uses.iter_mut() {
404 let Some(merge) = &contract_use.merge_layers else {
405 continue;
406 };
407 let source_expr = contract_use.source_expr.encode();
408 let Some(requirements) = requirements_by_ancestor.get(&source_expr) else {
409 continue;
410 };
411 let source_segments = contract_use
412 .source_expr
413 .segments()
414 .map(helm_schema_core::Segment::encode_component)
415 .collect::<Vec<_>>();
416 let specific_layers = merge
417 .layers()
418 .iter()
419 .map(|layer| {
420 layer
421 .path
422 .segments()
423 .map(helm_schema_core::Segment::encode_component)
424 .collect::<Vec<_>>()
425 })
426 .filter(|layer| {
427 layer.len() > source_segments.len() && layer.starts_with(source_segments.as_slice())
428 })
429 .collect::<Vec<_>>();
430 let [first, second, rest @ ..] = specific_layers.as_slice() else {
431 continue;
432 };
433 let mut common_suffix_len = first
434 .iter()
435 .rev()
436 .zip(second.iter().rev())
437 .take_while(|(left, right)| left == right)
438 .count();
439 for layer in rest {
440 common_suffix_len = common_suffix_len.min(
441 first
442 .iter()
443 .rev()
444 .zip(layer.iter().rev())
445 .take_while(|(left, right)| left == right)
446 .count(),
447 );
448 }
449 let suffix = first.get(first.len().saturating_sub(common_suffix_len)..);
450 let Some(suffix) = suffix.filter(|suffix| {
451 !suffix.is_empty() && suffix.iter().all(|segment| segment.as_str() != "*")
452 }) else {
453 continue;
454 };
455 let Some(requirements) =
456 merge_suffix_string_requirements(&source_expr, requirements, suffix)
457 else {
458 continue;
459 };
460 let cache_key = (
461 contract_use.source_expr.clone(),
462 suffix.to_vec(),
463 contract_use.condition.clone(),
464 );
465 let overlaps_requirement = overlap_cache.get(&cache_key).copied().unwrap_or_else(|| {
466 let overlaps = contract_use.condition.disjuncts().iter().any(|row| {
467 requirements.iter().any(|requirement| {
468 !helm_schema_core::GuardDnf::from_conjunction(
469 row.iter().cloned().chain(requirement.iter().cloned()),
470 )
471 .is_never()
472 })
473 });
474 overlap_cache.insert(cache_key, overlaps);
475 overlaps
476 });
477 if !overlaps_requirement {
478 continue;
479 }
480 let source = helm_schema_core::ValuesPath::parse(&source_expr);
481 let projected =
482 helm_schema_core::ValuesPath::parse(&helm_schema_core::join_encoded_value_path(
483 source_segments
484 .iter()
485 .cloned()
486 .chain(suffix.iter().cloned()),
487 ));
488 contract_use.map_value_paths(&mut |path| {
489 if path == source {
490 projected.clone()
491 } else {
492 path
493 }
494 });
495 }
496}
497
498fn merge_suffix_string_requirements(
499 source: &str,
500 requirements: &BTreeSet<(String, Vec<helm_schema_core::Predicate>)>,
501 suffix: &[String],
502) -> Option<BTreeSet<Vec<helm_schema_core::Predicate>>> {
503 let source_segments = helm_schema_core::split_value_path(source);
504 let mut paths = BTreeSet::new();
505 let mut conjunctions = BTreeSet::new();
506 for (path, predicates) in requirements {
507 let segments = helm_schema_core::split_value_path(path);
508 if segments.len() <= source_segments.len()
509 || !segments.starts_with(source_segments.as_slice())
510 || !segments.ends_with(suffix)
511 {
512 continue;
513 }
514 paths.insert(segments);
515 conjunctions.insert(predicates.clone());
516 }
517 (paths.len() >= 2).then_some(conjunctions)
518}
519
520fn dependency_global_sources(prefix: &[String]) -> Vec<String> {
521 (0..=prefix.len())
522 .map(|prefix_len| {
523 helm_schema_core::join_value_path(
524 prefix
525 .get(..prefix_len)
526 .unwrap_or_default()
527 .iter()
528 .cloned()
529 .chain(std::iter::once("global".to_string())),
530 )
531 })
532 .collect()
533}
534
535fn project_global_uses(uses: &mut Vec<ContractUse>, global_sources: &[String]) {
536 let Some(dependency_global) = global_sources.last() else {
537 return;
538 };
539 let dependency_global_segments = helm_schema_core::split_value_path(dependency_global);
540 let mut projected = Vec::with_capacity(uses.len());
541 for dependency_use in std::mem::take(uses) {
542 let source_segments = dependency_use
543 .source_expr
544 .segments()
545 .map(helm_schema_core::Segment::encode_component)
546 .collect::<Vec<_>>();
547 let Some(relative) = source_segments.strip_prefix(dependency_global_segments.as_slice())
548 else {
549 projected.push(dependency_use);
550 continue;
551 };
552 let Some((selection_relative, key)) = global_selection_path(relative) else {
553 projected.push(dependency_use);
554 continue;
555 };
556
557 for (source_index, global_source) in global_sources.iter().enumerate() {
558 let selection_source = helm_schema_core::join_encoded_value_path(
559 helm_schema_core::split_value_path(global_source)
560 .into_iter()
561 .chain(selection_relative.iter().cloned()),
562 );
563 let mut selected_use = dependency_use.clone();
564 selected_use.map_value_paths(&mut |path| {
565 replace_value_path_prefix(path, dependency_global, global_source)
566 });
567 selected_use.condition =
568 selected_use
569 .condition
570 .conjoined_with_guards(global_source_selection_guards(
571 global_sources,
572 selection_relative,
573 source_index,
574 &selection_source,
575 key,
576 ));
577 projected.push(selected_use);
578 }
579 }
580 *uses = projected;
581}
582
583fn global_selection_path(relative: &[String]) -> Option<(&[String], &str)> {
584 let selection_len = relative
585 .iter()
586 .position(|segment| segment == "*")
587 .unwrap_or(relative.len());
588 let selection = relative.get(..selection_len)?;
589 Some((selection, selection.last()?.as_str()))
590}
591
592fn global_source_selection_guards(
593 global_sources: &[String],
594 relative: &[String],
595 source_index: usize,
596 source: &str,
597 key: &str,
598) -> Vec<Guard> {
599 let mut guards = Vec::new();
600 for higher_priority in global_sources.iter().take(source_index) {
601 let higher_source = helm_schema_core::join_encoded_value_path(
602 helm_schema_core::split_value_path(higher_priority)
603 .into_iter()
604 .chain(relative.iter().cloned()),
605 );
606 let mut container_segments = helm_schema_core::split_value_path(&higher_source);
607 container_segments.pop();
608 guards.push(Guard::AnyOf {
609 alternatives: vec![
610 vec![Guard::NotHasKey {
611 path: helm_schema_core::ValuesPath::parse(
612 &helm_schema_core::join_encoded_value_path(container_segments),
613 ),
614 key: key.to_string(),
615 }],
616 vec![Guard::Eq {
617 path: helm_schema_core::ValuesPath::parse(&higher_source),
618 value: helm_schema_core::GuardValue::Null,
619 }],
620 ],
621 });
622 }
623 if source_index + 1 < global_sources.len() {
624 let mut container_segments = helm_schema_core::split_value_path(source);
625 container_segments.pop();
626 guards.push(Guard::HasKey {
627 path: helm_schema_core::ValuesPath::parse(&helm_schema_core::join_encoded_value_path(
628 container_segments,
629 )),
630 key: key.to_string(),
631 });
632 guards.push(Guard::NotEq {
633 path: helm_schema_core::ValuesPath::parse(source),
634 value: helm_schema_core::GuardValue::Null,
635 });
636 }
637 guards
638}
639
640fn replace_value_path_prefix(
641 path: helm_schema_core::ValuesPath,
642 from: &str,
643 to: &str,
644) -> helm_schema_core::ValuesPath {
645 let from = helm_schema_core::ValuesPath::parse(from);
646 if path != from && !path.is_descendant_of(&from) {
647 return path;
648 }
649 let relative = path
650 .segments()
651 .skip(from.segments().len())
652 .cloned()
653 .collect::<Vec<_>>();
654 let target = helm_schema_core::ValuesPath::parse(to);
655 helm_schema_core::ValuesPath::from_segments(target.segments().chain(&relative))
656}
657
658fn project_global_fail_captures(
659 captures: &mut BTreeSet<crate::eval_effect::FailCapture>,
660 global_sources: &[String],
661) {
662 let Some(dependency_global) = global_sources.last() else {
663 return;
664 };
665 let dependency_global_path = helm_schema_core::ValuesPath::parse(dependency_global);
666 let mut projected = BTreeSet::new();
667 for dependency_capture in std::mem::take(captures) {
668 let Some(source_path) = dependency_capture.kind.sole_value_path() else {
669 projected.insert(dependency_capture);
670 continue;
671 };
672 if source_path != &dependency_global_path
673 && !source_path.is_descendant_of(&dependency_global_path)
674 {
675 projected.insert(dependency_capture);
676 continue;
677 }
678 let relative = source_path
679 .segments()
680 .skip(dependency_global_path.segments().len())
681 .map(helm_schema_core::Segment::encode_component)
682 .collect::<Vec<_>>();
683 if relative.is_empty() {
684 if matches!(
685 dependency_capture.kind,
686 crate::eval_effect::CaptureKind::RangeInput { .. }
687 ) {
688 for global_source in global_sources {
689 let mut selected_capture = dependency_capture.clone();
690 selected_capture.conjunction = selected_capture
691 .conjunction
692 .into_iter()
693 .map(|predicate| {
694 predicate.map_value_paths(&mut |path| {
695 replace_value_path_prefix(path, dependency_global, global_source)
696 })
697 })
698 .collect();
699 selected_capture.kind.map_value_paths(&mut |path| {
700 replace_value_path_prefix(path, dependency_global, global_source)
701 });
702 projected.insert(selected_capture);
703 }
704 } else {
705 projected.insert(dependency_capture);
706 }
707 continue;
708 }
709 let Some((selection_relative, key)) = global_selection_path(&relative) else {
710 projected.insert(dependency_capture);
711 continue;
712 };
713
714 for (source_index, global_source) in global_sources.iter().enumerate() {
715 let selection_source = helm_schema_core::join_encoded_value_path(
716 helm_schema_core::split_value_path(global_source)
717 .into_iter()
718 .chain(selection_relative.iter().cloned()),
719 );
720 let mut selected_capture = dependency_capture.clone();
721 selected_capture.conjunction = selected_capture
722 .conjunction
723 .into_iter()
724 .map(|predicate| {
725 predicate.map_value_paths(&mut |path| {
726 replace_value_path_prefix(path, dependency_global, global_source)
727 })
728 })
729 .collect();
730 selected_capture.kind.map_value_paths(&mut |path| {
731 replace_value_path_prefix(path, dependency_global, global_source)
732 });
733 selected_capture.conjunction.extend(
734 global_source_selection_guards(
735 global_sources,
736 selection_relative,
737 source_index,
738 &selection_source,
739 key,
740 )
741 .into_iter()
742 .map(helm_schema_core::Predicate::from),
743 );
744 projected.insert(selected_capture);
745 }
746 }
747 *captures = projected;
748}
749
750fn project_global_range_modes(
751 range_modes: &mut crate::range_modes::RangeModes,
752 global_sources: &[String],
753) {
754 let Some(dependency_global) = global_sources.last() else {
755 return;
756 };
757 let dependency_path = helm_schema_core::ValuesPath::parse(dependency_global);
758 let dependency_segments = dependency_path
759 .segments()
760 .map(helm_schema_core::Segment::encode_component)
761 .collect::<Vec<_>>();
762 let projected_paths = range_modes
763 .iter()
764 .filter_map(|(path, mode)| {
765 let segments = path
766 .segments()
767 .map(helm_schema_core::Segment::encode_component)
768 .collect::<Vec<_>>();
769 let relative = segments.strip_prefix(dependency_segments.as_slice())?;
770 Some((path.clone(), relative.to_vec(), mode))
771 })
772 .collect::<Vec<_>>();
773
774 for (path, relative, mode) in projected_paths {
775 range_modes.remove(&path);
776 if relative.is_empty() {
777 for global_source in global_sources {
778 range_modes.merge_mode(&helm_schema_core::ValuesPath::parse(global_source), mode);
779 }
780 continue;
781 }
782 if global_selection_path(&relative).is_none() {
783 continue;
784 }
785 for global_source in global_sources {
786 let projected = helm_schema_core::join_encoded_value_path(
787 helm_schema_core::split_value_path(global_source)
788 .into_iter()
789 .chain(relative.iter().cloned()),
790 );
791 range_modes.merge_mode(&helm_schema_core::ValuesPath::parse(&projected), mode);
792 }
793 }
794}