Skip to main content

rite_model/
expression.rs

1//! Expression parsing for computed values in ceremony DSL.
2//!
3//! This module provides types and functions for parsing pipeline expressions
4//! that compute values at runtime, such as `${artifact.ksr | sha256 | hex}`.
5//!
6//! ## Syntax
7//!
8//! Expressions use Unix-style pipes:
9//!
10//! ```text
11//! ${artifact.ksr | sha256 | hex}
12//! ${artifact.keypair.public | fingerprint | hex | upper}
13//! ${concat(artifact.a | sha256, artifact.b | sha256) | hex}
14//! ```
15//!
16//! ## Design Principles
17//!
18//! - **Bytes-first**: Hash functions return raw bytes; encoding is explicit
19//! - **Left-to-right**: Data flows through transformations naturally
20//! - **Safe**: All functions are total and terminate (no user-defined functions)
21//!
22//! ## Grammar
23//!
24//! The formal grammar is documented in `docs/dsl/grammar.md` (not yet published).
25
26// The expression parser uses byte-index arithmetic and slice indexing throughout.
27// All indices are guarded by length checks and loop bounds; the operations cannot
28// overflow or panic in practice. Suppressed here to avoid obscuring the parser logic.
29#![allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)]
30
31use std::ops::Range;
32
33/// The type/namespace of a reference.
34///
35/// Used in full-form references like `${param.name}` or `${artifact.ksr}`.
36/// This is the canonical definition, re-exported by `reference.rs`.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum RefType {
39    /// Parameter reference (runtime configuration from CLI/env/defaults)
40    Param,
41    /// Role reference (ceremony participant)
42    Role,
43    /// Artifact reference (materials, outputs, intermediate values)
44    Artifact,
45}
46
47impl std::str::FromStr for RefType {
48    type Err = String;
49
50    fn from_str(s: &str) -> Result<Self, Self::Err> {
51        match s {
52            "param" => Ok(Self::Param),
53            "role" => Ok(Self::Role),
54            "artifact" => Ok(Self::Artifact),
55            _ => Err(format!("unknown reference type: {s}")),
56        }
57    }
58}
59
60impl std::fmt::Display for RefType {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Self::Param => write!(f, "param"),
64            Self::Role => write!(f, "role"),
65            Self::Artifact => write!(f, "artifact"),
66        }
67    }
68}
69
70/// A runtime value in the expression system.
71#[derive(Debug, Clone, PartialEq)]
72pub enum Value {
73    /// Raw binary data (computation core)
74    Bytes(Vec<u8>),
75    /// UTF-8 text string
76    String(String),
77    /// Integer value
78    Integer(i64),
79    /// Boolean value
80    Boolean(bool),
81    /// Null/missing value
82    Null,
83}
84
85impl Value {
86    /// Try to get this value as bytes.
87    /// Strings are converted to UTF-8 bytes.
88    pub fn as_bytes(&self) -> Option<Vec<u8>> {
89        match self {
90            Value::Bytes(b) => Some(b.clone()),
91            Value::String(s) => Some(s.as_bytes().to_vec()),
92            _ => None,
93        }
94    }
95
96    /// Try to get this value as a string.
97    pub fn as_string(&self) -> Option<&str> {
98        match self {
99            Value::String(s) => Some(s),
100            _ => None,
101        }
102    }
103
104    /// Try to get this value as an integer.
105    pub fn as_integer(&self) -> Option<i64> {
106        match self {
107            Value::Integer(i) => Some(*i),
108            _ => None,
109        }
110    }
111
112    /// Check if this value is null.
113    pub fn is_null(&self) -> bool {
114        matches!(self, Value::Null)
115    }
116
117    /// Get a human-readable type name for error messages.
118    pub fn type_name(&self) -> &'static str {
119        match self {
120            Value::Bytes(_) => "bytes",
121            Value::String(_) => "string",
122            Value::Integer(_) => "integer",
123            Value::Boolean(_) => "boolean",
124            Value::Null => "null",
125        }
126    }
127}
128
129impl std::fmt::Display for Value {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            Value::Bytes(b) => write!(f, "<{} bytes>", b.len()),
133            Value::String(s) => write!(f, "{s}"),
134            Value::Integer(i) => write!(f, "{i}"),
135            Value::Boolean(b) => write!(f, "{b}"),
136            Value::Null => write!(f, "null"),
137        }
138    }
139}
140
141/// A parsed expression from ceremony YAML.
142///
143/// Expressions can be:
144/// - Simple references: `${artifact.ksr}`
145/// - Pipelines: `${artifact.ksr | sha256 | hex}`
146/// - Function calls with arguments: `${concat(a | sha256, b | sha256) | hex}`
147#[derive(Debug, Clone, PartialEq)]
148pub enum Expression {
149    /// A simple reference to a parameter or artifact
150    Reference(Reference),
151
152    /// A literal value
153    Literal(Literal),
154
155    /// A pipeline of transformations
156    Pipeline {
157        /// The starting value (reference or literal)
158        source: Box<Expression>,
159        /// The transformation stages
160        stages: Vec<PipeStage>,
161    },
162}
163
164/// A reference to a ceremony value: `${type.name}` or `${type.name.property}`.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct Reference {
167    /// The reference type (param, artifact, role)
168    pub ref_type: RefType,
169    /// The primary identifier
170    pub name: String,
171    /// Optional sub-property (e.g., `private` in `artifact.keypair.private`)
172    pub property: Option<String>,
173}
174
175impl Reference {
176    /// Create a new reference.
177    pub fn new(ref_type: RefType, name: &str) -> Self {
178        Self {
179            ref_type,
180            name: name.to_string(),
181            property: None,
182        }
183    }
184
185    /// Create a new reference with a property.
186    pub fn with_property(ref_type: RefType, name: &str, property: &str) -> Self {
187        Self {
188            ref_type,
189            name: name.to_string(),
190            property: Some(property.to_string()),
191        }
192    }
193}
194
195impl std::fmt::Display for Reference {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        if let Some(prop) = &self.property {
198            write!(f, "{}.{}.{}", self.ref_type, self.name, prop)
199        } else {
200            write!(f, "{}.{}", self.ref_type, self.name)
201        }
202    }
203}
204
205/// A literal value in an expression.
206#[derive(Debug, Clone, PartialEq)]
207pub enum Literal {
208    /// String literal (single or double quoted)
209    String(String),
210    /// Integer literal
211    Integer(i64),
212    /// Floating-point literal
213    Float(f64),
214    /// Boolean literal
215    Boolean(bool),
216    /// Null literal
217    Null,
218}
219
220#[allow(clippy::cast_possible_truncation)]
221impl From<Literal> for Value {
222    fn from(lit: Literal) -> Self {
223        match lit {
224            Literal::String(s) => Value::String(s),
225            Literal::Integer(i) => Value::Integer(i),
226            Literal::Float(f) => Value::Integer(f as i64), // Truncate float to integer
227            Literal::Boolean(b) => Value::Boolean(b),
228            Literal::Null => Value::Null,
229        }
230    }
231}
232
233/// A stage in a pipeline.
234#[derive(Debug, Clone, PartialEq)]
235pub struct PipeStage {
236    /// The function name
237    pub function: String,
238    /// Optional arguments to the function
239    pub args: Vec<Expression>,
240}
241
242impl PipeStage {
243    /// Create a new pipe stage with just a function name (no arguments).
244    pub fn new(function: &str) -> Self {
245        Self {
246            function: function.to_string(),
247            args: Vec::new(),
248        }
249    }
250
251    /// Create a new pipe stage with arguments.
252    pub fn with_args(function: &str, args: Vec<Expression>) -> Self {
253        Self {
254            function: function.to_string(),
255            args,
256        }
257    }
258}
259
260/// An expression with its location in the source string.
261#[derive(Debug, Clone, PartialEq)]
262pub struct LocatedExpression {
263    /// The parsed expression
264    pub expression: Expression,
265    /// Byte range in the source string
266    pub range: Range<usize>,
267}
268
269/// A value that may contain deferred expressions (parsed, not yet evaluated).
270///
271/// This is the bridge between the resolver and runtime: the resolver parses
272/// all `${...}` patterns into structured expressions, and the runtime evaluates
273/// them without any string parsing.
274#[derive(Debug, Clone, PartialEq)]
275pub enum ExprValue {
276    /// Fully resolved literal (no expressions)
277    Literal(Literal),
278    /// Single expression (entire value is one expression)
279    Expr(Expression),
280    /// String with embedded expressions: `"Hash: ${artifact.x | sha256}"`
281    Interpolated(Vec<StringPart>),
282    /// Structured object with potentially deferred fields
283    Object(std::collections::HashMap<String, ExprValue>),
284    /// Array of values
285    Array(Vec<ExprValue>),
286}
287
288impl ExprValue {
289    /// Get the value as a literal string, if it is one.
290    pub fn as_literal_string(&self) -> Option<&str> {
291        match self {
292            ExprValue::Literal(Literal::String(s)) => Some(s),
293            _ => None,
294        }
295    }
296
297    /// Get a field from an Object variant.
298    pub fn get(&self, key: &str) -> Option<&ExprValue> {
299        match self {
300            ExprValue::Object(map) => map.get(key),
301            _ => None,
302        }
303    }
304
305    /// Convert to a display string (for script generation, not evaluation).
306    ///
307    /// For literals, returns the literal value.
308    /// For expressions, returns the expression syntax like `"${artifact.ksr | sha256}"`.
309    pub fn to_display_string(&self) -> String {
310        match self {
311            ExprValue::Literal(Literal::String(s)) => s.clone(),
312            ExprValue::Literal(Literal::Integer(i)) => i.to_string(),
313            ExprValue::Literal(Literal::Float(f)) => f.to_string(),
314            ExprValue::Literal(Literal::Boolean(b)) => b.to_string(),
315            ExprValue::Literal(Literal::Null) => "null".to_string(),
316            ExprValue::Expr(expr) => format!("${{{}}}", expr_to_string(expr)),
317            ExprValue::Interpolated(parts) => parts
318                .iter()
319                .map(|p| match p {
320                    StringPart::Literal(s) => s.clone(),
321                    StringPart::Expr(expr) => format!("${{{}}}", expr_to_string(expr)),
322                })
323                .collect(),
324            ExprValue::Object(_) => "[object]".to_string(),
325            ExprValue::Array(_) => "[array]".to_string(),
326        }
327    }
328}
329
330/// Convert an `Expression` back to a string representation.
331fn expr_to_string(expr: &Expression) -> String {
332    match expr {
333        Expression::Reference(r) => r.to_string(),
334        Expression::Literal(Literal::String(s)) => format!("\"{s}\""),
335        Expression::Literal(Literal::Integer(i)) => i.to_string(),
336        Expression::Literal(Literal::Float(f)) => f.to_string(),
337        Expression::Literal(Literal::Boolean(b)) => b.to_string(),
338        Expression::Literal(Literal::Null) => "null".to_string(),
339        Expression::Pipeline { source, stages } => {
340            let mut result = expr_to_string(source);
341            for stage in stages {
342                result.push_str(" | ");
343                result.push_str(&stage.function);
344                if !stage.args.is_empty() {
345                    result.push('(');
346                    let args: Vec<_> = stage.args.iter().map(expr_to_string).collect();
347                    result.push_str(&args.join(", "));
348                    result.push(')');
349                }
350            }
351            result
352        }
353    }
354}
355
356/// A part of an interpolated string.
357#[derive(Debug, Clone, PartialEq)]
358pub enum StringPart {
359    /// Literal text
360    Literal(String),
361    /// Embedded expression
362    Expr(Expression),
363}
364
365/// Parse an expression string.
366///
367/// The string should be in the form `${...}` containing a pipeline expression.
368///
369/// # Examples
370///
371/// ```
372/// use rite_model::expression::{parse_expression, Expression, Reference, RefType, PipeStage};
373///
374/// // Simple reference
375/// let expr = parse_expression("${artifact.ksr}").unwrap();
376/// assert!(matches!(expr, Expression::Reference(_)));
377///
378/// // Pipeline
379/// let expr = parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
380/// if let Expression::Pipeline { stages, .. } = expr {
381///     assert_eq!(stages.len(), 2);
382///     assert_eq!(stages[0].function, "sha256");
383///     assert_eq!(stages[1].function, "hex");
384/// }
385/// ```
386pub fn parse_expression(s: &str) -> Option<Expression> {
387    let s = s.trim();
388
389    // Must be wrapped in ${ }
390    let inner = s.strip_prefix("${").and_then(|s| s.strip_suffix('}'))?;
391    let inner = inner.trim();
392
393    if inner.is_empty() {
394        return None;
395    }
396
397    parse_pipeline(inner)
398}
399
400/// Parse a pipeline expression (without the `${ }` wrapper).
401fn parse_pipeline(s: &str) -> Option<Expression> {
402    let s = s.trim();
403
404    // Split on pipe operator, being careful about nested parentheses.
405    let parts = split_on_pipes(s);
406
407    if parts.is_empty() {
408        return None;
409    }
410
411    // First part is the source.
412    let source = parse_source(parts.first()?.trim())?;
413
414    if parts.len() == 1 {
415        // No pipeline, just a source.
416        return Some(source);
417    }
418
419    // Rest are pipe stages.
420    let mut stages = Vec::new();
421    for part in parts.iter().skip(1) {
422        let stage = parse_pipe_stage(part.trim())?;
423        stages.push(stage);
424    }
425
426    Some(Expression::Pipeline {
427        source: Box::new(source),
428        stages,
429    })
430}
431
432/// Split a string on pipe operators, respecting parentheses.
433fn split_on_pipes(s: &str) -> Vec<&str> {
434    let mut parts = Vec::new();
435    let mut start = 0;
436    let mut paren_depth: u32 = 0;
437    let bytes = s.as_bytes();
438
439    for (i, &b) in bytes.iter().enumerate() {
440        match b {
441            b'(' => paren_depth += 1,
442            b')' => paren_depth = paren_depth.saturating_sub(1),
443            b'|' if paren_depth == 0 => {
444                parts.push(&s[start..i]);
445                start = i + 1;
446            }
447            _ => {}
448        }
449    }
450
451    // Add the last part.
452    if start < s.len() {
453        parts.push(&s[start..]);
454    }
455
456    parts
457}
458
459/// Parse a source expression (reference, literal, or function call).
460fn parse_source(s: &str) -> Option<Expression> {
461    let s = s.trim();
462
463    // Try to parse as a function call (for concat, etc.)
464    if let Some(expr) = parse_function_call(s) {
465        return Some(expr);
466    }
467
468    // Try to parse as a reference (namespace.name.property).
469    if let Some(expr_ref) = parse_ref(s) {
470        return Some(Expression::Reference(expr_ref));
471    }
472
473    // Try to parse as a literal.
474    if let Some(lit) = parse_literal(s) {
475        return Some(Expression::Literal(lit));
476    }
477
478    None
479}
480
481/// Parse a reference: `namespace.name` or `namespace.name.property`.
482fn parse_ref(s: &str) -> Option<Reference> {
483    let parts: Vec<&str> = s.splitn(3, '.').collect();
484
485    if parts.len() < 2 {
486        return None;
487    }
488
489    let namespace = parts[0];
490    if !is_valid_identifier(namespace) {
491        return None;
492    }
493
494    // Parse and validate namespace.
495    let ref_type: RefType = namespace.parse().ok()?;
496
497    let name = parts[1];
498    if !is_valid_identifier(name) {
499        return None;
500    }
501
502    let property = if parts.len() == 3 {
503        let prop = parts[2];
504        if !is_valid_identifier(prop) {
505            return None;
506        }
507        Some(prop.to_string())
508    } else {
509        None
510    };
511
512    Some(Reference {
513        ref_type,
514        name: name.to_string(),
515        property,
516    })
517}
518
519/// Parse a literal value.
520fn parse_literal(s: &str) -> Option<Literal> {
521    let s = s.trim();
522
523    // String literal (double or single quoted).
524    if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
525        let inner = &s[1..s.len() - 1];
526        return Some(Literal::String(inner.to_string()));
527    }
528
529    // Integer literal.
530    if let Ok(i) = s.parse::<i64>() {
531        return Some(Literal::Integer(i));
532    }
533
534    None
535}
536
537/// Parse a function call: `name(arg1, arg2, ...)`
538fn parse_function_call(s: &str) -> Option<Expression> {
539    let s = s.trim();
540
541    // Find the opening paren.
542    let paren_pos = s.find('(')?;
543    let name = &s[..paren_pos];
544
545    if !is_valid_identifier(name) {
546        return None;
547    }
548
549    // Must end with closing paren.
550    if !s.ends_with(')') {
551        return None;
552    }
553
554    let args_str = &s[paren_pos + 1..s.len() - 1];
555
556    // Parse arguments.
557    let args = parse_function_args(args_str)?;
558
559    // For functions like concat, the args ARE the sources, represented as a Pipeline
560    // with a placeholder source and the function as the first stage.
561    Some(Expression::Pipeline {
562        source: Box::new(Expression::Literal(Literal::Integer(0))), // Placeholder
563        stages: vec![PipeStage::with_args(name, args)],
564    })
565}
566
567/// Parse function arguments, handling nested expressions.
568fn parse_function_args(s: &str) -> Option<Vec<Expression>> {
569    let s = s.trim();
570
571    if s.is_empty() {
572        return Some(Vec::new());
573    }
574
575    let mut args = Vec::new();
576    let mut current_start = 0;
577    let mut paren_depth: u32 = 0;
578    let bytes = s.as_bytes();
579
580    for (i, &b) in bytes.iter().enumerate() {
581        match b {
582            b'(' => paren_depth += 1,
583            b')' => paren_depth = paren_depth.saturating_sub(1),
584            b',' if paren_depth == 0 => {
585                let arg = &s[current_start..i];
586                let expr = parse_pipeline(arg.trim())?;
587                args.push(expr);
588                current_start = i + 1;
589            }
590            _ => {}
591        }
592    }
593
594    // Add the last argument.
595    if current_start < s.len() {
596        let arg = &s[current_start..];
597        let expr = parse_pipeline(arg.trim())?;
598        args.push(expr);
599    }
600
601    Some(args)
602}
603
604/// Parse a pipe stage: `function` or `function(args)`.
605fn parse_pipe_stage(s: &str) -> Option<PipeStage> {
606    let s = s.trim();
607
608    // Check for function with arguments.
609    if let Some(paren_pos) = s.find('(') {
610        let name = &s[..paren_pos];
611        if !is_valid_identifier(name) {
612            return None;
613        }
614
615        if !s.ends_with(')') {
616            return None;
617        }
618
619        let args_str = &s[paren_pos + 1..s.len() - 1];
620        let args = parse_function_args(args_str)?;
621
622        return Some(PipeStage::with_args(name, args));
623    }
624
625    // Simple function name.
626    if is_valid_identifier(s) {
627        return Some(PipeStage::new(s));
628    }
629
630    None
631}
632
633/// Check if a string is a valid identifier (ASCII alphanumeric or underscore, not starting
634/// with a digit).
635fn is_valid_identifier(s: &str) -> bool {
636    let mut chars = s.chars();
637    match chars.next() {
638        None => false,
639        Some(first) => {
640            (first.is_ascii_alphabetic() || first == '_')
641                && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
642        }
643    }
644}
645
646/// Find all expressions in a string.
647///
648/// Returns expressions with their locations for LSP support.
649pub fn find_expressions(s: &str) -> Vec<LocatedExpression> {
650    let mut results = Vec::new();
651    let bytes = s.as_bytes();
652    let mut i = 0;
653
654    while i < bytes.len() {
655        if bytes[i] == b'$' && i + 1 < bytes.len() && bytes[i + 1] == b'{' {
656            // Find matching closing brace.
657            let mut depth = 1;
658            let mut j = i + 2;
659            while j < bytes.len() && depth > 0 {
660                match bytes[j] {
661                    b'{' => depth += 1,
662                    b'}' => depth -= 1,
663                    _ => {}
664                }
665                j += 1;
666            }
667
668            if depth == 0 {
669                let expr_str = &s[i..j];
670                if let Some(expression) = parse_expression(expr_str) {
671                    results.push(LocatedExpression {
672                        expression,
673                        range: i..j,
674                    });
675                }
676                i = j;
677                continue;
678            }
679        }
680        i += 1;
681    }
682
683    results
684}
685
686/// Parse a JSON value into an `ExprValue`, extracting all `${...}` expressions.
687///
688/// This function recursively converts JSON into `ExprValue`:
689/// - Strings containing `${...}` become `Expr` (if entire string) or `Interpolated` (if mixed)
690/// - Plain strings become `Literal(String)`
691/// - Numbers, bools, null become their `Literal` equivalents
692/// - Objects and arrays are recursed into
693///
694/// # Examples
695///
696/// ```
697/// use rite_model::expression::{parse_expr_value, ExprValue, Literal};
698///
699/// // Plain string → Literal
700/// let json = serde_json::json!("hello");
701/// let expr = parse_expr_value(&json);
702/// assert!(matches!(expr, ExprValue::Literal(Literal::String(_))));
703///
704/// // Expression string → Expr
705/// let json = serde_json::json!("${artifact.ksr | sha256}");
706/// let expr = parse_expr_value(&json);
707/// assert!(matches!(expr, ExprValue::Expr(_)));
708/// ```
709pub fn parse_expr_value(json: &serde_json::Value) -> ExprValue {
710    parse_expr_value_at(json, 0)
711}
712
713/// Maximum nesting depth [`parse_expr_value`] recurses into.
714///
715/// Matches the depth cap the resolver enforces on ceremony YAML, so values
716/// that went through resolution never reach this limit.
717const MAX_VALUE_DEPTH: usize = 64;
718
719/// Depth-tracking worker for [`parse_expr_value`].
720///
721/// Values nested deeper than [`MAX_VALUE_DEPTH`] are truncated to
722/// `Literal(Null)`: the function returns a value rather than a `Result`, so
723/// truncation is the least invasive way to stay total on adversarial input
724/// without overflowing the stack. The resolver rejects ceremony files nested
725/// past the same cap before values reach this function, so the truncation is
726/// a defensive backstop, not user-visible behavior.
727fn parse_expr_value_at(json: &serde_json::Value, depth: usize) -> ExprValue {
728    if depth >= MAX_VALUE_DEPTH {
729        return ExprValue::Literal(Literal::Null);
730    }
731    let child_depth = depth.saturating_add(1);
732    match json {
733        serde_json::Value::String(s) => parse_string_to_expr_value(s),
734        serde_json::Value::Number(n) => {
735            if let Some(i) = n.as_i64() {
736                ExprValue::Literal(Literal::Integer(i))
737            } else if let Some(f) = n.as_f64() {
738                ExprValue::Literal(Literal::Float(f))
739            } else {
740                // Fallback for very large numbers: truncate to 0.
741                ExprValue::Literal(Literal::Integer(0))
742            }
743        }
744        serde_json::Value::Bool(b) => ExprValue::Literal(Literal::Boolean(*b)),
745        serde_json::Value::Null => ExprValue::Literal(Literal::Null),
746        serde_json::Value::Object(map) => {
747            let mut result = std::collections::HashMap::new();
748            for (k, v) in map {
749                result.insert(k.clone(), parse_expr_value_at(v, child_depth));
750            }
751            ExprValue::Object(result)
752        }
753        serde_json::Value::Array(arr) => {
754            let result: Vec<_> = arr
755                .iter()
756                .map(|v| parse_expr_value_at(v, child_depth))
757                .collect();
758            ExprValue::Array(result)
759        }
760    }
761}
762
763/// Parse a string into an `ExprValue`.
764///
765/// Handles three cases:
766/// 1. Entire string is one expression: `${...}` → `ExprValue::Expr`
767/// 2. String contains expressions: `"Hash: ${...}"` → `ExprValue::Interpolated`
768/// 3. Plain string: `"hello"` → `ExprValue::Literal`
769fn parse_string_to_expr_value(s: &str) -> ExprValue {
770    let trimmed = s.trim();
771
772    // Case 1: Entire string is a single expression.
773    if trimmed.starts_with("${")
774        && trimmed.ends_with('}')
775        && !trimmed[2..trimmed.len() - 1].contains("${")
776        && let Some(expr) = parse_expression(trimmed)
777    {
778        return ExprValue::Expr(expr);
779    }
780
781    // Case 2: String contains embedded expressions.
782    if s.contains("${") {
783        let parts = parse_interpolated_string(s);
784        if parts.len() == 1 {
785            // Single literal part.
786            if let StringPart::Literal(lit) = &parts[0] {
787                return ExprValue::Literal(Literal::String(lit.clone()));
788            }
789        }
790        return ExprValue::Interpolated(parts);
791    }
792
793    // Case 3: Plain string.
794    ExprValue::Literal(Literal::String(s.to_string()))
795}
796
797/// Parse an interpolated string into parts.
798///
799/// `"Hello ${param.name}!"` → `[Literal("Hello "), Expr(...), Literal("!")]`
800fn parse_interpolated_string(s: &str) -> Vec<StringPart> {
801    let mut parts = Vec::new();
802    let bytes = s.as_bytes();
803    let mut last_end = 0;
804    let mut i = 0;
805
806    while i < bytes.len() {
807        if bytes[i] == b'$' && i + 1 < bytes.len() && bytes[i + 1] == b'{' {
808            // Push literal part before this expression.
809            if i > last_end {
810                parts.push(StringPart::Literal(s[last_end..i].to_string()));
811            }
812
813            // Find matching closing brace.
814            let mut depth = 1;
815            let mut j = i + 2;
816            while j < bytes.len() && depth > 0 {
817                match bytes[j] {
818                    b'{' => depth += 1,
819                    b'}' => depth -= 1,
820                    _ => {}
821                }
822                j += 1;
823            }
824
825            if depth == 0 {
826                let expr_str = &s[i..j];
827                if let Some(expr) = parse_expression(expr_str) {
828                    parts.push(StringPart::Expr(expr));
829                } else {
830                    // Failed to parse: keep as literal.
831                    parts.push(StringPart::Literal(expr_str.to_string()));
832                }
833                last_end = j;
834                i = j;
835                continue;
836            }
837        }
838        i += 1;
839    }
840
841    // Push remaining literal part.
842    if last_end < s.len() {
843        parts.push(StringPart::Literal(s[last_end..].to_string()));
844    }
845
846    parts
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852
853    #[test]
854    fn parse_simple_reference() {
855        let expr = parse_expression("${artifact.ksr}").unwrap();
856        match expr {
857            Expression::Reference(r) => {
858                assert_eq!(r.ref_type, RefType::Artifact);
859                assert_eq!(r.name, "ksr");
860                assert!(r.property.is_none());
861            }
862            _ => panic!("Expected Reference"),
863        }
864    }
865
866    #[test]
867    fn parse_reference_with_property() {
868        let expr = parse_expression("${artifact.keypair.private}").unwrap();
869        match expr {
870            Expression::Reference(r) => {
871                assert_eq!(r.ref_type, RefType::Artifact);
872                assert_eq!(r.name, "keypair");
873                assert_eq!(r.property, Some("private".to_string()));
874            }
875            _ => panic!("Expected Reference"),
876        }
877    }
878
879    #[test]
880    fn parse_param_reference() {
881        let expr = parse_expression("${param.key_label}").unwrap();
882        match expr {
883            Expression::Reference(r) => {
884                assert_eq!(r.ref_type, RefType::Param);
885                assert_eq!(r.name, "key_label");
886            }
887            _ => panic!("Expected Reference"),
888        }
889    }
890
891    #[test]
892    fn parse_simple_pipeline() {
893        let expr = parse_expression("${artifact.ksr | sha256}").unwrap();
894        match expr {
895            Expression::Pipeline { source, stages } => {
896                assert!(matches!(*source, Expression::Reference(_)));
897                assert_eq!(stages.len(), 1);
898                assert_eq!(stages[0].function, "sha256");
899                assert!(stages[0].args.is_empty());
900            }
901            _ => panic!("Expected Pipeline"),
902        }
903    }
904
905    #[test]
906    fn parse_multi_stage_pipeline() {
907        let expr = parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
908        match expr {
909            Expression::Pipeline { stages, .. } => {
910                assert_eq!(stages.len(), 2);
911                assert_eq!(stages[0].function, "sha256");
912                assert_eq!(stages[1].function, "hex");
913            }
914            _ => panic!("Expected Pipeline"),
915        }
916    }
917
918    #[test]
919    fn parse_pipeline_with_function_args() {
920        let expr = parse_expression("${artifact.data | substr(0, 16)}").unwrap();
921        match expr {
922            Expression::Pipeline { stages, .. } => {
923                assert_eq!(stages.len(), 1);
924                assert_eq!(stages[0].function, "substr");
925                assert_eq!(stages[0].args.len(), 2);
926            }
927            _ => panic!("Expected Pipeline"),
928        }
929    }
930
931    #[test]
932    fn parse_complex_pipeline() {
933        let expr =
934            parse_expression("${artifact.keypair.public | fingerprint | hex | upper}").unwrap();
935        match expr {
936            Expression::Pipeline { source, stages } => {
937                if let Expression::Reference(r) = *source {
938                    assert_eq!(r.name, "keypair");
939                    assert_eq!(r.property, Some("public".to_string()));
940                }
941                assert_eq!(stages.len(), 3);
942                assert_eq!(stages[0].function, "fingerprint");
943                assert_eq!(stages[1].function, "hex");
944                assert_eq!(stages[2].function, "upper");
945            }
946            _ => panic!("Expected Pipeline"),
947        }
948    }
949
950    #[test]
951    fn parse_invalid_expression() {
952        assert!(parse_expression("not an expression").is_none());
953        assert!(parse_expression("${").is_none());
954        assert!(parse_expression("${}").is_none());
955        assert!(parse_expression("${invalid}").is_none()); // No namespace
956    }
957
958    #[test]
959    fn find_expressions_in_string() {
960        let exprs = find_expressions("Hash: ${artifact.ksr | sha256 | hex}");
961        assert_eq!(exprs.len(), 1);
962        assert_eq!(exprs[0].range.start, 6);
963    }
964
965    #[test]
966    fn find_multiple_expressions() {
967        let exprs = find_expressions("${artifact.a | sha256 | hex}-${artifact.b | sha256 | hex}");
968        assert_eq!(exprs.len(), 2);
969    }
970
971    #[test]
972    fn value_type_names() {
973        assert_eq!(Value::Bytes(vec![]).type_name(), "bytes");
974        assert_eq!(Value::String(String::new()).type_name(), "string");
975        assert_eq!(Value::Integer(0).type_name(), "integer");
976        assert_eq!(Value::Boolean(true).type_name(), "boolean");
977        assert_eq!(Value::Null.type_name(), "null");
978    }
979
980    #[test]
981    fn value_as_bytes() {
982        let v = Value::Bytes(vec![1, 2, 3]);
983        assert_eq!(v.as_bytes(), Some(vec![1, 2, 3]));
984
985        let v = Value::String("hello".into());
986        assert_eq!(v.as_bytes(), Some(b"hello".to_vec()));
987
988        let v = Value::Integer(42);
989        assert!(v.as_bytes().is_none());
990    }
991
992    mod ceremony_examples {
993        use super::*;
994
995        // From dnssec_signing/dnssec_signing.rite.yaml
996        #[test]
997        fn dnssec_hash_verification() {
998            // actual: "${artifact.ksr | sha256 | hex | substr(0, 16)}"
999            let expr = parse_expression("${artifact.ksr | sha256 | hex | substr(0, 16)}").unwrap();
1000            match expr {
1001                Expression::Pipeline { source, stages } => {
1002                    if let Expression::Reference(r) = *source {
1003                        assert_eq!(r.ref_type, RefType::Artifact);
1004                        assert_eq!(r.name, "ksr");
1005                    } else {
1006                        panic!("Expected Reference source");
1007                    }
1008                    // 3 stages: sha256, hex, substr
1009                    assert_eq!(stages.len(), 3);
1010                    assert_eq!(stages[0].function, "sha256");
1011                    assert_eq!(stages[1].function, "hex");
1012                    assert_eq!(stages[2].function, "substr");
1013                    assert_eq!(stages[2].args.len(), 2);
1014                }
1015                _ => panic!("Expected Pipeline"),
1016            }
1017        }
1018
1019        #[test]
1020        fn dnssec_param_reference() {
1021            // expected: "${param.expected_ksr_hash}"
1022            let expr = parse_expression("${param.expected_ksr_hash}").unwrap();
1023            match expr {
1024                Expression::Reference(r) => {
1025                    assert_eq!(r.ref_type, RefType::Param);
1026                    assert_eq!(r.name, "expected_ksr_hash");
1027                }
1028                _ => panic!("Expected Reference"),
1029            }
1030        }
1031
1032        #[test]
1033        fn dnssec_oral_readback() {
1034            // value: "${artifact.ksr | sha256 | hex}"
1035            let expr = parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
1036            match expr {
1037                Expression::Pipeline { stages, .. } => {
1038                    assert_eq!(stages.len(), 2);
1039                    assert_eq!(stages[0].function, "sha256");
1040                    assert_eq!(stages[1].function, "hex");
1041                }
1042                _ => panic!("Expected Pipeline"),
1043            }
1044        }
1045
1046        // From airgapped_hsm/airgapped_hsm.rite.yaml
1047        #[test]
1048        fn hsm_artifact_reference() {
1049            // input: ${artifact.master_keypair}
1050            let expr = parse_expression("${artifact.master_keypair}").unwrap();
1051            match expr {
1052                Expression::Reference(r) => {
1053                    assert_eq!(r.ref_type, RefType::Artifact);
1054                    assert_eq!(r.name, "master_keypair");
1055                    assert!(r.property.is_none());
1056                }
1057                _ => panic!("Expected Reference"),
1058            }
1059        }
1060
1061        #[test]
1062        fn hsm_public_key_fingerprint() {
1063            // value: "${artifact.master_public_key | sha256 | hex}"
1064            let expr = parse_expression("${artifact.master_public_key | sha256 | hex}").unwrap();
1065            match expr {
1066                Expression::Pipeline { source, stages } => {
1067                    if let Expression::Reference(r) = *source {
1068                        assert_eq!(r.name, "master_public_key");
1069                    }
1070                    assert_eq!(stages.len(), 2);
1071                }
1072                _ => panic!("Expected Pipeline"),
1073            }
1074        }
1075
1076        // From crypto_test/crypto_test.rite.yaml
1077        #[test]
1078        fn crypto_artifact_with_property() {
1079            // key_to_wrap: ${artifact.keypair.private}
1080            let expr = parse_expression("${artifact.keypair.private}").unwrap();
1081            match expr {
1082                Expression::Reference(r) => {
1083                    assert_eq!(r.ref_type, RefType::Artifact);
1084                    assert_eq!(r.name, "keypair");
1085                    assert_eq!(r.property, Some("private".to_string()));
1086                }
1087                _ => panic!("Expected Reference"),
1088            }
1089        }
1090
1091        // String interpolation patterns
1092        #[test]
1093        fn find_embedded_expressions() {
1094            // message: "Verify serial number: ${param.hsm_serial}"
1095            let exprs = find_expressions("Verify serial number: ${param.hsm_serial}");
1096            assert_eq!(exprs.len(), 1);
1097            if let Expression::Reference(r) = &exprs[0].expression {
1098                assert_eq!(r.ref_type, RefType::Param);
1099                assert_eq!(r.name, "hsm_serial");
1100            }
1101        }
1102
1103        #[test]
1104        fn find_multiple_embedded_params() {
1105            // "Key ${param.key_label} for HSM ${param.hsm_serial}"
1106            let exprs = find_expressions("Key ${param.key_label} for HSM ${param.hsm_serial}");
1107            assert_eq!(exprs.len(), 2);
1108        }
1109
1110        // Role references (namespace = "role" - used in validation, not expressions)
1111        #[test]
1112        fn role_reference_parses() {
1113            let expr = parse_expression("${role.ceremony_admin}").unwrap();
1114            match expr {
1115                Expression::Reference(r) => {
1116                    assert_eq!(r.ref_type, RefType::Role);
1117                    assert_eq!(r.name, "ceremony_admin");
1118                }
1119                _ => panic!("Expected Reference"),
1120            }
1121        }
1122    }
1123
1124    mod expr_value_tests {
1125        use super::*;
1126
1127        #[test]
1128        fn parse_literal_string() {
1129            let json = serde_json::json!("hello");
1130            let expr = parse_expr_value(&json);
1131            assert!(matches!(expr, ExprValue::Literal(Literal::String(s)) if s == "hello"));
1132        }
1133
1134        #[test]
1135        fn parse_literal_integer() {
1136            let json = serde_json::json!(42);
1137            let expr = parse_expr_value(&json);
1138            assert!(matches!(expr, ExprValue::Literal(Literal::Integer(42))));
1139        }
1140
1141        #[test]
1142        fn parse_literal_float() {
1143            let json = serde_json::json!(1.5);
1144            let expr = parse_expr_value(&json);
1145            assert!(
1146                matches!(expr, ExprValue::Literal(Literal::Float(f)) if (f - 1.5).abs() < 0.001)
1147            );
1148        }
1149
1150        #[test]
1151        fn parse_literal_boolean() {
1152            let json = serde_json::json!(true);
1153            let expr = parse_expr_value(&json);
1154            assert!(matches!(expr, ExprValue::Literal(Literal::Boolean(true))));
1155        }
1156
1157        #[test]
1158        fn parse_literal_null() {
1159            let json = serde_json::Value::Null;
1160            let expr = parse_expr_value(&json);
1161            assert!(matches!(expr, ExprValue::Literal(Literal::Null)));
1162        }
1163
1164        #[test]
1165        fn parse_single_expression() {
1166            let json = serde_json::json!("${artifact.ksr | sha256}");
1167            let expr = parse_expr_value(&json);
1168            match expr {
1169                ExprValue::Expr(Expression::Pipeline { source, stages }) => {
1170                    assert!(matches!(*source, Expression::Reference(_)));
1171                    assert_eq!(stages.len(), 1);
1172                    assert_eq!(stages[0].function, "sha256");
1173                }
1174                _ => panic!("Expected Expr(Pipeline), got {expr:?}"),
1175            }
1176        }
1177
1178        #[test]
1179        fn parse_simple_reference() {
1180            let json = serde_json::json!("${param.name}");
1181            let expr = parse_expr_value(&json);
1182            match expr {
1183                ExprValue::Expr(Expression::Reference(r)) => {
1184                    assert_eq!(r.ref_type, RefType::Param);
1185                    assert_eq!(r.name, "name");
1186                }
1187                _ => panic!("Expected Expr(Reference), got {expr:?}"),
1188            }
1189        }
1190
1191        #[test]
1192        fn parse_interpolated_string() {
1193            let json = serde_json::json!("Hello, ${param.name}!");
1194            let expr = parse_expr_value(&json);
1195            match expr {
1196                ExprValue::Interpolated(parts) => {
1197                    assert_eq!(parts.len(), 3);
1198                    assert!(matches!(&parts[0], StringPart::Literal(s) if s == "Hello, "));
1199                    assert!(matches!(
1200                        &parts[1],
1201                        StringPart::Expr(Expression::Reference(_))
1202                    ));
1203                    assert!(matches!(&parts[2], StringPart::Literal(s) if s == "!"));
1204                }
1205                _ => panic!("Expected Interpolated, got {expr:?}"),
1206            }
1207        }
1208
1209        #[test]
1210        fn parse_object_with_expressions() {
1211            let json = serde_json::json!({
1212                "actual": "${artifact.ksr | sha256 | hex}",
1213                "expected": "${param.expected_hash}",
1214                "static": "hello"
1215            });
1216            let expr = parse_expr_value(&json);
1217            match expr {
1218                ExprValue::Object(map) => {
1219                    assert!(matches!(map.get("actual"), Some(ExprValue::Expr(_))));
1220                    assert!(matches!(map.get("expected"), Some(ExprValue::Expr(_))));
1221                    assert!(matches!(
1222                        map.get("static"),
1223                        Some(ExprValue::Literal(Literal::String(_)))
1224                    ));
1225                }
1226                _ => panic!("Expected Object, got {expr:?}"),
1227            }
1228        }
1229
1230        #[test]
1231        fn parse_array_with_expressions() {
1232            let json = serde_json::json!(["${param.a}", "static", "${param.b}"]);
1233            let expr = parse_expr_value(&json);
1234            match expr {
1235                ExprValue::Array(arr) => {
1236                    assert_eq!(arr.len(), 3);
1237                    assert!(matches!(&arr[0], ExprValue::Expr(_)));
1238                    assert!(matches!(&arr[1], ExprValue::Literal(Literal::String(_))));
1239                    assert!(matches!(&arr[2], ExprValue::Expr(_)));
1240                }
1241                _ => panic!("Expected Array, got {expr:?}"),
1242            }
1243        }
1244
1245        #[test]
1246        fn parse_deeply_nested_value_is_truncated_not_crashed() {
1247            // 200 levels of nesting: far past the cap. The function must
1248            // return (no stack overflow), truncating everything below
1249            // MAX_VALUE_DEPTH to Literal(Null).
1250            let mut json = serde_json::json!("leaf");
1251            for _ in 0..200 {
1252                json = serde_json::Value::Array(vec![json]);
1253            }
1254            let mut expr = parse_expr_value(&json);
1255            let mut levels = 0;
1256            loop {
1257                match expr {
1258                    ExprValue::Array(mut arr) => {
1259                        assert_eq!(arr.len(), 1);
1260                        expr = arr.remove(0);
1261                        levels += 1;
1262                    }
1263                    other => {
1264                        assert!(
1265                            matches!(other, ExprValue::Literal(Literal::Null)),
1266                            "truncated tail must be Literal(Null), got {other:?}"
1267                        );
1268                        break;
1269                    }
1270                }
1271            }
1272            assert_eq!(levels, MAX_VALUE_DEPTH, "recursion must stop at the cap");
1273        }
1274
1275        #[test]
1276        fn parse_moderately_nested_value_is_preserved() {
1277            let mut json = serde_json::json!("leaf");
1278            for _ in 0..10 {
1279                json = serde_json::Value::Array(vec![json]);
1280            }
1281            let mut expr = parse_expr_value(&json);
1282            for _ in 0..10 {
1283                match expr {
1284                    ExprValue::Array(mut arr) => {
1285                        assert_eq!(arr.len(), 1);
1286                        expr = arr.remove(0);
1287                    }
1288                    other => panic!("expected Array, got {other:?}"),
1289                }
1290            }
1291            assert!(matches!(expr, ExprValue::Literal(Literal::String(s)) if s == "leaf"));
1292        }
1293
1294        #[test]
1295        fn parse_multiple_expressions_in_string() {
1296            let json = serde_json::json!("${param.a} and ${param.b}");
1297            let expr = parse_expr_value(&json);
1298            match expr {
1299                ExprValue::Interpolated(parts) => {
1300                    assert_eq!(parts.len(), 3);
1301                    assert!(matches!(&parts[0], StringPart::Expr(_)));
1302                    assert!(matches!(&parts[1], StringPart::Literal(s) if s == " and "));
1303                    assert!(matches!(&parts[2], StringPart::Expr(_)));
1304                }
1305                _ => panic!("Expected Interpolated, got {expr:?}"),
1306            }
1307        }
1308    }
1309}