Skip to main content

made_core/value_objects/ceremony/
guard_condition.rs

1use 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}