made_core/value_objects/ceremony/
guard_condition.rs1use serde::{Deserialize, Serialize};
2
3use super::{
4 JoinStepCount, OutputFieldGuardCondition, StepId, StepRepeatExhaustedGuardCondition, StepStatus,
5};
6
7mod counted_join_condition;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(tag = "kind", rename_all = "snake_case")]
11pub enum GuardCondition {
12 Always,
13 AllStepsCompleted,
14 AnyStepCompleted,
15 StepsCompleted(#[serde(with = "counted_join_condition")] JoinStepCount),
16 StepStatus { step_id: StepId, status: StepStatus },
17 OutputField(OutputFieldGuardCondition),
18 StepRepeatExhausted(StepRepeatExhaustedGuardCondition),
19 HumanApproval,
20}
21
22impl GuardCondition {
23 #[must_use]
24 pub fn referenced_step_id(&self) -> Option<&StepId> {
25 match self {
26 Self::StepStatus { step_id, .. } => Some(step_id),
27 Self::OutputField(condition) => Some(condition.step_id()),
28 Self::StepRepeatExhausted(condition) => Some(condition.step_id()),
29 Self::Always
30 | Self::AllStepsCompleted
31 | Self::AnyStepCompleted
32 | Self::StepsCompleted(_)
33 | Self::HumanApproval => None,
34 }
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn counted_join_serializes_as_tagged_object() {
44 let condition = GuardCondition::StepsCompleted(JoinStepCount::new(2).unwrap());
45 let encoded = serde_json::to_string(&condition).unwrap();
46 assert_eq!(encoded, r#"{"kind":"steps_completed","count":2}"#);
47 assert_eq!(
48 serde_json::from_str::<GuardCondition>(&encoded).unwrap(),
49 condition
50 );
51 }
52
53 #[test]
54 fn counted_join_deserialization_preserves_positive_count_invariant() {
55 for encoded in [
56 r#"{"kind":"steps_completed","count":0}"#,
57 r#"{"kind":"steps_completed","count":-1}"#,
58 r#"{"kind":"steps_completed"}"#,
59 r#"{"kind":"steps_completed","count":4294967296}"#,
60 ] {
61 assert!(
62 serde_json::from_str::<GuardCondition>(encoded).is_err(),
63 "{encoded}"
64 );
65 }
66 }
67
68 #[test]
69 fn other_guard_variants_keep_their_exact_canonical_bytes() {
70 use crate::value_objects::StepOutputField;
71 let step = StepId::new("inspect_api").unwrap();
72 let cases = [
73 (GuardCondition::Always, r#"{"kind":"always"}"#),
74 (
75 GuardCondition::AllStepsCompleted,
76 r#"{"kind":"all_steps_completed"}"#,
77 ),
78 (
79 GuardCondition::AnyStepCompleted,
80 r#"{"kind":"any_step_completed"}"#,
81 ),
82 (
83 GuardCondition::HumanApproval,
84 r#"{"kind":"human_approval"}"#,
85 ),
86 (
87 GuardCondition::StepStatus {
88 step_id: step.clone(),
89 status: StepStatus::Completed,
90 },
91 r#"{"kind":"step_status","step_id":"inspect_api","status":"COMPLETED"}"#,
92 ),
93 (
94 GuardCondition::OutputField(OutputFieldGuardCondition::new(
95 step.clone(),
96 StepOutputField::new("approved").unwrap(),
97 serde_json::json!(true),
98 )),
99 r#"{"kind":"output_field","step_id":"inspect_api","output_field":"approved","expected":true}"#,
100 ),
101 (
102 GuardCondition::StepRepeatExhausted(StepRepeatExhaustedGuardCondition::new(step)),
103 r#"{"kind":"step_repeat_exhausted","step_id":"inspect_api"}"#,
104 ),
105 ];
106 for (condition, encoded) in cases {
107 assert_eq!(serde_json::to_string(&condition).unwrap(), encoded);
108 assert_eq!(
109 serde_json::from_str::<GuardCondition>(encoded).unwrap(),
110 condition
111 );
112 }
113 }
114}