1use std::collections::HashMap;
6use std::time::{Duration, Instant};
7
8pub type State = HashMap<String, serde_json::Value>;
10
11#[derive(Debug, thiserror::Error)]
13pub enum StateError {
14 #[error("state key '{0}' is missing")]
16 MissingKey(String),
17
18 #[error("failed to deserialize state key '{0}': {1}")]
20 Deserialize(String, String),
21}
22
23pub type StateReducer = Box<
36 dyn Fn(&serde_json::Value, &serde_json::Value) -> Result<serde_json::Value, String>
37 + Send
38 + Sync,
39>;
40
41pub trait StateExt {
43 fn get_str(&self, key: &str) -> Option<&str>;
47
48 fn get_bool(&self, key: &str) -> Option<bool>;
50
51 fn get_u64(&self, key: &str) -> Option<u64>;
53
54 fn get_i64(&self, key: &str) -> Option<i64>;
56
57 fn get_f64(&self, key: &str) -> Option<f64>;
59
60 fn get_json<T>(&self, key: &str) -> Result<T, StateError>
62 where
63 T: serde::de::DeserializeOwned;
64
65 fn require<T>(&self, key: &str) -> Result<T, StateError>
67 where
68 T: serde::de::DeserializeOwned;
69
70 fn set<T>(&mut self, key: impl Into<String>, value: T)
72 where
73 T: serde::Serialize;
74
75 fn remove(&mut self, key: &str) -> Option<serde_json::Value>;
77
78 fn contains(&self, key: &str) -> bool;
80
81 fn reduce(
85 &mut self,
86 key: &str,
87 value: serde_json::Value,
88 reducer: &StateReducer,
89 ) -> Result<(), String>;
90
91 fn append_array(&mut self, key: &str, items: serde_json::Value) -> Result<(), String>;
93}
94
95impl StateExt for State {
96 fn get_str(&self, key: &str) -> Option<&str> {
97 self.get(key).and_then(|v| v.as_str())
98 }
99
100 fn get_bool(&self, key: &str) -> Option<bool> {
101 self.get(key).and_then(|v| v.as_bool())
102 }
103
104 fn get_u64(&self, key: &str) -> Option<u64> {
105 self.get(key).and_then(|v| v.as_u64())
106 }
107
108 fn get_i64(&self, key: &str) -> Option<i64> {
109 self.get(key).and_then(|v| v.as_i64())
110 }
111
112 fn get_f64(&self, key: &str) -> Option<f64> {
113 self.get(key).and_then(|v| v.as_f64())
114 }
115
116 fn get_json<T>(&self, key: &str) -> Result<T, StateError>
117 where
118 T: serde::de::DeserializeOwned,
119 {
120 let value = self
121 .get(key)
122 .ok_or_else(|| StateError::MissingKey(key.to_string()))?;
123 serde_json::from_value(value.clone())
124 .map_err(|e| StateError::Deserialize(key.to_string(), e.to_string()))
125 }
126
127 fn require<T>(&self, key: &str) -> Result<T, StateError>
128 where
129 T: serde::de::DeserializeOwned,
130 {
131 self.get_json(key)
132 }
133
134 fn set<T>(&mut self, key: impl Into<String>, value: T)
135 where
136 T: serde::Serialize,
137 {
138 let json = serde_json::to_value(value).unwrap_or(serde_json::Value::Null);
139 HashMap::insert(self, key.into(), json);
140 }
141
142 fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
143 HashMap::remove(self, key)
144 }
145
146 fn contains(&self, key: &str) -> bool {
147 self.contains_key(key)
148 }
149
150 fn reduce(
151 &mut self,
152 key: &str,
153 value: serde_json::Value,
154 reducer: &StateReducer,
155 ) -> Result<(), String> {
156 if let Some(existing) = self.get(key) {
157 let merged = reducer(existing, &value)?;
158 self.insert(key.to_string(), merged);
159 } else {
160 self.insert(key.to_string(), value);
161 }
162 Ok(())
163 }
164
165 fn append_array(&mut self, key: &str, items: serde_json::Value) -> Result<(), String> {
166 let new_items = items.as_array().ok_or("append_array expects an array")?;
167 if let Some(existing) = self.get(key) {
168 let mut arr = existing
169 .as_array()
170 .ok_or("append_array: existing value is not an array")?
171 .clone();
172 arr.extend(new_items.iter().cloned());
173 self.insert(key.to_string(), serde_json::Value::Array(arr));
174 } else {
175 self.insert(key.to_string(), items);
176 }
177 Ok(())
178 }
179}
180
181pub fn array_reducer() -> StateReducer {
191 Box::new(|existing: &serde_json::Value, new: &serde_json::Value| {
192 let mut arr = existing
193 .as_array()
194 .ok_or("existing value is not an array")?
195 .clone();
196 let additions = new.as_array().ok_or("new value is not an array")?;
197 arr.extend(additions.iter().cloned());
198 Ok(serde_json::Value::Array(arr))
199 })
200}
201
202#[derive(Debug)]
204pub struct GraphResult {
205 pub state: State,
207 pub execution_log: Vec<ExecutionEntry>,
209 pub duration: Duration,
211}
212
213#[derive(Debug, Clone)]
215pub struct ExecutionEntry {
216 pub node_name: String,
218 pub start_time: Instant,
220 pub end_time: Instant,
222 pub success: bool,
224}
225
226impl ExecutionEntry {
227 pub fn elapsed(&self) -> Duration {
229 self.end_time.duration_since(self.start_time)
230 }
231}