oats_framework/
actions.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use crate::{Result, Object, Trait};
5
6/// Action identifier
7pub type ActionId = uuid::Uuid;
8
9/// Context passed to actions containing relevant objects and traits
10#[derive(Debug, Clone)]
11pub struct ActionContext {
12    /// Objects relevant to this action
13    pub objects: HashMap<String, Object>,
14    /// Additional parameters for the action
15    pub parameters: HashMap<String, serde_json::Value>,
16    /// Metadata about the action execution
17    pub metadata: HashMap<String, String>,
18}
19
20impl ActionContext {
21    /// Create a new action context
22    #[inline]
23    pub fn new() -> Self {
24        Self {
25            objects: HashMap::new(),
26            parameters: HashMap::new(),
27            metadata: HashMap::new(),
28        }
29    }
30
31    /// Create a new action context with expected capacity
32    #[inline]
33    pub fn with_capacity(expected_objects: usize, expected_parameters: usize) -> Self {
34        Self {
35            objects: HashMap::with_capacity(expected_objects),
36            parameters: HashMap::with_capacity(expected_parameters),
37            metadata: HashMap::new(),
38        }
39    }
40
41    /// Add an object to the context
42    #[inline]
43    pub fn add_object(&mut self, name: impl Into<String>, object: Object) {
44        self.objects.insert(name.into(), object);
45    }
46
47    /// Get an object from the context
48    #[inline]
49    pub fn get_object(&self, name: &str) -> Option<&Object> {
50        self.objects.get(name)
51    }
52
53    /// Get multiple objects efficiently
54    #[inline]
55    pub fn get_objects(&self, names: &[&str]) -> HashMap<String, &Object> {
56        names.iter()
57            .filter_map(|name| self.objects.get(*name).map(|obj| (name.to_string(), obj)))
58            .collect()
59    }
60
61    /// Add a parameter to the context
62    #[inline]
63    pub fn add_parameter(&mut self, name: impl Into<String>, value: serde_json::Value) {
64        self.parameters.insert(name.into(), value);
65    }
66
67    /// Get a parameter from the context
68    #[inline]
69    pub fn get_parameter(&self, name: &str) -> Option<&serde_json::Value> {
70        self.parameters.get(name)
71    }
72
73    /// Add metadata to the context
74    #[inline]
75    pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
76        self.metadata.insert(key.into(), value.into());
77    }
78
79    /// Get metadata from the context
80    #[inline]
81    pub fn get_metadata(&self, key: &str) -> Option<&String> {
82        self.metadata.get(key)
83    }
84
85    /// Get object count
86    #[inline]
87    pub fn object_count(&self) -> usize {
88        self.objects.len()
89    }
90
91    /// Get parameter count
92    #[inline]
93    pub fn parameter_count(&self) -> usize {
94        self.parameters.len()
95    }
96
97    /// Get metadata count
98    #[inline]
99    pub fn metadata_count(&self) -> usize {
100        self.metadata.len()
101    }
102
103    /// Reserve capacity for objects
104    #[inline]
105    pub fn reserve_objects(&mut self, additional: usize) {
106        self.objects.reserve(additional);
107    }
108
109    /// Reserve capacity for parameters
110    #[inline]
111    pub fn reserve_parameters(&mut self, additional: usize) {
112        self.parameters.reserve(additional);
113    }
114
115    /// Clear all objects
116    #[inline]
117    pub fn clear_objects(&mut self) {
118        self.objects.clear();
119    }
120
121    /// Clear all parameters
122    #[inline]
123    pub fn clear_parameters(&mut self) {
124        self.parameters.clear();
125    }
126
127    /// Clear all metadata
128    #[inline]
129    pub fn clear_metadata(&mut self) {
130        self.metadata.clear();
131    }
132}
133
134impl Default for ActionContext {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140/// Result of an action execution
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct ActionResult {
143    /// Whether the action was successful
144    pub success: bool,
145    /// New traits to be applied
146    pub trait_updates: Vec<Trait>,
147    /// Messages or logs from the action
148    pub messages: Vec<String>,
149    /// Additional data returned by the action
150    pub data: HashMap<String, serde_json::Value>,
151}
152
153impl ActionResult {
154    /// Create a successful action result
155    #[inline]
156    pub fn success() -> Self {
157        Self {
158            success: true,
159            trait_updates: Vec::new(),
160            messages: Vec::new(),
161            data: HashMap::new(),
162        }
163    }
164
165    /// Create a failed action result
166    #[inline]
167    pub fn failure(message: impl Into<String>) -> Self {
168        Self {
169            success: false,
170            trait_updates: Vec::new(),
171            messages: vec![message.into()],
172            data: HashMap::new(),
173        }
174    }
175
176    /// Create a successful action result with pre-allocated capacity
177    pub fn success_with_capacity(trait_capacity: usize, message_capacity: usize, data_capacity: usize) -> Self {
178        Self {
179            success: true,
180            trait_updates: Vec::with_capacity(trait_capacity),
181            messages: Vec::with_capacity(message_capacity),
182            data: HashMap::with_capacity(data_capacity),
183        }
184    }
185
186    /// Add a trait update to the result
187    #[inline]
188    pub fn add_trait_update(&mut self, trait_obj: Trait) {
189        self.trait_updates.push(trait_obj);
190    }
191
192    /// Add multiple trait updates efficiently
193    #[inline]
194    pub fn add_trait_updates(&mut self, trait_updates: impl IntoIterator<Item = Trait>) {
195        self.trait_updates.extend(trait_updates);
196    }
197
198    /// Add a message to the result
199    #[inline]
200    pub fn add_message(&mut self, message: impl Into<String>) {
201        self.messages.push(message.into());
202    }
203
204    /// Add multiple messages efficiently
205    #[inline]
206    pub fn add_messages(&mut self, messages: impl IntoIterator<Item = String>) {
207        self.messages.extend(messages);
208    }
209
210    /// Add data to the result
211    #[inline]
212    pub fn add_data(&mut self, key: impl Into<String>, value: serde_json::Value) {
213        self.data.insert(key.into(), value);
214    }
215
216    /// Reserve capacity for expected updates
217    #[inline]
218    pub fn reserve_capacity(&mut self, trait_updates: usize, messages: usize) {
219        self.trait_updates.reserve(trait_updates);
220        self.messages.reserve(messages);
221    }
222
223    /// Check if the action was successful
224    #[inline]
225    pub fn is_success(&self) -> bool {
226        self.success
227    }
228
229    /// Check if the action failed
230    #[inline]
231    pub fn is_failure(&self) -> bool {
232        !self.success
233    }
234
235    /// Get trait update count
236    #[inline]
237    pub fn trait_update_count(&self) -> usize {
238        self.trait_updates.len()
239    }
240
241    /// Get message count
242    #[inline]
243    pub fn message_count(&self) -> usize {
244        self.messages.len()
245    }
246
247    /// Get data count
248    #[inline]
249    pub fn data_count(&self) -> usize {
250        self.data.len()
251    }
252
253    /// Clear all trait updates
254    #[inline]
255    pub fn clear_trait_updates(&mut self) {
256        self.trait_updates.clear();
257    }
258
259    /// Clear all messages
260    #[inline]
261    pub fn clear_messages(&mut self) {
262        self.messages.clear();
263    }
264
265    /// Clear all data
266    #[inline]
267    pub fn clear_data(&mut self) {
268        self.data.clear();
269    }
270}
271
272/// An action represents stateless logic that reads traits and returns updates
273#[async_trait]
274pub trait Action: Send + Sync {
275    /// Get the name of this action
276    fn name(&self) -> &str;
277
278    /// Get the description of this action
279    fn description(&self) -> &str;
280
281    /// Execute the action with the given context
282    async fn execute(&self, context: ActionContext) -> Result<ActionResult>;
283
284    /// Get the required trait names for this action
285    fn required_traits(&self) -> Vec<String> {
286        Vec::new()
287    }
288
289    /// Get the optional trait names for this action
290    fn optional_traits(&self) -> Vec<String> {
291        Vec::new()
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_action_context() {
301        let mut context = ActionContext::new();
302        let test_object = Object::new("test", "type");
303
304        context.add_object("test_obj", test_object);
305        context.add_parameter("param", serde_json::json!("value"));
306        context.add_metadata("key", "value");
307
308        assert!(context.get_object("test_obj").is_some());
309        assert!(context.get_parameter("param").is_some());
310        assert_eq!(context.get_metadata("key"), Some(&"value".to_string()));
311    }
312
313    #[test]
314    fn test_action_result() {
315        let mut result = ActionResult::success();
316        result.add_message("Test message");
317        result.add_data("key", serde_json::json!("value"));
318
319        assert!(result.is_success());
320        assert_eq!(result.messages.len(), 1);
321        assert_eq!(result.data.len(), 1);
322    }
323}