use chrono::{DateTime, Utc};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StateTransition {
pub key: String,
pub old_value: Option<Value>,
pub new_value: Option<Value>,
pub action_id: String,
pub timestamp: DateTime<Utc>,
}
pub struct StateStore {
state: Mutex<HashMap<String, Value>>,
transitions: Mutex<Vec<StateTransition>>,
}
impl StateStore {
pub fn new() -> Self {
Self {
state: Mutex::new(HashMap::new()),
transitions: Mutex::new(Vec::new()),
}
}
pub fn get(&self, key: &str) -> Option<Value> {
self.state.lock().get(key).cloned()
}
pub fn get_or(&self, key: &str, default: Value) -> Value {
self.state.lock().get(key).cloned().unwrap_or(default)
}
pub fn exists(&self, key: &str) -> bool {
self.state.lock().contains_key(key)
}
pub fn set(&self, key: &str, value: Value, action_id: &str) -> StateTransition {
let mut state = self.state.lock();
let old = state.get(key).cloned();
state.insert(key.to_string(), value.clone());
let t = StateTransition {
key: key.to_string(),
old_value: old,
new_value: Some(value),
action_id: action_id.to_string(),
timestamp: Utc::now(),
};
self.transitions.lock().push(t.clone());
t
}
pub fn delete(&self, key: &str, action_id: &str) -> Option<StateTransition> {
let mut state = self.state.lock();
let old = state.remove(key)?;
let t = StateTransition {
key: key.to_string(),
old_value: Some(old),
new_value: None,
action_id: action_id.to_string(),
timestamp: Utc::now(),
};
self.transitions.lock().push(t.clone());
Some(t)
}
pub fn snapshot(&self) -> HashMap<String, Value> {
self.state.lock().clone()
}
pub fn restore(&self, snapshot: HashMap<String, Value>, transition_count: usize) {
*self.state.lock() = snapshot;
self.transitions.lock().truncate(transition_count);
}
pub fn transition_count(&self) -> usize {
self.transitions.lock().len()
}
pub fn transitions(&self) -> Vec<StateTransition> {
self.transitions.lock().clone()
}
pub fn transitions_since(&self, index: usize) -> Vec<StateTransition> {
let transitions = self.transitions.lock();
let start = index.min(transitions.len());
transitions[start..].to_vec()
}
pub fn keys(&self) -> Vec<String> {
self.state.lock().keys().cloned().collect()
}
pub fn replace_all(&self, snapshot: HashMap<String, Value>) {
*self.state.lock() = snapshot;
self.transitions.lock().clear();
}
}
impl Default for StateStore {
fn default() -> Self {
Self::new()
}
}
impl car_ir::precondition::StateView for StateStore {
fn get_value(&self, key: &str) -> Option<Value> {
self.get(key)
}
fn key_exists(&self, key: &str) -> bool {
self.exists(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_and_get() {
let store = StateStore::new();
store.set("x", Value::from(42), "test");
assert_eq!(store.get("x"), Some(Value::from(42)));
}
#[test]
fn exists() {
let store = StateStore::new();
assert!(!store.exists("x"));
store.set("x", Value::from(1), "test");
assert!(store.exists("x"));
}
#[test]
fn delete() {
let store = StateStore::new();
store.set("x", Value::from(1), "test");
let t = store.delete("x", "test");
assert!(t.is_some());
assert!(!store.exists("x"));
}
#[test]
fn delete_nonexistent() {
let store = StateStore::new();
assert!(store.delete("x", "test").is_none());
}
#[test]
fn snapshot_and_restore() {
let store = StateStore::new();
store.set("x", Value::from(1), "a");
let snap = store.snapshot();
let tc = store.transition_count();
store.set("y", Value::from(2), "b");
assert!(store.exists("y"));
store.restore(snap, tc);
assert!(store.exists("x"));
assert!(!store.exists("y"));
assert_eq!(store.transition_count(), 1);
}
#[test]
fn transitions_logged() {
let store = StateStore::new();
store.set("a", Value::from(1), "act1");
store.set("b", Value::from(2), "act2");
let transitions = store.transitions();
assert_eq!(transitions.len(), 2);
assert_eq!(transitions[0].key, "a");
assert_eq!(transitions[1].key, "b");
}
#[test]
fn transitions_since() {
let store = StateStore::new();
store.set("a", Value::from(1), "act1");
let idx = store.transition_count();
store.set("b", Value::from(2), "act2");
let since = store.transitions_since(idx);
assert_eq!(since.len(), 1);
assert_eq!(since[0].key, "b");
}
#[test]
fn transition_records_old_value() {
let store = StateStore::new();
store.set("x", Value::from(1), "first");
store.set("x", Value::from(2), "second");
let transitions = store.transitions();
assert_eq!(transitions[1].old_value, Some(Value::from(1)));
assert_eq!(transitions[1].new_value, Some(Value::from(2)));
}
#[test]
fn keys() {
let store = StateStore::new();
store.set("a", Value::from(1), "t");
store.set("b", Value::from(2), "t");
let mut keys = store.keys();
keys.sort();
assert_eq!(keys, vec!["a", "b"]);
}
#[test]
fn transitions_since_after_restore_does_not_panic() {
let store = StateStore::new();
store.set("a", serde_json::json!(1), "test");
store.set("b", serde_json::json!(2), "test");
let count_before = store.transition_count();
store.restore(HashMap::new(), 0);
let result = store.transitions_since(count_before);
assert!(result.is_empty());
}
#[test]
fn transitions_since_normal_usage() {
let store = StateStore::new();
store.set("a", serde_json::json!(1), "test");
let mark = store.transition_count();
store.set("b", serde_json::json!(2), "test");
let since = store.transitions_since(mark);
assert_eq!(since.len(), 1);
assert_eq!(since[0].key, "b");
}
#[test]
fn replace_all_swaps_state_without_transitions() {
let store = StateStore::new();
store.set("old_key", serde_json::json!("old"), "setup");
let mut new_state = HashMap::new();
new_state.insert("new_key".to_string(), serde_json::json!("new"));
store.replace_all(new_state);
assert_eq!(store.get("new_key"), Some(serde_json::json!("new")));
assert_eq!(store.get("old_key"), None);
assert_eq!(store.transition_count(), 0);
}
}