use std::collections::BTreeSet;
use std::rc::Rc;
use crate::{ContractProvenance, ResourceRef, ValueKind};
use helm_schema_core::Predicate;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SiteFacts {
pub resource: Option<ResourceRef>,
pub path_prefix: Vec<String>,
pub provenance: Option<ContractProvenance>,
}
pub type PathCondition = Predicate;
#[derive(Clone, Debug, PartialEq, Eq)]
pub 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 conditional(condition: PathCondition, node: T) -> Self {
Self {
arms: vec![(condition, 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 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)
}
#[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 enum AbstractFragment {
Mapping(Mapping),
Sequence(Sequence),
Scalar(AbstractString),
Splice(Splice),
Opaque(Opaque),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Mapping {
pub entries: Vec<MappingEntry>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MappingEntry {
pub key: EntryKey,
pub value: Guarded<AbstractFragment>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EntryKey {
Literal(String),
Dynamic(AbstractString),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Sequence {
pub items: Vec<Guarded<AbstractFragment>>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub 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 enum StringPart {
Text(BTreeSet<String>),
Splice(Splice),
Taint(TaintPart),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TaintPart {
pub paths: BTreeSet<String>,
pub(crate) structured_value: Option<crate::abstract_value::AbstractValue>,
pub(crate) json_serialized: bool,
pub site: Option<Rc<SiteFacts>>,
pub provenance: Vec<ContractProvenance>,
}
impl TaintPart {
#[must_use]
pub fn new(paths: BTreeSet<String>) -> Self {
Self {
paths,
structured_value: None,
json_serialized: false,
site: None,
provenance: Vec::new(),
}
}
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,
site: None,
provenance: Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Splice {
pub values_path: String,
pub kind: ValueKind,
pub meta: SpliceMeta,
}
impl Splice {
#[must_use]
pub fn scalar(values_path: impl Into<String>) -> Self {
Self {
values_path: values_path.into(),
kind: ValueKind::Scalar,
meta: SpliceMeta::default(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SpliceMeta {
pub defaulted: bool,
pub encoded: bool,
pub shape_erased: bool,
pub nil_omitted: bool,
pub yaml_serialized: bool,
pub string_contract: 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>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Opaque {
pub taint: BTreeSet<String>,
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 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 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();
}
}
}
}
}