Skip to main content

pflow_tokenmodel/
snapshot.rs

1//! Snapshot: current state of all states in a schema.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::schema::Schema;
9
10/// The current state of all states in a schema.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Snapshot {
13    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
14    pub tokens: HashMap<String, i64>,
15
16    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
17    pub data: HashMap<String, Value>,
18}
19
20impl Snapshot {
21    pub fn new() -> Self {
22        Self {
23            tokens: HashMap::new(),
24            data: HashMap::new(),
25        }
26    }
27
28    /// Creates a snapshot initialized from schema defaults.
29    pub fn from_schema(s: &Schema) -> Self {
30        let mut snap = Self::new();
31        for st in &s.states {
32            if st.is_token() {
33                snap.tokens.insert(st.id.clone(), st.initial_tokens());
34            } else if let Some(initial) = &st.initial {
35                snap.data.insert(st.id.clone(), initial.clone());
36            } else {
37                snap.data
38                    .insert(st.id.clone(), Value::Object(Default::default()));
39            }
40        }
41        snap
42    }
43
44    pub fn get_tokens(&self, state_id: &str) -> i64 {
45        self.tokens.get(state_id).copied().unwrap_or(0)
46    }
47
48    pub fn set_tokens(&mut self, state_id: &str, count: i64) {
49        self.tokens.insert(state_id.to_string(), count);
50    }
51
52    pub fn add_tokens(&mut self, state_id: &str, delta: i64) {
53        let entry = self.tokens.entry(state_id.to_string()).or_insert(0);
54        *entry += delta;
55    }
56
57    pub fn get_data(&self, state_id: &str) -> Option<&Value> {
58        self.data.get(state_id)
59    }
60
61    pub fn set_data(&mut self, state_id: &str, value: Value) {
62        self.data.insert(state_id.to_string(), value);
63    }
64
65    /// Returns data as a mutable JSON object, or None.
66    pub fn get_data_map(&self, state_id: &str) -> Option<&serde_json::Map<String, Value>> {
67        self.data.get(state_id).and_then(|v| v.as_object())
68    }
69
70    /// Gets a value from a data state map.
71    pub fn get_data_map_value(&self, state_id: &str, key: &str) -> Option<&Value> {
72        self.get_data_map(state_id).and_then(|m| m.get(key))
73    }
74
75    /// Sets a value in a data state map.
76    pub fn set_data_map_value(&mut self, state_id: &str, key: &str, value: Value) {
77        let entry = self
78            .data
79            .entry(state_id.to_string())
80            .or_insert_with(|| Value::Object(Default::default()));
81        if let Value::Object(map) = entry {
82            map.insert(key.to_string(), value);
83        }
84    }
85}
86
87impl Default for Snapshot {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93/// Variable bindings for parameterized action execution.
94pub type Bindings = HashMap<String, Value>;
95
96/// Extension trait for Bindings convenience methods.
97pub trait BindingsExt {
98    fn get_string(&self, key: &str) -> String;
99    fn get_i64(&self, key: &str) -> i64;
100}
101
102impl BindingsExt for Bindings {
103    fn get_string(&self, key: &str) -> String {
104        self.get(key)
105            .and_then(|v| v.as_str())
106            .unwrap_or("")
107            .to_string()
108    }
109
110    fn get_i64(&self, key: &str) -> i64 {
111        self.get(key)
112            .and_then(|v| {
113                v.as_i64()
114                    .or_else(|| v.as_f64().map(|f| f as i64))
115                    .or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
116            })
117            .unwrap_or(0)
118    }
119}