use std::collections::HashMap;
use std::time::{Duration, Instant};
pub type State = HashMap<String, serde_json::Value>;
#[derive(Debug, thiserror::Error)]
pub enum StateError {
#[error("state key '{0}' is missing")]
MissingKey(String),
#[error("failed to deserialize state key '{0}': {1}")]
Deserialize(String, String),
}
pub type StateReducer = Box<
dyn Fn(&serde_json::Value, &serde_json::Value) -> Result<serde_json::Value, String>
+ Send
+ Sync,
>;
pub trait StateExt {
fn get_str(&self, key: &str) -> Option<&str>;
fn get_bool(&self, key: &str) -> Option<bool>;
fn get_u64(&self, key: &str) -> Option<u64>;
fn get_i64(&self, key: &str) -> Option<i64>;
fn get_f64(&self, key: &str) -> Option<f64>;
fn get_json<T>(&self, key: &str) -> Result<T, StateError>
where
T: serde::de::DeserializeOwned;
fn require<T>(&self, key: &str) -> Result<T, StateError>
where
T: serde::de::DeserializeOwned;
fn set<T>(&mut self, key: impl Into<String>, value: T)
where
T: serde::Serialize;
fn remove(&mut self, key: &str) -> Option<serde_json::Value>;
fn contains(&self, key: &str) -> bool;
fn reduce(
&mut self,
key: &str,
value: serde_json::Value,
reducer: &StateReducer,
) -> Result<(), String>;
fn append_array(&mut self, key: &str, items: serde_json::Value) -> Result<(), String>;
}
impl StateExt for State {
fn get_str(&self, key: &str) -> Option<&str> {
self.get(key).and_then(|v| v.as_str())
}
fn get_bool(&self, key: &str) -> Option<bool> {
self.get(key).and_then(|v| v.as_bool())
}
fn get_u64(&self, key: &str) -> Option<u64> {
self.get(key).and_then(|v| v.as_u64())
}
fn get_i64(&self, key: &str) -> Option<i64> {
self.get(key).and_then(|v| v.as_i64())
}
fn get_f64(&self, key: &str) -> Option<f64> {
self.get(key).and_then(|v| v.as_f64())
}
fn get_json<T>(&self, key: &str) -> Result<T, StateError>
where
T: serde::de::DeserializeOwned,
{
let value = self
.get(key)
.ok_or_else(|| StateError::MissingKey(key.to_string()))?;
serde_json::from_value(value.clone())
.map_err(|e| StateError::Deserialize(key.to_string(), e.to_string()))
}
fn require<T>(&self, key: &str) -> Result<T, StateError>
where
T: serde::de::DeserializeOwned,
{
self.get_json(key)
}
fn set<T>(&mut self, key: impl Into<String>, value: T)
where
T: serde::Serialize,
{
let json = serde_json::to_value(value).unwrap_or(serde_json::Value::Null);
HashMap::insert(self, key.into(), json);
}
fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
HashMap::remove(self, key)
}
fn contains(&self, key: &str) -> bool {
self.contains_key(key)
}
fn reduce(
&mut self,
key: &str,
value: serde_json::Value,
reducer: &StateReducer,
) -> Result<(), String> {
if let Some(existing) = self.get(key) {
let merged = reducer(existing, &value)?;
self.insert(key.to_string(), merged);
} else {
self.insert(key.to_string(), value);
}
Ok(())
}
fn append_array(&mut self, key: &str, items: serde_json::Value) -> Result<(), String> {
let new_items = items.as_array().ok_or("append_array expects an array")?;
if let Some(existing) = self.get(key) {
let mut arr = existing
.as_array()
.ok_or("append_array: existing value is not an array")?
.clone();
arr.extend(new_items.iter().cloned());
self.insert(key.to_string(), serde_json::Value::Array(arr));
} else {
self.insert(key.to_string(), items);
}
Ok(())
}
}
pub fn array_reducer() -> StateReducer {
Box::new(|existing: &serde_json::Value, new: &serde_json::Value| {
let mut arr = existing
.as_array()
.ok_or("existing value is not an array")?
.clone();
let additions = new.as_array().ok_or("new value is not an array")?;
arr.extend(additions.iter().cloned());
Ok(serde_json::Value::Array(arr))
})
}
#[derive(Debug)]
pub struct GraphResult {
pub state: State,
pub execution_log: Vec<ExecutionEntry>,
pub duration: Duration,
}
#[derive(Debug, Clone)]
pub struct ExecutionEntry {
pub node_name: String,
pub start_time: Instant,
pub end_time: Instant,
pub success: bool,
}
impl ExecutionEntry {
pub fn elapsed(&self) -> Duration {
self.end_time.duration_since(self.start_time)
}
}