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::{ContractUse, Guard, ValueKind, YamlPath};
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct ContractIr {
16 uses: Vec<ContractUse>,
17 dependency_uses: Vec<ContractUse>,
18 type_hints: BTreeMap<String, BTreeSet<String>>,
19 guarded_type_hints: BTreeMap<String, BTreeSet<String>>,
23 fallback_type_hints: BTreeMap<String, BTreeSet<String>>,
27 guarded_fallback_type_hints: BTreeMap<String, BTreeSet<String>>,
31 shape_erased_value_paths: BTreeSet<String>,
35 string_contract_value_paths: BTreeSet<String>,
38 range_modes: crate::range_modes::RangeModes,
41 values_default_sources: BTreeSet<crate::ValuesDefaultSource>,
43 values_root_overlay_prefixes: BTreeSet<String>,
46 values_program_wrappers: BTreeSet<helm_schema_core::ValuesProgramWrapper>,
47 values_program_wrapper_exclusions: BTreeSet<String>,
52 fail_conditions: Vec<crate::eval_effect::FailCapture>,
55 dependency_values_root_fragments: BTreeSet<String>,
56}
57
58impl ContractIr {
59 #[must_use]
66 pub fn from_contract_uses(uses: Vec<ContractUse>) -> Self {
67 Self {
68 uses,
69 ..Self::default()
70 }
71 }
72
73 pub(crate) fn push(&mut self, contract_use: ContractUse) {
74 self.uses.push(contract_use);
75 }
76
77 pub(crate) fn push_dependency_use(&mut self, contract_use: ContractUse) {
78 self.dependency_uses.push(contract_use);
79 }
80
81 pub fn push_pathless_scalar(&mut self, source_expr: impl Into<String>) {
86 self.push(ContractUse::new(
87 source_expr.into(),
88 YamlPath(Vec::new()),
89 ValueKind::Scalar,
90 Vec::new(),
91 None,
92 ));
93 }
94
95 pub fn push_pathless_dependency_fragment(&mut self, source_expr: impl Into<String>) {
97 self.dependency_values_root_fragments
98 .insert(source_expr.into());
99 }
100
101 pub fn append(&mut self, mut other: Self) {
103 self.uses.append(&mut other.uses);
104 self.dependency_uses.append(&mut other.dependency_uses);
105 self.dependency_values_root_fragments
106 .append(&mut other.dependency_values_root_fragments);
107 for (path, schema_types) in other.type_hints {
108 self.type_hints
109 .entry(path)
110 .or_default()
111 .extend(schema_types);
112 }
113 for (path, schema_types) in other.guarded_type_hints {
114 self.guarded_type_hints
115 .entry(path)
116 .or_default()
117 .extend(schema_types);
118 }
119 for (path, schema_types) in other.fallback_type_hints {
120 self.fallback_type_hints
121 .entry(path)
122 .or_default()
123 .extend(schema_types);
124 }
125 for (path, schema_types) in other.guarded_fallback_type_hints {
126 self.guarded_fallback_type_hints
127 .entry(path)
128 .or_default()
129 .extend(schema_types);
130 }
131 self.shape_erased_value_paths
132 .append(&mut other.shape_erased_value_paths);
133 self.string_contract_value_paths
134 .append(&mut other.string_contract_value_paths);
135 self.range_modes.merge(&other.range_modes);
136 self.values_default_sources
137 .append(&mut other.values_default_sources);
138 self.values_root_overlay_prefixes
139 .append(&mut other.values_root_overlay_prefixes);
140 self.values_program_wrappers
141 .append(&mut other.values_program_wrappers);
142 self.values_program_wrapper_exclusions
143 .append(&mut other.values_program_wrapper_exclusions);
144 for condition in std::mem::take(&mut other.fail_conditions) {
145 if !self.fail_conditions.contains(&condition) {
146 self.fail_conditions.push(condition);
147 }
148 }
149 }
150
151 pub fn add_terminal_fail_condition(&mut self, condition: helm_schema_core::Predicate) {
161 let capture = crate::eval_effect::FailCapture {
162 conjunction: vec![condition],
163 ranged: crate::range_modes::RangeModes::default(),
164 kind: crate::eval_effect::CaptureKind::Fail,
165 };
166 if !self.fail_conditions.contains(&capture) {
167 self.fail_conditions.push(capture);
168 }
169 }
170
171 pub fn append_guards_to_all_uses(&mut self, guards: &[Guard]) {
173 for contract_use in self.uses.iter_mut().chain(&mut self.dependency_uses) {
174 contract_use.condition = contract_use
175 .condition
176 .conjoined_with_guards(guards.iter().cloned());
177 }
178 for capture in &mut self.fail_conditions {
182 capture.conjunction.splice(
183 0..0,
184 guards
185 .iter()
186 .cloned()
187 .map(helm_schema_core::Predicate::from),
188 );
189 }
190 if !guards.is_empty() {
194 self.values_default_sources.clear();
195 self.values_root_overlay_prefixes.clear();
196 self.string_contract_value_paths.clear();
200 }
201 }
202
203 pub fn mark_rendered_output_textual(&mut self) {
207 for contract_use in self.uses.iter_mut().chain(&mut self.dependency_uses) {
208 contract_use.kind = ValueKind::Serialized;
209 }
210 }
211
212 pub fn map_value_paths<F>(&mut self, mut map: F)
218 where
219 F: FnMut(&str) -> String,
220 {
221 for contract_use in self.uses.iter_mut().chain(&mut self.dependency_uses) {
222 contract_use.map_value_paths(&mut map);
223 }
224 self.dependency_values_root_fragments =
225 std::mem::take(&mut self.dependency_values_root_fragments)
226 .into_iter()
227 .map(|path| map(&path))
228 .collect();
229 let mut type_hints: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
230 for (path, schema_types) in std::mem::take(&mut self.type_hints) {
231 type_hints
232 .entry(map(&path))
233 .or_default()
234 .extend(schema_types);
235 }
236 self.type_hints = type_hints;
237 let mut guarded_type_hints: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
238 for (path, schema_types) in std::mem::take(&mut self.guarded_type_hints) {
239 guarded_type_hints
240 .entry(map(&path))
241 .or_default()
242 .extend(schema_types);
243 }
244 self.guarded_type_hints = guarded_type_hints;
245 let mut fallback_type_hints: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
246 for (path, schema_types) in std::mem::take(&mut self.fallback_type_hints) {
247 fallback_type_hints
248 .entry(map(&path))
249 .or_default()
250 .extend(schema_types);
251 }
252 self.fallback_type_hints = fallback_type_hints;
253 let mut guarded_fallback_type_hints: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
254 for (path, schema_types) in std::mem::take(&mut self.guarded_fallback_type_hints) {
255 guarded_fallback_type_hints
256 .entry(map(&path))
257 .or_default()
258 .extend(schema_types);
259 }
260 self.guarded_fallback_type_hints = guarded_fallback_type_hints;
261 self.shape_erased_value_paths = std::mem::take(&mut self.shape_erased_value_paths)
262 .into_iter()
263 .map(|path| map(&path))
264 .collect();
265 self.string_contract_value_paths = std::mem::take(&mut self.string_contract_value_paths)
266 .into_iter()
267 .map(|path| map(&path))
268 .collect();
269 self.range_modes.map_value_paths(&mut map);
270 self.values_default_sources = std::mem::take(&mut self.values_default_sources)
271 .into_iter()
272 .map(|source| crate::ValuesDefaultSource {
273 target_path: map(&source.target_path),
274 source_path: map(&source.source_path),
275 })
276 .collect();
277 self.values_root_overlay_prefixes = std::mem::take(&mut self.values_root_overlay_prefixes)
278 .into_iter()
279 .map(|path| map(&path))
280 .collect();
281 self.values_program_wrappers = std::mem::take(&mut self.values_program_wrappers)
282 .into_iter()
283 .map(|wrapper| helm_schema_core::ValuesProgramWrapper {
284 scope_path: map(&wrapper.scope_path),
285 key: wrapper.key,
286 spread: wrapper.spread,
287 })
288 .collect();
289 self.values_program_wrapper_exclusions =
290 std::mem::take(&mut self.values_program_wrapper_exclusions)
291 .into_iter()
292 .map(|path| map(&path))
293 .collect();
294 self.fail_conditions = std::mem::take(&mut self.fail_conditions)
295 .into_iter()
296 .map(|mut capture| {
297 capture.conjunction = capture
298 .conjunction
299 .into_iter()
300 .map(|predicate| predicate.map_value_paths(&mut map))
301 .collect();
302 capture.ranged.map_value_paths(&mut map);
303 capture.kind.map_value_paths(&mut map);
304 capture
305 })
306 .collect();
307 }
308
309 pub fn add_type_hint(&mut self, path: impl Into<String>, schema_type: impl Into<String>) {
312 let path = path.into();
313 let schema_type = schema_type.into();
314 if path.trim().is_empty() || schema_type.trim().is_empty() {
315 return;
316 }
317 self.type_hints.entry(path).or_default().insert(schema_type);
318 }
319
320 pub(crate) fn extend_type_hints(
322 &mut self,
323 type_hints: impl IntoIterator<Item = (String, BTreeSet<String>)>,
324 ) {
325 for (path, schema_types) in type_hints {
326 if path.trim().is_empty() {
327 continue;
328 }
329 let schema_types = schema_types
330 .into_iter()
331 .filter(|schema_type| !schema_type.trim().is_empty())
332 .collect::<BTreeSet<_>>();
333 if schema_types.is_empty() {
334 continue;
335 }
336 self.type_hints
337 .entry(path)
338 .or_default()
339 .extend(schema_types);
340 }
341 }
342
343 pub(crate) fn extend_fallback_type_hints(
344 &mut self,
345 type_hints: impl IntoIterator<Item = (String, BTreeSet<String>)>,
346 ) {
347 for (path, schema_types) in type_hints {
348 if path.trim().is_empty() {
349 continue;
350 }
351 let schema_types = schema_types
352 .into_iter()
353 .filter(|schema_type| !schema_type.trim().is_empty())
354 .collect::<BTreeSet<_>>();
355 if schema_types.is_empty() {
356 continue;
357 }
358 self.fallback_type_hints
359 .entry(path)
360 .or_default()
361 .extend(schema_types);
362 }
363 }
364
365 pub(crate) fn extend_guarded_fallback_type_hints(
366 &mut self,
367 type_hints: impl IntoIterator<Item = (String, BTreeSet<String>)>,
368 ) {
369 for (path, schema_types) in type_hints {
370 if path.trim().is_empty() {
371 continue;
372 }
373 let schema_types = schema_types
374 .into_iter()
375 .filter(|schema_type| !schema_type.trim().is_empty())
376 .collect::<BTreeSet<_>>();
377 if schema_types.is_empty() {
378 continue;
379 }
380 self.guarded_fallback_type_hints
381 .entry(path)
382 .or_default()
383 .extend(schema_types);
384 }
385 }
386
387 pub(crate) fn extend_guarded_type_hints(
388 &mut self,
389 type_hints: impl IntoIterator<Item = (String, BTreeSet<String>)>,
390 ) {
391 for (path, schema_types) in type_hints {
392 if path.trim().is_empty() {
393 continue;
394 }
395 let schema_types = schema_types
396 .into_iter()
397 .filter(|schema_type| !schema_type.trim().is_empty())
398 .collect::<BTreeSet<_>>();
399 if schema_types.is_empty() {
400 continue;
401 }
402 self.guarded_type_hints
403 .entry(path)
404 .or_default()
405 .extend(schema_types);
406 }
407 }
408
409 pub(crate) fn extend_shape_erased_value_paths(
410 &mut self,
411 paths: impl IntoIterator<Item = String>,
412 ) {
413 self.shape_erased_value_paths
414 .extend(paths.into_iter().filter(|path| !path.trim().is_empty()));
415 }
416
417 pub(crate) fn extend_string_contract_value_paths(
418 &mut self,
419 paths: impl IntoIterator<Item = String>,
420 ) {
421 self.string_contract_value_paths
422 .extend(paths.into_iter().filter(|path| !path.trim().is_empty()));
423 }
424
425 pub(crate) fn merge_range_modes(&mut self, range_modes: &crate::range_modes::RangeModes) {
426 self.range_modes.merge(range_modes);
427 }
428
429 pub(crate) fn extend_values_default_sources(
430 &mut self,
431 sources: impl IntoIterator<Item = crate::ValuesDefaultSource>,
432 ) {
433 self.values_default_sources.extend(sources);
434 }
435
436 pub(crate) fn extend_values_root_overlay_prefixes(
437 &mut self,
438 prefixes: impl IntoIterator<Item = String>,
439 ) {
440 self.values_root_overlay_prefixes.extend(prefixes);
441 }
442
443 pub(crate) fn extend_values_program_wrappers(
444 &mut self,
445 wrappers: impl IntoIterator<Item = helm_schema_core::ValuesProgramWrapper>,
446 ) {
447 self.values_program_wrappers.extend(wrappers);
448 }
449
450 pub(crate) fn extend_values_program_wrapper_exclusions(
451 &mut self,
452 paths: impl IntoIterator<Item = String>,
453 ) {
454 self.values_program_wrapper_exclusions.extend(paths);
455 }
456
457 pub(crate) fn scrub_program_wrapper_sentinel_evidence(&mut self) {
464 let keys: std::collections::BTreeSet<String> = self
465 .values_program_wrappers
466 .iter()
467 .map(|wrapper| wrapper.key.clone())
468 .collect();
469 if keys.is_empty() {
470 return;
471 }
472 let touches = |path: &str| {
473 helm_schema_core::split_value_path(path)
474 .iter()
475 .any(|segment| keys.contains(segment))
476 };
477 self.uses
478 .retain(|contract_use| !touches(&contract_use.source_expr));
479 self.dependency_uses
480 .retain(|contract_use| !touches(&contract_use.source_expr));
481 self.fail_conditions.retain(|capture| {
482 let mut paths: Vec<String> = capture
483 .conjunction
484 .iter()
485 .flat_map(helm_schema_core::Predicate::value_paths)
486 .collect();
487 let mut kind = capture.kind.clone();
488 kind.map_value_paths(&mut |path: &str| {
489 paths.push(path.to_string());
490 path.to_string()
491 });
492 !paths.iter().any(|path| touches(path))
493 });
494 }
495
496 pub(crate) fn extend_fail_conditions(
497 &mut self,
498 conditions: impl IntoIterator<Item = crate::eval_effect::FailCapture>,
499 ) {
500 for conjunction in conditions {
501 if !self.fail_conditions.contains(&conjunction) {
502 self.fail_conditions.push(conjunction);
503 }
504 }
505 }
506
507 #[must_use]
510 #[tracing::instrument(skip_all)]
511 pub fn finalize(mut self) -> FinalizedContract {
512 self.scrub_program_wrapper_sentinel_evidence();
513 let Self {
514 mut uses,
515 mut dependency_uses,
516 type_hints,
517 guarded_type_hints,
518 fallback_type_hints,
519 guarded_fallback_type_hints,
520 shape_erased_value_paths,
521 string_contract_value_paths,
522 range_modes,
523 values_default_sources,
524 values_root_overlay_prefixes,
525 values_program_wrappers,
526 values_program_wrapper_exclusions,
527 mut fail_conditions,
528 dependency_values_root_fragments,
529 } = self;
530 for source_expr in &dependency_values_root_fragments {
531 dependency_uses.push(ContractUse::new(
532 source_expr.clone(),
533 YamlPath(Vec::new()),
534 ValueKind::Fragment,
535 Vec::new(),
536 None,
537 ));
538 }
539 normalize_contract_uses(&mut uses);
540 drop_self_truthy_subsumed_duplicates(&mut dependency_uses);
541 canonicalize_contract_uses(&mut dependency_uses);
542 uses.append(&mut dependency_uses);
543 drop_default_guard_subsumed_duplicates(&mut uses);
544 drop_self_truthy_subsumed_duplicates(&mut uses);
545 canonicalize_contract_uses(&mut uses);
546 fail_conditions.sort();
547 fail_conditions.dedup();
548 FinalizedContract::new(
549 uses,
550 &type_hints,
551 &guarded_type_hints,
552 &fallback_type_hints,
553 &guarded_fallback_type_hints,
554 &shape_erased_value_paths,
555 &string_contract_value_paths,
556 &range_modes,
557 values_default_sources,
558 values_root_overlay_prefixes,
559 values_program_wrappers,
560 values_program_wrapper_exclusions,
561 &fail_conditions,
562 &dependency_values_root_fragments,
563 )
564 }
565}