1use helm_schema_core::ConditionalGuard;
4use serde::Serialize;
5
6pub const POLICY_VOCABULARY_VERSION: u64 = 1;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum SchemaProfile {
17 #[default]
19 Full,
20 Lean,
26}
27
28impl SchemaProfile {
29 #[must_use]
31 pub const fn as_str(self) -> &'static str {
32 match self {
33 Self::Full => "full",
34 Self::Lean => "lean",
35 }
36 }
37
38 #[must_use]
40 pub const fn resolved_policy(self) -> ResolvedEmissionPolicy {
41 ResolvedEmissionPolicy {
42 requested_profile: Some(self),
43 policy: EmissionPolicy::for_profile(self),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
53#[serde(rename_all = "kebab-case")]
54pub struct EmissionPolicy {
55 root_anchored_conditionals: bool,
56 local_conditionals: bool,
57 terminal_clauses: bool,
58 kind_partitions: bool,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum ConditionalAnchors {
64 None,
66 Root,
68 Local,
70 RootAndLocal,
72}
73
74impl ConditionalAnchors {
75 #[must_use]
77 pub const fn new(root_anchored_conditionals: bool, local_conditionals: bool) -> Self {
78 match (root_anchored_conditionals, local_conditionals) {
79 (false, false) => Self::None,
80 (true, false) => Self::Root,
81 (false, true) => Self::Local,
82 (true, true) => Self::RootAndLocal,
83 }
84 }
85
86 const fn root_selected(self) -> bool {
87 matches!(self, Self::Root | Self::RootAndLocal)
88 }
89
90 const fn local_selected(self) -> bool {
91 matches!(self, Self::Local | Self::RootAndLocal)
92 }
93}
94
95impl EmissionPolicy {
96 pub const fn new(
103 conditional_anchors: ConditionalAnchors,
104 terminal_clauses: bool,
105 kind_partitions: bool,
106 ) -> Result<Self, InvalidEmissionPolicy> {
107 let policy = Self {
108 root_anchored_conditionals: conditional_anchors.root_selected(),
109 local_conditionals: conditional_anchors.local_selected(),
110 terminal_clauses,
111 kind_partitions,
112 };
113 if policy.is_valid() {
114 Ok(policy)
115 } else {
116 Err(InvalidEmissionPolicy)
117 }
118 }
119
120 pub(crate) const fn for_profile(profile: SchemaProfile) -> Self {
121 match profile {
122 SchemaProfile::Full => Self {
123 root_anchored_conditionals: true,
124 local_conditionals: true,
125 terminal_clauses: true,
126 kind_partitions: true,
127 },
128 SchemaProfile::Lean => Self {
129 root_anchored_conditionals: false,
130 local_conditionals: true,
131 terminal_clauses: false,
132 kind_partitions: false,
133 },
134 }
135 }
136
137 pub(crate) const fn is_valid(self) -> bool {
138 !self.kind_partitions || self.root_anchored_conditionals || self.local_conditionals
139 }
140
141 const fn apply_delta(self, delta: EmissionPolicyDelta) -> Result<Self, InvalidEmissionPolicy> {
142 let root_anchored_conditionals = match delta.root_anchored_conditionals {
143 Some(value) => value,
144 None => self.root_anchored_conditionals,
145 };
146 let local_conditionals = match delta.local_conditionals {
147 Some(value) => value,
148 None => self.local_conditionals,
149 };
150 Self::new(
151 ConditionalAnchors::new(root_anchored_conditionals, local_conditionals),
152 match delta.terminal_clauses {
153 Some(value) => value,
154 None => self.terminal_clauses,
155 },
156 match delta.kind_partitions {
157 Some(value) => value,
158 None => self.kind_partitions,
159 },
160 )
161 }
162
163 #[must_use]
165 pub const fn root_anchored_conditionals(self) -> bool {
166 self.root_anchored_conditionals
167 }
168
169 #[must_use]
171 pub const fn local_conditionals(self) -> bool {
172 self.local_conditionals
173 }
174
175 #[must_use]
177 pub const fn terminal_clauses(self) -> bool {
178 self.terminal_clauses
179 }
180
181 #[must_use]
183 pub const fn kind_partitions(self) -> bool {
184 self.kind_partitions
185 }
186
187 pub(crate) fn selects(self, class: &EmissionClass) -> bool {
188 match class {
189 EmissionClass::Mandatory => true,
190 EmissionClass::Conditional {
191 anchor,
192 flavor: ConditionalFlavor::Ordinary,
193 ..
194 } => {
195 if anchor.is_root() {
196 self.root_anchored_conditionals
197 } else {
198 self.local_conditionals
199 }
200 }
201 EmissionClass::Conditional {
202 anchor,
203 flavor: ConditionalFlavor::KindPartition,
204 ..
205 } => {
206 self.kind_partitions
207 && if anchor.is_root() {
208 self.root_anchored_conditionals
209 } else {
210 self.local_conditionals
211 }
212 }
213 EmissionClass::Terminal { .. } => self.terminal_clauses,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
220#[error("kind partitions require root-anchored-conditionals or local-conditionals to be enabled")]
221pub struct InvalidEmissionPolicy;
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
225pub struct EmissionPolicyDelta {
226 root_anchored_conditionals: Option<bool>,
227 local_conditionals: Option<bool>,
228 terminal_clauses: Option<bool>,
229 kind_partitions: Option<bool>,
230}
231
232impl EmissionPolicyDelta {
233 #[must_use]
235 pub const fn new(
236 root_anchored_conditionals: Option<bool>,
237 local_conditionals: Option<bool>,
238 terminal_clauses: Option<bool>,
239 kind_partitions: Option<bool>,
240 ) -> Self {
241 Self {
242 root_anchored_conditionals,
243 local_conditionals,
244 terminal_clauses,
245 kind_partitions,
246 }
247 }
248
249 #[must_use]
251 pub const fn root_anchored_conditionals(self) -> Option<bool> {
252 self.root_anchored_conditionals
253 }
254
255 #[must_use]
257 pub const fn local_conditionals(self) -> Option<bool> {
258 self.local_conditionals
259 }
260
261 #[must_use]
263 pub const fn terminal_clauses(self) -> Option<bool> {
264 self.terminal_clauses
265 }
266
267 #[must_use]
269 pub const fn kind_partitions(self) -> Option<bool> {
270 self.kind_partitions
271 }
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum EmissionSelection {
277 Preset {
279 profile: SchemaProfile,
281 delta: EmissionPolicyDelta,
283 },
284 Explicit(EmissionPolicy),
286}
287
288impl EmissionSelection {
289 pub const fn resolve(self) -> Result<ResolvedEmissionPolicy, InvalidEmissionPolicy> {
295 match self {
296 Self::Preset { profile, delta } => {
297 let policy = match EmissionPolicy::for_profile(profile).apply_delta(delta) {
298 Ok(policy) => policy,
299 Err(error) => return Err(error),
300 };
301 Ok(ResolvedEmissionPolicy {
302 requested_profile: Some(profile),
303 policy,
304 })
305 }
306 Self::Explicit(policy) => Ok(ResolvedEmissionPolicy {
307 requested_profile: None,
308 policy,
309 }),
310 }
311 }
312}
313
314impl Default for EmissionSelection {
315 fn default() -> Self {
316 SchemaProfile::Full.into()
317 }
318}
319
320impl From<SchemaProfile> for EmissionSelection {
321 fn from(profile: SchemaProfile) -> Self {
322 Self::Preset {
323 profile,
324 delta: EmissionPolicyDelta::default(),
325 }
326 }
327}
328
329impl From<EmissionPolicy> for EmissionSelection {
330 fn from(policy: EmissionPolicy) -> Self {
331 Self::Explicit(policy)
332 }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub struct ResolvedEmissionPolicy {
338 requested_profile: Option<SchemaProfile>,
339 policy: EmissionPolicy,
340}
341
342impl ResolvedEmissionPolicy {
343 #[must_use]
345 pub const fn requested_profile(self) -> Option<SchemaProfile> {
346 self.requested_profile
347 }
348
349 #[must_use]
351 pub const fn policy(self) -> EmissionPolicy {
352 self.policy
353 }
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub(crate) struct NestedGuardScope {
358 pub(crate) ancestor_segments: Vec<String>,
359 pub(crate) guards: Vec<ConditionalGuard>,
360}
361
362#[derive(Debug, Clone, PartialEq, Eq)]
363pub(crate) struct GuardScopes {
364 pub(crate) outer: Vec<ConditionalGuard>,
365 pub(crate) nested: Vec<NestedGuardScope>,
366}
367
368impl GuardScopes {
369 pub(crate) fn new(outer: Vec<ConditionalGuard>, nested: Vec<NestedGuardScope>) -> Self {
370 Self { outer, nested }
371 }
372
373 pub(crate) fn is_empty(&self) -> bool {
374 self.outer.is_empty() && self.nested.is_empty()
375 }
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
379pub(crate) struct NonEmptyGuardScopes(GuardScopes);
380
381impl NonEmptyGuardScopes {
382 pub(crate) fn new(scopes: GuardScopes) -> Option<Self> {
383 (!scopes.is_empty()).then_some(Self(scopes))
384 }
385
386 pub(crate) fn scopes(&self) -> &GuardScopes {
387 &self.0
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub(crate) enum EmissionAnchor {
393 Root,
394 Local(Vec<String>),
395}
396
397impl EmissionAnchor {
398 pub(crate) fn from_segments(segments: &[String]) -> Self {
399 if segments.is_empty() {
400 Self::Root
401 } else {
402 Self::Local(segments.to_vec())
403 }
404 }
405
406 const fn is_root(&self) -> bool {
407 matches!(self, Self::Root)
408 }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412pub(crate) enum ConditionalFlavor {
413 Ordinary,
414 KindPartition,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq)]
418pub(crate) enum TerminalWhen {
419 Always,
420 Guarded(NonEmptyGuardScopes),
421}
422
423#[derive(Debug, Clone, PartialEq, Eq)]
424pub(crate) enum EmissionClass {
425 Mandatory,
426 Conditional {
427 guards: GuardScopes,
428 anchor: EmissionAnchor,
429 flavor: ConditionalFlavor,
430 },
431 Terminal {
432 when: TerminalWhen,
433 },
434}
435
436impl EmissionClass {
437 pub(crate) fn conditional(
438 guards: GuardScopes,
439 anchor_segments: &[String],
440 flavor: ConditionalFlavor,
441 ) -> Self {
442 if guards.is_empty() {
443 Self::Mandatory
444 } else {
445 Self::Conditional {
446 guards,
447 anchor: EmissionAnchor::from_segments(anchor_segments),
448 flavor,
449 }
450 }
451 }
452
453 pub(crate) fn terminal_guarded(guards: Vec<ConditionalGuard>) -> Option<Self> {
454 let scopes = NonEmptyGuardScopes::new(GuardScopes::new(guards, Vec::new()))?;
455 Some(Self::Terminal {
456 when: TerminalWhen::Guarded(scopes),
457 })
458 }
459
460 pub(crate) const fn terminal_always() -> Self {
461 Self::Terminal {
462 when: TerminalWhen::Always,
463 }
464 }
465
466 pub(crate) const fn kind(&self) -> EmissionClassKind {
467 match self {
468 Self::Mandatory => EmissionClassKind::Mandatory,
469 Self::Conditional {
470 anchor: EmissionAnchor::Root,
471 flavor: ConditionalFlavor::Ordinary,
472 ..
473 } => EmissionClassKind::OrdinaryRoot,
474 Self::Conditional {
475 anchor: EmissionAnchor::Local(_),
476 flavor: ConditionalFlavor::Ordinary,
477 ..
478 } => EmissionClassKind::OrdinaryLocal,
479 Self::Conditional {
480 anchor: EmissionAnchor::Root,
481 flavor: ConditionalFlavor::KindPartition,
482 ..
483 } => EmissionClassKind::KindPartitionRoot,
484 Self::Conditional {
485 anchor: EmissionAnchor::Local(_),
486 flavor: ConditionalFlavor::KindPartition,
487 ..
488 } => EmissionClassKind::KindPartitionLocal,
489 Self::Terminal {
490 when: TerminalWhen::Always,
491 } => EmissionClassKind::TerminalAlways,
492 Self::Terminal {
493 when: TerminalWhen::Guarded(_),
494 } => EmissionClassKind::TerminalGuarded,
495 }
496 }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
501pub enum EmissionClassKind {
502 Mandatory,
504 OrdinaryRoot,
506 OrdinaryLocal,
508 KindPartitionRoot,
510 KindPartitionLocal,
512 TerminalAlways,
514 TerminalGuarded,
516}
517
518#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
520pub enum EmissionOrigin {
521 Overlay,
523 RequirementImplication,
525 MergeShadow,
527 OmittedMember,
529 Backprojection,
531}