1use crate::actions::ArtifactValue;
7use crate::artifact_resolver::resolve_artifact_bytes;
8use crate::executor::ExecutionError;
9use crate::state::HandlerContext;
10use base16ct::lower::encode_string as hex_encode_string;
11use base32ct::{Base32Unpadded, Encoding as Base32Encoding};
12use base64ct::{Base64, Encoding as Base64Encoding};
13use rite_model::expression::{
14 ExprValue, Expression, Literal, PipeStage, RefType, Reference, StringPart, Value,
15 literal_to_json,
16};
17use rite_model::{ArtifactId, ParamId};
18use sha2::{Digest, Sha256, Sha384, Sha512};
19
20pub fn evaluate(expr: &Expression, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
30 match expr {
31 Expression::Reference(ref_) => evaluate_reference(ref_, ctx),
32 Expression::Literal(lit) => Ok(evaluate_literal(lit)),
33 Expression::Pipeline { source, stages } => {
34 let mut value = evaluate(source, ctx)?;
35 for stage in stages {
36 value = apply_pipe_stage(&value, stage, ctx)?;
37 }
38 Ok(value)
39 }
40 }
41}
42
43fn evaluate_reference(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
45 match ref_.ref_type {
46 RefType::Param => evaluate_param_ref(ref_, ctx),
47 RefType::Artifact => evaluate_artifact_ref(ref_, ctx),
48 RefType::Role => Ok(Value::String(ref_.name.clone())),
51 }
52}
53
54fn evaluate_param_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
56 let param_id = ParamId::new(&ref_.name);
57 let value = ctx.params.get(¶m_id).ok_or_else(|| {
58 ExecutionError::InvalidParams(format!("Unknown parameter: {}", ref_.name))
59 })?;
60
61 json_to_value(value)
62}
63
64fn json_to_value(json: &serde_json::Value) -> Result<Value, ExecutionError> {
66 match json {
67 serde_json::Value::String(s) => Ok(Value::String(s.clone())),
68 serde_json::Value::Number(n) => {
69 if let Some(i) = n.as_i64() {
70 Ok(Value::Integer(i))
71 } else if let Some(f) = n.as_f64() {
72 #[allow(clippy::cast_possible_truncation)]
73 Ok(Value::Integer(f as i64))
74 } else {
75 Err(ExecutionError::InvalidParams(
76 "Number too large".to_string(),
77 ))
78 }
79 }
80 serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)),
81 serde_json::Value::Null => Ok(Value::Null),
82 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
83 Err(ExecutionError::InvalidParams(
84 "Cannot use array or object as expression value".to_string(),
85 ))
86 }
87 }
88}
89
90fn evaluate_artifact_ref(ref_: &Reference, ctx: &HandlerContext) -> Result<Value, ExecutionError> {
92 let artifact_id = ArtifactId::new(&ref_.name);
93 let artifact = ctx.get_artifact(&artifact_id).ok_or_else(|| {
94 ExecutionError::InvalidParams(format!("Artifact '{}' not found", ref_.name))
95 })?;
96
97 match artifact {
98 ArtifactValue::Text(text) => {
100 if let Some(property) = &ref_.property {
101 return Err(ExecutionError::InvalidParams(format!(
102 "Property access '.{property}' not supported for text artifacts"
103 )));
104 }
105 Ok(Value::String(text.clone()))
106 }
107
108 ArtifactValue::Bytes(_) | ArtifactValue::Secret(_) => {
112 if let Some(property) = &ref_.property {
114 return Err(ExecutionError::InvalidParams(format!(
115 "Property access '.{property}' not supported for binary artifacts"
116 )));
117 }
118 let bytes = resolve_artifact_bytes(ctx.artifacts, &artifact_id, None)?;
119 Ok(Value::bytes(bytes.to_vec()))
120 }
121
122 ArtifactValue::Shares(_) => Err(ExecutionError::InvalidParams(format!(
127 "'{artifact_id}' holds shares of a secret, which no expression renders; \
128 name the share under 'reads:' of a step that shows or combines it"
129 ))),
130
131 _ => {
133 let bytes =
134 resolve_artifact_bytes(ctx.artifacts, &artifact_id, ref_.property.as_deref())?;
135 Ok(Value::bytes(bytes.to_vec()))
136 }
137 }
138}
139
140fn evaluate_literal(lit: &Literal) -> Value {
142 match lit {
143 Literal::String(s) => Value::String(s.clone()),
144 Literal::Integer(i) => Value::Integer(*i),
145 #[allow(clippy::cast_possible_truncation)]
146 Literal::Float(f) => Value::Integer(*f as i64), Literal::Boolean(b) => Value::Boolean(*b),
148 Literal::Null => Value::Null,
149 }
150}
151
152fn apply_pipe_stage(
154 input: &Value,
155 stage: &PipeStage,
156 ctx: &HandlerContext,
157) -> Result<Value, ExecutionError> {
158 match stage.function.as_str() {
159 "sha256" => apply_sha256(input),
161 "sha384" => apply_sha384(input),
162 "sha512" => apply_sha512(input),
163
164 "hex" => apply_hex(input),
166 "base32" => apply_base32(input),
167 "base64" => apply_base64(input),
168
169 "upper" => apply_upper(input),
171 "lower" => apply_lower(input),
172 "substr" => apply_substr(input, &stage.args, ctx),
173
174 "concat" => apply_concat(&stage.args, ctx),
176
177 unknown => Err(ExecutionError::InvalidParams(format!(
178 "Unknown function: {unknown}"
179 ))),
180 }
181}
182
183fn apply_sha256(input: &Value) -> Result<Value, ExecutionError> {
184 let bytes = input.as_bytes().ok_or_else(|| {
185 ExecutionError::InvalidParams(format!(
186 "sha256 expects bytes or string, got {}",
187 input.type_name()
188 ))
189 })?;
190
191 let mut hasher = Sha256::new();
192 hasher.update(bytes);
193 let result = hasher.finalize();
194
195 Ok(Value::bytes(result.to_vec()))
196}
197
198fn apply_sha384(input: &Value) -> Result<Value, ExecutionError> {
199 let bytes = input.as_bytes().ok_or_else(|| {
200 ExecutionError::InvalidParams(format!(
201 "sha384 expects bytes or string, got {}",
202 input.type_name()
203 ))
204 })?;
205
206 let mut hasher = Sha384::new();
207 hasher.update(bytes);
208 let result = hasher.finalize();
209
210 Ok(Value::bytes(result.to_vec()))
211}
212
213fn apply_sha512(input: &Value) -> Result<Value, ExecutionError> {
214 let bytes = input.as_bytes().ok_or_else(|| {
215 ExecutionError::InvalidParams(format!(
216 "sha512 expects bytes or string, got {}",
217 input.type_name()
218 ))
219 })?;
220
221 let mut hasher = Sha512::new();
222 hasher.update(bytes);
223 let result = hasher.finalize();
224
225 Ok(Value::bytes(result.to_vec()))
226}
227
228fn apply_hex(input: &Value) -> Result<Value, ExecutionError> {
229 let bytes = input.as_bytes().ok_or_else(|| {
230 ExecutionError::InvalidParams(format!(
231 "hex expects bytes or string, got {}",
232 input.type_name()
233 ))
234 })?;
235
236 let hex_string = hex_encode(bytes);
237 Ok(Value::String(hex_string))
238}
239
240fn apply_base32(input: &Value) -> Result<Value, ExecutionError> {
241 let bytes = input.as_bytes().ok_or_else(|| {
242 ExecutionError::InvalidParams(format!(
243 "base32 expects bytes or string, got {}",
244 input.type_name()
245 ))
246 })?;
247
248 let encoded = Base32Unpadded::encode_string(bytes).to_uppercase();
250 Ok(Value::String(encoded))
251}
252
253fn apply_base64(input: &Value) -> Result<Value, ExecutionError> {
254 let bytes = input.as_bytes().ok_or_else(|| {
255 ExecutionError::InvalidParams(format!(
256 "base64 expects bytes or string, got {}",
257 input.type_name()
258 ))
259 })?;
260
261 let encoded = Base64::encode_string(bytes);
262 Ok(Value::String(encoded))
263}
264
265fn hex_encode(bytes: &[u8]) -> String {
267 hex_encode_string(bytes)
268}
269
270fn apply_upper(input: &Value) -> Result<Value, ExecutionError> {
271 let s = input.as_string().ok_or_else(|| {
272 ExecutionError::InvalidParams(format!("upper expects string, got {}", input.type_name()))
273 })?;
274
275 Ok(Value::String(s.to_uppercase()))
276}
277
278fn apply_lower(input: &Value) -> Result<Value, ExecutionError> {
279 let s = input.as_string().ok_or_else(|| {
280 ExecutionError::InvalidParams(format!("lower expects string, got {}", input.type_name()))
281 })?;
282
283 Ok(Value::String(s.to_lowercase()))
284}
285
286fn apply_substr(
287 input: &Value,
288 args: &[Expression],
289 ctx: &HandlerContext,
290) -> Result<Value, ExecutionError> {
291 let s = input.as_string().ok_or_else(|| {
292 ExecutionError::InvalidParams(format!("substr expects string, got {}", input.type_name()))
293 })?;
294
295 let [start_arg, len_arg] = args else {
296 return Err(ExecutionError::InvalidParams(
297 "substr requires 2 arguments: start and length".to_string(),
298 ));
299 };
300
301 let start_i64 = evaluate(start_arg, ctx)?
302 .as_integer()
303 .ok_or_else(|| ExecutionError::InvalidParams("substr start must be integer".to_string()))?;
304
305 let len_i64 = evaluate(len_arg, ctx)?.as_integer().ok_or_else(|| {
306 ExecutionError::InvalidParams("substr length must be integer".to_string())
307 })?;
308
309 let start = usize::try_from(start_i64.max(0)).unwrap_or(0).min(s.len());
311 let len = usize::try_from(len_i64.max(0)).unwrap_or(0);
312 let end = start.saturating_add(len).min(s.len());
313
314 Ok(Value::String(
315 s.get(start..end).unwrap_or_default().to_string(),
316 ))
317}
318
319fn apply_concat(args: &[Expression], ctx: &HandlerContext) -> Result<Value, ExecutionError> {
320 let values = args
321 .iter()
322 .map(|arg| evaluate(arg, ctx))
323 .collect::<Result<Vec<_>, _>>()?;
324 let parts = values
325 .iter()
326 .map(|value| {
327 value.as_bytes().ok_or_else(|| {
328 ExecutionError::InvalidParams(format!(
329 "concat expects bytes arguments, got {}",
330 value.type_name()
331 ))
332 })
333 })
334 .collect::<Result<Vec<_>, _>>()?;
335
336 let mut result = Vec::with_capacity(parts.iter().map(|part| part.len()).sum());
339 for part in parts {
340 result.extend_from_slice(part);
341 }
342
343 Ok(Value::bytes(result))
344}
345
346pub fn evaluate_expr_value(
359 value: &ExprValue,
360 ctx: &HandlerContext,
361) -> Result<serde_json::Value, ExecutionError> {
362 match value {
363 ExprValue::Literal(lit) => Ok(literal_to_json(lit)),
364 ExprValue::Expr(expr) => {
365 let result = evaluate(expr, ctx)?;
366 Ok(value_to_json(&result))
367 }
368 ExprValue::Interpolated(parts) => {
369 let mut result = String::new();
370 for part in parts {
371 match part {
372 StringPart::Literal(s) => result.push_str(s),
373 StringPart::Expr(expr) => {
374 let val = evaluate(expr, ctx)?;
375 result.push_str(&val.to_string());
376 }
377 }
378 }
379 Ok(serde_json::Value::String(result))
380 }
381 ExprValue::Object(map) => {
382 let mut result = serde_json::Map::new();
383 for (k, v) in map {
384 result.insert(k.clone(), evaluate_expr_value(v, ctx)?);
385 }
386 Ok(serde_json::Value::Object(result))
387 }
388 ExprValue::Array(arr) => {
389 let result: Result<Vec<_>, _> =
390 arr.iter().map(|v| evaluate_expr_value(v, ctx)).collect();
391 Ok(serde_json::Value::Array(result?))
392 }
393 }
394}
395
396pub fn evaluate_expr_value_to_string(
400 value: &ExprValue,
401 ctx: &HandlerContext,
402) -> Result<String, ExecutionError> {
403 match value {
404 ExprValue::Literal(Literal::String(s)) => Ok(s.clone()),
405 ExprValue::Literal(lit) => {
406 let json = literal_to_json(lit);
408 Ok(match json {
409 serde_json::Value::String(s) => s,
410 serde_json::Value::Number(n) => n.to_string(),
411 serde_json::Value::Bool(b) => b.to_string(),
412 serde_json::Value::Null => "null".to_string(),
413 _ => json.to_string(),
414 })
415 }
416 ExprValue::Expr(expr) => {
417 let result = evaluate(expr, ctx)?;
418 Ok(result.to_string())
419 }
420 ExprValue::Interpolated(parts) => {
421 let mut result = String::new();
422 for part in parts {
423 match part {
424 StringPart::Literal(s) => result.push_str(s),
425 StringPart::Expr(expr) => {
426 let val = evaluate(expr, ctx)?;
427 result.push_str(&val.to_string());
428 }
429 }
430 }
431 Ok(result)
432 }
433 ExprValue::Object(_) | ExprValue::Array(_) => {
434 let json = evaluate_expr_value(value, ctx)?;
436 Ok(json.to_string())
437 }
438 }
439}
440
441pub fn value_to_json(value: &Value) -> serde_json::Value {
443 match value {
444 Value::Bytes(b) => {
445 serde_json::Value::String(Base64::encode_string(b))
447 }
448 Value::String(s) => serde_json::Value::String(s.clone()),
449 Value::Integer(i) => serde_json::Value::Number((*i).into()),
450 Value::Boolean(b) => serde_json::Value::Bool(*b),
451 Value::Null => serde_json::Value::Null,
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::actions::ArtifactValue;
459 use rite_model::{MaterialId, RoleId};
460 use std::collections::HashMap;
461
462 fn empty_context() -> HandlerContext<'static> {
463 static EMPTY_PARAMS: std::sync::LazyLock<HashMap<ParamId, serde_json::Value>> =
464 std::sync::LazyLock::new(HashMap::new);
465 static EMPTY_ARTIFACTS: std::sync::LazyLock<HashMap<ArtifactId, ArtifactValue>> =
466 std::sync::LazyLock::new(HashMap::new);
467 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
468 std::sync::LazyLock::new(HashMap::new);
469 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
470 std::sync::LazyLock::new(HashMap::new);
471 HandlerContext {
472 params: &EMPTY_PARAMS,
473 artifacts: &EMPTY_ARTIFACTS,
474 roles: &EMPTY_ROLES,
475 materials: &EMPTY_MATERIALS,
476 }
477 }
478
479 fn make_context(
482 params: HashMap<ParamId, serde_json::Value>,
483 artifacts: HashMap<ArtifactId, ArtifactValue>,
484 ) -> HandlerContext<'static> {
485 static EMPTY_ROLES: std::sync::LazyLock<HashMap<RoleId, String>> =
486 std::sync::LazyLock::new(HashMap::new);
487 static EMPTY_MATERIALS: std::sync::LazyLock<HashMap<MaterialId, String>> =
488 std::sync::LazyLock::new(HashMap::new);
489 let params_box = Box::leak(Box::new(params));
490 let artifacts_box = Box::leak(Box::new(artifacts));
491 HandlerContext {
492 params: params_box,
493 artifacts: artifacts_box,
494 roles: &EMPTY_ROLES,
495 materials: &EMPTY_MATERIALS,
496 }
497 }
498
499 #[test]
502 fn a_share_set_is_refused_by_every_expression_form() {
503 let share = crate::Share::new(2, 1, vec![0xAB; 4]);
504 let mut artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
505 artifacts.insert(
506 ArtifactId::new("shares"),
507 ArtifactValue::Shares(crate::ShareSet::new(2, [share])),
508 );
509 let ctx = make_context(HashMap::new(), artifacts);
510
511 for text in [
512 "${artifact.shares}",
513 "${artifact.shares.share_1}",
514 "${artifact.shares | hex}",
515 ] {
516 let expr = rite_model::expression::parse_expression(text).unwrap();
517 let error = evaluate(&expr, &ctx).expect_err(text).to_string();
518 assert!(error.contains("no expression renders"), "{text}: {error}");
519 assert!(!error.contains("ab"), "{text}: {error}");
520 }
521 }
522
523 #[test]
524 fn sha256_of_a_string_matches_the_known_digest() {
525 let input = Value::String("hello".to_string());
526 let result = apply_sha256(&input).unwrap();
527
528 if let Value::Bytes(bytes) = result {
529 assert_eq!(bytes.len(), 32); let hex = hex_encode(&bytes);
531 assert_eq!(
532 hex,
533 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
534 );
535 } else {
536 panic!("Expected Bytes");
537 }
538 }
539
540 #[test]
541 fn sha256_of_bytes_produces_32_bytes() {
542 let input = Value::bytes(b"hello".to_vec());
543 let result = apply_sha256(&input).unwrap();
544
545 if let Value::Bytes(bytes) = result {
546 assert_eq!(bytes.len(), 32);
547 } else {
548 panic!("Expected Bytes");
549 }
550 }
551
552 #[test]
553 fn hex_encodes_bytes_in_lowercase() {
554 let input = Value::bytes(vec![0xde, 0xad, 0xbe, 0xef]);
555 let result = apply_hex(&input).unwrap();
556
557 assert_eq!(result, Value::String("deadbeef".to_string()));
558 }
559
560 #[test]
561 fn base32_encodes_bytes_unpadded_and_uppercase() {
562 let input = Value::bytes(b"hello".to_vec());
563 let result = apply_base32(&input).unwrap();
564
565 assert_eq!(result, Value::String("NBSWY3DP".to_string()));
567 }
568
569 #[test]
570 fn base64_encodes_bytes_with_padding() {
571 let input = Value::bytes(b"hello".to_vec());
572 let result = apply_base64(&input).unwrap();
573
574 assert_eq!(result, Value::String("aGVsbG8=".to_string()));
575 }
576
577 #[test]
578 fn upper_uppercases_a_string() {
579 let input = Value::String("hello".to_string());
580 let result = apply_upper(&input).unwrap();
581
582 assert_eq!(result, Value::String("HELLO".to_string()));
583 }
584
585 #[test]
586 fn lower_lowercases_a_string() {
587 let input = Value::String("HELLO".to_string());
588 let result = apply_lower(&input).unwrap();
589
590 assert_eq!(result, Value::String("hello".to_string()));
591 }
592
593 #[test]
594 fn a_pipeline_chains_sha256_into_hex() {
595 let ctx = empty_context();
596
597 let expr = Expression::Pipeline {
599 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
600 stages: vec![PipeStage::new("sha256"), PipeStage::new("hex")],
601 };
602
603 let result = evaluate(&expr, &ctx).unwrap();
604
605 if let Value::String(s) = result {
606 assert_eq!(
607 s,
608 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
609 );
610 } else {
611 panic!("Expected String");
612 }
613 }
614
615 #[test]
616 fn a_pipeline_chains_sha256_hex_and_upper() {
617 let ctx = empty_context();
618
619 let expr = Expression::Pipeline {
621 source: Box::new(Expression::Literal(Literal::String("hello".to_string()))),
622 stages: vec![
623 PipeStage::new("sha256"),
624 PipeStage::new("hex"),
625 PipeStage::new("upper"),
626 ],
627 };
628
629 let result = evaluate(&expr, &ctx).unwrap();
630
631 if let Value::String(s) = result {
632 assert_eq!(
633 s,
634 "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
635 );
636 } else {
637 panic!("Expected String");
638 }
639 }
640
641 #[test]
642 fn sha256_rejects_an_integer() {
643 let input = Value::Integer(42);
644 let result = apply_sha256(&input);
645
646 assert!(result.is_err());
647 }
648
649 #[test]
650 fn upper_rejects_bytes() {
651 let input = Value::bytes(vec![1, 2, 3]);
652 let result = apply_upper(&input);
653
654 assert!(result.is_err());
655 }
656
657 #[test]
658 fn substr_takes_a_prefix() {
659 let ctx = empty_context();
660
661 let expr = Expression::Pipeline {
663 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
664 stages: vec![PipeStage {
665 function: "substr".to_string(),
666 args: vec![
667 Expression::Literal(Literal::Integer(0)),
668 Expression::Literal(Literal::Integer(4)),
669 ],
670 }],
671 };
672
673 let result = evaluate(&expr, &ctx).unwrap();
674 assert_eq!(result, Value::String("abcd".to_string()));
675 }
676
677 #[test]
678 fn substr_takes_a_range_from_the_middle() {
679 let ctx = empty_context();
680
681 let expr = Expression::Pipeline {
683 source: Box::new(Expression::Literal(Literal::String("abcdefgh".to_string()))),
684 stages: vec![PipeStage {
685 function: "substr".to_string(),
686 args: vec![
687 Expression::Literal(Literal::Integer(2)),
688 Expression::Literal(Literal::Integer(4)),
689 ],
690 }],
691 };
692
693 let result = evaluate(&expr, &ctx).unwrap();
694 assert_eq!(result, Value::String("cdef".to_string()));
695 }
696
697 #[test]
698 fn an_artifact_reference_resolves_through_a_pipeline() {
699 let mut params = HashMap::new();
700 params.insert(ParamId::new("name"), serde_json::json!("test"));
701
702 let mut artifacts = HashMap::new();
703 artifacts.insert(
705 ArtifactId::new("ksr"),
706 ArtifactValue::Bytes(b"KSR content".to_vec()),
707 );
708
709 let ctx = make_context(params, artifacts);
710
711 let expr =
713 rite_model::expression::parse_expression("${artifact.ksr | sha256 | hex}").unwrap();
714 let result = evaluate(&expr, &ctx).unwrap();
715
716 if let Value::String(s) = result {
717 assert_eq!(s.len(), 64); } else {
719 panic!("Expected String");
720 }
721 }
722
723 #[test]
724 fn a_param_reference_resolves_to_its_value() {
725 let mut params = HashMap::new();
726 params.insert(ParamId::new("expected_hash"), serde_json::json!("abc123"));
727
728 let artifacts = HashMap::new();
729 let ctx = make_context(params, artifacts);
730
731 let expr = rite_model::expression::parse_expression("${param.expected_hash}").unwrap();
732 let result = evaluate(&expr, &ctx).unwrap();
733
734 assert_eq!(result, Value::String("abc123".to_string()));
735 }
736
737 #[test]
738 fn sha384_produces_48_bytes() {
739 let input = Value::String("hello".to_string());
740 let result = apply_sha384(&input).unwrap();
741
742 if let Value::Bytes(bytes) = result {
743 assert_eq!(bytes.len(), 48); } else {
745 panic!("Expected Bytes");
746 }
747 }
748
749 #[test]
750 fn sha512_produces_64_bytes() {
751 let input = Value::String("hello".to_string());
752 let result = apply_sha512(&input).unwrap();
753
754 if let Value::Bytes(bytes) = result {
755 assert_eq!(bytes.len(), 64); } else {
757 panic!("Expected Bytes");
758 }
759 }
760
761 #[test]
762 fn concat_joins_its_arguments_and_ignores_the_input() {
763 let ctx = empty_context();
764
765 let expr = Expression::Pipeline {
768 source: Box::new(Expression::Literal(Literal::String("ignored".to_string()))),
769 stages: vec![PipeStage {
770 function: "concat".to_string(),
771 args: vec![
772 Expression::Literal(Literal::String("hello".to_string())),
773 Expression::Literal(Literal::String(" ".to_string())),
774 Expression::Literal(Literal::String("world".to_string())),
775 ],
776 }],
777 };
778
779 let result = evaluate(&expr, &ctx).unwrap();
780
781 if let Value::Bytes(b) = result {
783 assert_eq!(b.as_slice(), b"hello world");
784 } else {
785 panic!("Expected Bytes, got {}", result.type_name());
786 }
787 }
788
789 #[test]
790 fn a_parsed_expression_string_evaluates_end_to_end() {
791 let ctx = empty_context();
792
793 let expr = rite_model::expression::parse_expression("${\"test\" | sha256 | hex}").unwrap();
794 let result = evaluate(&expr, &ctx).unwrap();
795
796 if let Value::String(s) = result {
797 assert_eq!(
799 s,
800 "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
801 );
802 } else {
803 panic!("Expected String");
804 }
805 }
806
807 mod ceremony_examples {
808 use super::*;
809
810 fn ceremony_context() -> HandlerContext<'static> {
811 let params: HashMap<ParamId, serde_json::Value> = [
812 ("ceremony_id", serde_json::json!("KC-2024-Q4")),
813 ("hsm_serial", serde_json::json!("HSM-001")),
814 ("key_label", serde_json::json!("MASTER-KEY")),
815 ("expected_ksr_hash", serde_json::json!("a3b8f4e91c2d6078")),
816 ("zone_name", serde_json::json!(".")),
817 ("signing_validity_days", serde_json::json!(21)),
818 ]
819 .into_iter()
820 .map(|(k, v)| (ParamId::new(k), v))
821 .collect();
822
823 let mut artifacts: HashMap<ArtifactId, ArtifactValue> = HashMap::new();
824 artifacts.insert(
825 ArtifactId::new("ksr"),
826 ArtifactValue::Bytes(b"KSR test content".to_vec()),
827 );
828 artifacts.insert(
829 ArtifactId::new("master_public_key"),
830 ArtifactValue::Bytes(b"PUBLIC KEY DATA".to_vec()),
831 );
832
833 make_context(params, artifacts)
834 }
835
836 #[test]
838 fn dnssec_hash_verification_full_pipeline() {
839 let ctx = ceremony_context();
840
841 let expr = rite_model::expression::parse_expression(
843 "${artifact.ksr | sha256 | hex | substr(0, 16)}",
844 )
845 .unwrap();
846 let result = evaluate(&expr, &ctx).unwrap();
847
848 if let Value::String(s) = result {
849 assert_eq!(s.len(), 16); assert!(s.chars().all(|c| c.is_ascii_hexdigit()));
851 } else {
852 panic!("Expected String");
853 }
854 }
855
856 #[test]
857 fn dnssec_param_comparison() {
858 let ctx = ceremony_context();
859
860 let expr =
862 rite_model::expression::parse_expression("${param.expected_ksr_hash}").unwrap();
863 let result = evaluate(&expr, &ctx).unwrap();
864
865 assert_eq!(result, Value::String("a3b8f4e91c2d6078".to_string()));
866 }
867
868 #[test]
869 fn hsm_fingerprint_computation() {
870 let ctx = ceremony_context();
871
872 let expr = rite_model::expression::parse_expression(
874 "${artifact.master_public_key | sha256 | hex}",
875 )
876 .unwrap();
877 let result = evaluate(&expr, &ctx).unwrap();
878
879 if let Value::String(s) = result {
880 assert_eq!(s.len(), 64); } else {
882 panic!("Expected String");
883 }
884 }
885 }
886}