scud/attractor/
outcome.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum StageStatus {
10 Success,
12 Failure,
14 Skipped,
16 WaitingForHuman,
18 Timeout,
20 Cancelled,
22}
23
24impl StageStatus {
25 pub fn is_success(&self) -> bool {
26 matches!(self, StageStatus::Success)
27 }
28
29 pub fn as_str(&self) -> &str {
30 match self {
31 StageStatus::Success => "success",
32 StageStatus::Failure => "failure",
33 StageStatus::Skipped => "skipped",
34 StageStatus::WaitingForHuman => "waiting_for_human",
35 StageStatus::Timeout => "timeout",
36 StageStatus::Cancelled => "cancelled",
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Outcome {
44 pub status: StageStatus,
46 pub preferred_label: Option<String>,
48 pub suggested_next: Vec<String>,
50 pub context_updates: HashMap<String, serde_json::Value>,
52 pub response_text: Option<String>,
54 pub summary: Option<String>,
56}
57
58impl Outcome {
59 pub fn success() -> Self {
61 Self {
62 status: StageStatus::Success,
63 preferred_label: None,
64 suggested_next: vec![],
65 context_updates: HashMap::new(),
66 response_text: None,
67 summary: None,
68 }
69 }
70
71 pub fn failure(message: impl Into<String>) -> Self {
73 Self {
74 status: StageStatus::Failure,
75 preferred_label: None,
76 suggested_next: vec![],
77 context_updates: HashMap::new(),
78 response_text: None,
79 summary: Some(message.into()),
80 }
81 }
82
83 pub fn success_with_label(label: impl Into<String>) -> Self {
85 Self {
86 status: StageStatus::Success,
87 preferred_label: Some(label.into()),
88 suggested_next: vec![],
89 context_updates: HashMap::new(),
90 response_text: None,
91 summary: None,
92 }
93 }
94
95 pub fn with_context(mut self, updates: HashMap<String, serde_json::Value>) -> Self {
97 self.context_updates = updates;
98 self
99 }
100
101 pub fn with_response(mut self, text: impl Into<String>) -> Self {
103 self.response_text = Some(text.into());
104 self
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_success_outcome() {
114 let o = Outcome::success();
115 assert!(o.status.is_success());
116 assert!(o.preferred_label.is_none());
117 assert!(o.context_updates.is_empty());
118 }
119
120 #[test]
121 fn test_failure_outcome() {
122 let o = Outcome::failure("bad things");
123 assert!(!o.status.is_success());
124 assert_eq!(o.summary.as_deref(), Some("bad things"));
125 }
126
127 #[test]
128 fn test_success_with_label() {
129 let o = Outcome::success_with_label("approve");
130 assert!(o.status.is_success());
131 assert_eq!(o.preferred_label.as_deref(), Some("approve"));
132 }
133
134 #[test]
135 fn test_with_context() {
136 let mut ctx = HashMap::new();
137 ctx.insert("key".into(), serde_json::json!("value"));
138 let o = Outcome::success().with_context(ctx);
139 assert_eq!(o.context_updates.len(), 1);
140 }
141}