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