oats_framework/
traits.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5/// Trait identifier
6pub type TraitId = Uuid;
7
8/// A trait represents immutable domain state
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Trait {
11    /// Unique identifier for this trait
12    pub id: TraitId,
13    /// Name of the trait
14    pub name: String,
15    /// Version of the trait
16    pub version: u32,
17    /// The actual trait data
18    pub data: TraitData,
19    /// Metadata about the trait
20    pub metadata: HashMap<String, String>,
21}
22
23/// The actual data contained in a trait
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum TraitData {
26    /// Simple string value
27    String(String),
28    /// Numeric value
29    Number(f64),
30    /// Boolean value
31    Boolean(bool),
32    /// Complex structured data
33    Object(HashMap<String, serde_json::Value>),
34    /// Array of values
35    Array(Vec<serde_json::Value>),
36    /// Binary data
37    Binary(Vec<u8>),
38}
39
40impl Trait {
41    /// Create a new trait with the given name and data
42    #[inline]
43    pub fn new(name: impl Into<String>, data: TraitData) -> Self {
44        Self {
45            id: Uuid::new_v4(),
46            name: name.into(),
47            version: 1,
48            data,
49            metadata: HashMap::new(),
50        }
51    }
52
53    /// Create a new trait with metadata
54    pub fn with_metadata(
55        name: impl Into<String>,
56        data: TraitData,
57        metadata: HashMap<String, String>,
58    ) -> Self {
59        Self {
60            id: Uuid::new_v4(),
61            name: name.into(),
62            version: 1,
63            data,
64            metadata,
65        }
66    }
67
68    /// Create a new trait with pre-allocated capacity
69    pub fn with_capacity(
70        name: impl Into<String>,
71        data: TraitData,
72        metadata_capacity: usize,
73    ) -> Self {
74        Self {
75            id: Uuid::new_v4(),
76            name: name.into(),
77            version: 1,
78            data,
79            metadata: HashMap::with_capacity(metadata_capacity),
80        }
81    }
82
83    /// Get the trait name
84    #[inline]
85    pub fn name(&self) -> &str {
86        &self.name
87    }
88
89    /// Get the trait data
90    #[inline]
91    pub fn data(&self) -> &TraitData {
92        &self.data
93    }
94
95    /// Get mutable trait data
96    #[inline]
97    pub fn data_mut(&mut self) -> &mut TraitData {
98        &mut self.data
99    }
100
101    /// Get the trait ID
102    #[inline]
103    pub fn id(&self) -> TraitId {
104        self.id
105    }
106
107    /// Get the trait version
108    #[inline]
109    pub fn version(&self) -> u32 {
110        self.version
111    }
112
113    /// Get a metadata value
114    #[inline]
115    pub fn get_metadata(&self, key: &str) -> Option<&String> {
116        self.metadata.get(key)
117    }
118
119    /// Set a metadata value
120    #[inline]
121    pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
122        self.metadata.insert(key.into(), value.into());
123    }
124
125    /// Create a new version of this trait
126    pub fn new_version(&self, data: TraitData) -> Self {
127        Self {
128            id: Uuid::new_v4(),
129            name: self.name.clone(),
130            version: self.version + 1,
131            data,
132            metadata: self.metadata.clone(),
133        }
134    }
135
136    /// Get metadata count
137    #[inline]
138    pub fn metadata_count(&self) -> usize {
139        self.metadata.len()
140    }
141
142    /// Reserve capacity for metadata
143    #[inline]
144    pub fn reserve_metadata(&mut self, additional: usize) {
145        self.metadata.reserve(additional);
146    }
147
148    /// Clear all metadata
149    #[inline]
150    pub fn clear_metadata(&mut self) {
151        self.metadata.clear();
152    }
153}
154
155impl TraitData {
156    /// Check if this trait data is a string
157    pub fn is_string(&self) -> bool {
158        matches!(self, TraitData::String(_))
159    }
160
161    /// Check if this trait data is a number
162    pub fn is_number(&self) -> bool {
163        matches!(self, TraitData::Number(_))
164    }
165
166    /// Check if this trait data is a boolean
167    pub fn is_boolean(&self) -> bool {
168        matches!(self, TraitData::Boolean(_))
169    }
170
171    /// Check if this trait data is an object
172    pub fn is_object(&self) -> bool {
173        matches!(self, TraitData::Object(_))
174    }
175
176    /// Check if this trait data is an array
177    pub fn is_array(&self) -> bool {
178        matches!(self, TraitData::Array(_))
179    }
180
181    /// Check if this trait data is binary
182    pub fn is_binary(&self) -> bool {
183        matches!(self, TraitData::Binary(_))
184    }
185
186    /// Try to get the string value
187    pub fn as_string(&self) -> Option<&String> {
188        match self {
189            TraitData::String(s) => Some(s),
190            _ => None,
191        }
192    }
193
194    /// Try to get the number value
195    pub fn as_number(&self) -> Option<f64> {
196        match self {
197            TraitData::Number(n) => Some(*n),
198            _ => None,
199        }
200    }
201
202    /// Try to get the boolean value
203    pub fn as_boolean(&self) -> Option<bool> {
204        match self {
205            TraitData::Boolean(b) => Some(*b),
206            _ => None,
207        }
208    }
209
210    /// Try to get the object value
211    pub fn as_object(&self) -> Option<&HashMap<String, serde_json::Value>> {
212        match self {
213            TraitData::Object(o) => Some(o),
214            _ => None,
215        }
216    }
217
218    /// Try to get the array value
219    pub fn as_array(&self) -> Option<&Vec<serde_json::Value>> {
220        match self {
221            TraitData::Array(a) => Some(a),
222            _ => None,
223        }
224    }
225
226    /// Try to get the binary value
227    pub fn as_binary(&self) -> Option<&Vec<u8>> {
228        match self {
229            TraitData::Binary(b) => Some(b),
230            _ => None,
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_trait_creation() {
241        let trait_data = TraitData::String("test".to_string());
242        let trait_obj = Trait::new("test_trait", trait_data);
243        
244        assert_eq!(trait_obj.name(), "test_trait");
245        assert_eq!(trait_obj.version, 1);
246        assert!(trait_obj.data.is_string());
247    }
248
249    #[test]
250    fn test_trait_data_methods() {
251        let string_data = TraitData::String("hello".to_string());
252        let number_data = TraitData::Number(42.0);
253        let bool_data = TraitData::Boolean(true);
254        
255        assert!(string_data.is_string());
256        assert!(number_data.is_number());
257        assert!(bool_data.is_boolean());
258        
259        assert_eq!(string_data.as_string(), Some(&"hello".to_string()));
260        assert_eq!(number_data.as_number(), Some(42.0));
261        assert_eq!(bool_data.as_boolean(), Some(true));
262    }
263
264    #[test]
265    fn test_trait_metadata() {
266        let mut trait_obj = Trait::new("test", TraitData::String("value".to_string()));
267        trait_obj.set_metadata("key", "value");
268        
269        assert_eq!(trait_obj.get_metadata("key"), Some(&"value".to_string()));
270        assert_eq!(trait_obj.get_metadata("nonexistent"), None);
271    }
272}