rustate/
context.rs

1//!
2//! Defines the `Context` struct used to hold arbitrary data (extended state)
3//! associated with a RuState state machine.
4
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::fmt;
10
11/// Represents the extended state (context) for a state machine.
12///
13/// The `Context` stores arbitrary data associated with the machine's current state.
14/// It uses a `HashMap<String, serde_json::Value>` internally, allowing for flexible,
15/// dynamic data structures. Values can be any type that implements `Serialize` and
16/// `Deserialize`.
17///
18/// While flexible, using specific, strongly-typed structs for context is often recommended
19/// for better compile-time safety and clarity, especially for complex machines.
20/// This generic `Context` is useful for simpler cases or when the structure is highly dynamic.
21#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
22pub struct Context {
23    /// Internal storage using a HashMap where keys are strings and values
24    /// are JSON values, allowing arbitrary data structures.
25    #[serde(flatten)] // Flattens the map into the parent JSON object during serialization
26    data: HashMap<String, Value>,
27}
28
29impl Context {
30    /// Creates a new, empty `Context`.
31    pub fn new() -> Self {
32        Self {
33            data: HashMap::new(),
34        }
35    }
36
37    /// Creates a new `Context` from a `serde_json::Value`.
38    ///
39    /// Assumes the input `value` is a JSON Object (`Value::Object`).
40    /// If the input is not an object, an empty `Context` is returned.
41    ///
42    /// # Arguments
43    /// * `value` - A `serde_json::Value` assumed to be an Object.
44    pub fn from_value(value: Value) -> Self {
45        match value {
46            Value::Object(map) => Self {
47                // Convert serde_json::Map to HashMap<String, Value>
48                data: map.into_iter().collect(),
49            },
50            _ => {
51                eprintln!("Warning: Context::from_value expected a JSON object, received {:?}. Returning empty context.", value);
52                Self::new() // Return empty context if not an object
53            }
54        }
55    }
56
57    /// Sets a value in the context, serializing it to a `serde_json::Value`.
58    ///
59    /// # Arguments
60    /// * `key` - The string key to associate with the value.
61    /// * `value` - The value to set. Must implement `serde::Serialize`.
62    ///
63    /// # Returns
64    /// `Ok(())` on success, or a `serde_json::Error` if serialization fails.
65    pub fn set<T: Serialize>(&mut self, key: &str, value: T) -> Result<(), serde_json::Error> {
66        let json_value = serde_json::to_value(value)?;
67        self.data.insert(key.to_string(), json_value);
68        Ok(())
69    }
70
71    /// Gets a value from the context, attempting to deserialize it into type `T`.
72    ///
73    /// # Arguments
74    /// * `key` - The string key of the value to retrieve.
75    ///
76    /// # Returns
77    /// * `Some(Ok(T))` if the key exists and deserialization succeeds.
78    /// * `Some(Err(serde_json::Error))` if the key exists but deserialization fails.
79    /// * `None` if the key does not exist.
80    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<Result<T, serde_json::Error>> {
81        self.data
82            .get(key)
83            .map(|value| serde_json::from_value(value.clone())) // Clone value for deserialization
84    }
85
86    /// Gets a reference to the raw `serde_json::Value` associated with a key.
87    ///
88    /// Returns `None` if the key does not exist.
89    pub fn get_value(&self, key: &str) -> Option<&Value> {
90        self.data.get(key)
91    }
92
93    /// Checks if a key exists in the context.
94    pub fn has(&self, key: &str) -> bool {
95        self.data.contains_key(key)
96    }
97
98    /// Removes a key and its associated value from the context.
99    ///
100    /// Returns the `serde_json::Value` if the key existed, otherwise `None`.
101    pub fn remove(&mut self, key: &str) -> Option<Value> {
102        self.data.remove(key)
103    }
104
105    /// Checks if the context contains no data.
106    pub fn is_empty(&self) -> bool {
107        self.data.is_empty()
108    }
109}
110
111impl fmt::Display for Context {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        // Attempt to pretty-print the JSON representation for better readability.
114        match serde_json::to_string_pretty(&self.data) {
115            Ok(pretty_json) => write!(f, "{}", pretty_json),
116            Err(_) => {
117                // Fallback to Debug formatting if pretty printing fails (should be rare)
118                write!(f, "{:?}", self.data)
119            }
120        }
121    }
122}