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}