use std::collections::BTreeSet;
use std::rc::Rc;
use crate::{ContractProvenance, ResourceRef, ValueKind};
use helm_schema_core::{Predicate, ValuesPath};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct SiteFacts {
pub resource: Option<ResourceRef>,
pub path_prefix: Vec<String>,
pub provenance: Option<ContractProvenance>,
}
pub(crate) type PathCondition = Predicate;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Guarded<T> {
pub arms: Vec<(PathCondition, T)>,
}
impl<T> Default for Guarded<T> {
fn default() -> Self {
Self::empty()
}
}
impl<T> Guarded<T> {
#[must_use]
pub fn empty() -> Self {
Self { arms: Vec::new() }
}
#[must_use]
pub fn unconditional(node: T) -> Self {
Self {
arms: vec![(Predicate::True, node)],
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.arms.is_empty()
}
pub fn guard_all(&mut self, condition: &PathCondition) {
if condition.is_trivial() && *condition == Predicate::True {
return;
}
for (arm_condition, _) in &mut self.arms {
*arm_condition = and_conditions(condition.clone(), arm_condition.clone());
}
}
pub fn extend(&mut self, other: Self) {
self.arms.extend(other.arms);
}
}
#[must_use]
pub(crate) fn and_conditions(outer: PathCondition, inner: PathCondition) -> PathCondition {
let mut parts = Vec::new();
for condition in [outer, inner] {
match condition {
Predicate::True => {}
Predicate::And(inner_parts) => {
for part in inner_parts {
if !parts.contains(&part) {
parts.push(part);
}
}
}
other => {
if !parts.contains(&other) {
parts.push(other);
}
}
}
}
Predicate::all(parts).normalize_boolean()
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[expect(
clippy::large_enum_variant,
reason = "boxing rendered fragments would add allocation and pointer chasing in the interpreter hot path"
)]
pub(crate) enum AbstractFragment {
Mapping(Mapping),
Sequence(Sequence),
Scalar(AbstractString),
Splice(Splice),
Opaque(Opaque),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct Mapping {
pub entries: Vec<MappingEntry>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct MappingEntry {
pub key: EntryKey,
pub value: Guarded<AbstractFragment>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum EntryKey {
Literal(String),
Dynamic(AbstractString),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct Sequence {
pub items: Vec<Guarded<AbstractFragment>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct AbstractString {
pub parts: Vec<StringPart>,
pub suppressed: bool,
}
impl AbstractString {
#[must_use]
pub fn literal(text: impl Into<String>) -> Self {
Self {
parts: vec![StringPart::Text([text.into()].into_iter().collect())],
suppressed: false,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[expect(
clippy::large_enum_variant,
reason = "string parts are traversed in hot projection loops and benefit from inline storage"
)]
pub(crate) enum StringPart {
Text(BTreeSet<String>),
Splice(Splice),
Taint(TaintPart),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct TaintPart {
pub paths: BTreeSet<ValuesPath>,
pub(crate) structured_value: Option<crate::abstract_value::AbstractValue>,
pub(crate) json_serialized: bool,
pub(crate) claims_value_kind: bool,
pub site: Option<Rc<SiteFacts>>,
pub provenance: Vec<ContractProvenance>,
}
impl Default for TaintPart {
fn default() -> Self {
Self::new(BTreeSet::new())
}
}
impl TaintPart {
#[must_use]
pub fn new(paths: BTreeSet<ValuesPath>) -> Self {
Self {
paths,
structured_value: None,
json_serialized: false,
claims_value_kind: true,
site: None,
provenance: Vec::new(),
}
}
#[must_use]
pub(crate) fn abstaining(paths: BTreeSet<ValuesPath>) -> Self {
Self {
claims_value_kind: false,
..Self::new(paths)
}
}
pub(crate) fn from_json_serialized(value: crate::abstract_value::AbstractValue) -> Self {
Self {
paths: value.fragment_rendered_paths(),
structured_value: Some(value),
json_serialized: true,
claims_value_kind: true,
site: None,
provenance: Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Splice {
pub values_path: helm_schema_core::ValuesPath,
pub kind: ValueKind,
pub meta: SpliceMeta,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct SpliceMeta {
pub input_identity: bool,
pub defaulted: bool,
pub encoded: bool,
pub shape_erased: bool,
pub stringified: bool,
pub nil_omitted: bool,
pub yaml_serialized: bool,
pub templated_yaml: bool,
pub plain_slot_string_format: bool,
pub json_serialized: bool,
pub json_decoded: bool,
pub lexical_escapes: BTreeSet<crate::helper_meta::LexicalEscape>,
pub split_segment: Option<helm_schema_core::SplitSegmentUse>,
pub merge_layers: Option<helm_schema_core::MergeLayersUse>,
pub range_key: bool,
pub omitted_members: std::collections::BTreeMap<String, Vec<helm_schema_core::Guard>>,
pub digest: bool,
pub merge_operand: bool,
pub provenance: Vec<ContractProvenance>,
pub site: Option<Rc<SiteFacts>>,
}
impl SpliceMeta {
pub(crate) fn is_input_identity(&self) -> bool {
let Self {
input_identity,
defaulted: _,
encoded,
shape_erased,
stringified,
nil_omitted,
yaml_serialized,
templated_yaml,
plain_slot_string_format,
json_serialized,
json_decoded: _,
lexical_escapes,
split_segment,
merge_layers,
range_key,
omitted_members,
digest,
merge_operand,
provenance: _,
site: _,
} = self;
*input_identity
&& !encoded
&& !shape_erased
&& !stringified
&& !nil_omitted
&& !yaml_serialized
&& !templated_yaml
&& !plain_slot_string_format
&& !json_serialized
&& lexical_escapes.is_empty()
&& split_segment.is_none()
&& merge_layers.is_none()
&& !range_key
&& omitted_members.is_empty()
&& !digest
&& !merge_operand
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Opaque {
pub taint: BTreeSet<ValuesPath>,
pub kind: ValueKind,
pub site: Option<Rc<SiteFacts>>,
pub provenance: Vec<ContractProvenance>,
}
impl Default for Opaque {
fn default() -> Self {
Self {
taint: BTreeSet::new(),
kind: ValueKind::Scalar,
site: None,
provenance: Vec::new(),
}
}
}
pub(crate) fn stamp_fragment_sites(
guarded: &mut Guarded<AbstractFragment>,
site: Option<&Rc<SiteFacts>>,
) {
if site.is_none() {
return;
}
for (_, node) in &mut guarded.arms {
stamp_node_sites(node, site);
}
}
fn stamp_node_sites(node: &mut AbstractFragment, site: Option<&Rc<SiteFacts>>) {
match node {
AbstractFragment::Mapping(mapping) => {
for entry in &mut mapping.entries {
stamp_fragment_sites(&mut entry.value, site);
}
}
AbstractFragment::Sequence(sequence) => {
for item in &mut sequence.items {
stamp_fragment_sites(item, site);
}
}
AbstractFragment::Scalar(scalar) => stamp_part_sites(&mut scalar.parts, site),
AbstractFragment::Splice(splice) => {
if splice.meta.site.is_none() {
splice.meta.site = site.cloned();
}
}
AbstractFragment::Opaque(opaque) => {
if opaque.site.is_none() {
opaque.site = site.cloned();
}
}
}
}
pub(crate) fn stamp_part_sites(parts: &mut [StringPart], site: Option<&Rc<SiteFacts>>) {
if site.is_none() {
return;
}
for part in parts {
match part {
StringPart::Text(_) => {}
StringPart::Splice(splice) => {
if splice.meta.site.is_none() {
splice.meta.site = site.cloned();
}
}
StringPart::Taint(taint) => {
if taint.site.is_none() {
taint.site = site.cloned();
}
}
}
}
}