oats_framework/
objects.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4use crate::traits::{Trait, TraitId};
5
6/// Object identifier
7pub type ObjectId = Uuid;
8
9/// An object is an identity container that composes traits
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Object {
12    /// Unique identifier for this object
13    pub id: ObjectId,
14    /// Name of the object
15    pub name: String,
16    /// Type of the object
17    pub object_type: String,
18    /// Traits associated with this object
19    pub traits: HashMap<String, Trait>,
20    /// Metadata about the object
21    pub metadata: HashMap<String, String>,
22    /// Creation timestamp
23    pub created_at: chrono::DateTime<chrono::Utc>,
24    /// Last update timestamp
25    pub updated_at: chrono::DateTime<chrono::Utc>,
26}
27
28impl Object {
29    /// Create a new object with the given name and type
30    #[inline]
31    pub fn new(name: impl Into<String>, object_type: impl Into<String>) -> Self {
32        let now = chrono::Utc::now();
33        Self {
34            id: Uuid::new_v4(),
35            name: name.into(),
36            object_type: object_type.into(),
37            traits: HashMap::new(),
38            metadata: HashMap::new(),
39            created_at: now,
40            updated_at: now,
41        }
42    }
43
44    /// Create a new object with initial traits
45    pub fn with_traits(
46        name: impl Into<String>,
47        object_type: impl Into<String>,
48        traits: Vec<Trait>,
49    ) -> Self {
50        let mut obj = Self::new(name, object_type);
51        obj.add_traits_bulk(traits);
52        obj
53    }
54
55    /// Create a new object with pre-allocated capacity
56    pub fn with_capacity(
57        name: impl Into<String>,
58        object_type: impl Into<String>,
59        trait_capacity: usize,
60        metadata_capacity: usize,
61    ) -> Self {
62        let now = chrono::Utc::now();
63        Self {
64            id: Uuid::new_v4(),
65            name: name.into(),
66            object_type: object_type.into(),
67            traits: HashMap::with_capacity(trait_capacity),
68            metadata: HashMap::with_capacity(metadata_capacity),
69            created_at: now,
70            updated_at: now,
71        }
72    }
73
74    /// Get the object name
75    pub fn name(&self) -> &str {
76        &self.name
77    }
78
79    /// Get the object type
80    pub fn object_type(&self) -> &str {
81        &self.object_type
82    }
83
84    /// Get the object ID
85    pub fn id(&self) -> ObjectId {
86        self.id
87    }
88
89    /// Add a trait to this object
90    pub fn add_trait(&mut self, trait_obj: Trait) {
91        self.traits.insert(trait_obj.name().to_string(), trait_obj);
92        self.updated_at = chrono::Utc::now();
93    }
94
95    /// Add multiple traits efficiently
96    pub fn add_traits(&mut self, traits: impl IntoIterator<Item = Trait>) {
97        let mut updated = false;
98        for trait_obj in traits {
99            self.traits.insert(trait_obj.name().to_string(), trait_obj);
100            updated = true;
101        }
102        if updated {
103            self.updated_at = chrono::Utc::now();
104        }
105    }
106
107    /// Add multiple traits without timestamp updates (for bulk operations)
108    pub fn add_traits_bulk(&mut self, traits: impl IntoIterator<Item = Trait>) {
109        for trait_obj in traits {
110            self.traits.insert(trait_obj.name().to_string(), trait_obj);
111        }
112        // Don't update timestamp for bulk operations
113    }
114
115    /// Add a trait without timestamp update (for internal operations)
116    pub fn add_trait_internal(&mut self, trait_obj: Trait) {
117        self.traits.insert(trait_obj.name().to_string(), trait_obj);
118        // Don't update timestamp for internal operations
119    }
120
121    /// Remove a trait from this object
122    #[inline]
123    pub fn remove_trait(&mut self, trait_name: &str) -> Option<Trait> {
124        let result = self.traits.remove(trait_name);
125        if result.is_some() {
126            self.updated_at = chrono::Utc::now();
127        }
128        result
129    }
130
131    /// Get a trait by name
132    #[inline]
133    pub fn get_trait(&self, trait_name: &str) -> Option<&Trait> {
134        self.traits.get(trait_name)
135    }
136
137    /// Get a trait by name (mutable)
138    #[inline]
139    pub fn get_trait_mut(&mut self, trait_name: &str) -> Option<&mut Trait> {
140        self.traits.get_mut(trait_name)
141    }
142
143    /// Get trait data by name (zero-copy access)
144    #[inline]
145    pub fn get_trait_data(&self, trait_name: &str) -> Option<&crate::traits::TraitData> {
146        self.traits.get(trait_name).map(|t| t.data())
147    }
148
149    /// Get trait data by name (mutable, zero-copy access)
150    #[inline]
151    pub fn get_trait_data_mut(&mut self, trait_name: &str) -> Option<&mut crate::traits::TraitData> {
152        self.traits.get_mut(trait_name).map(|t| t.data_mut())
153    }
154
155    /// Get all traits
156    #[inline]
157    pub fn traits(&self) -> &HashMap<String, Trait> {
158        &self.traits
159    }
160
161    /// Check if the object has a specific trait
162    #[inline]
163    pub fn has_trait(&self, trait_name: &str) -> bool {
164        self.traits.contains_key(trait_name)
165    }
166
167    /// Check if the object has multiple traits (efficient batch check)
168    #[inline]
169    pub fn has_traits(&self, trait_names: &[&str]) -> bool {
170        trait_names.iter().all(|name| self.traits.contains_key(*name))
171    }
172
173    /// Check if the object has any traits
174    #[inline]
175    pub fn has_any_traits(&self) -> bool {
176        !self.traits.is_empty()
177    }
178
179    /// Get a metadata value
180    #[inline]
181    pub fn get_metadata(&self, key: &str) -> Option<&String> {
182        self.metadata.get(key)
183    }
184
185    /// Set a metadata value
186    #[inline]
187    pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
188        self.metadata.insert(key.into(), value.into());
189        self.updated_at = chrono::Utc::now();
190    }
191
192    /// Get all metadata
193    #[inline]
194    pub fn metadata(&self) -> &HashMap<String, String> {
195        &self.metadata
196    }
197
198    /// Get the creation timestamp
199    #[inline]
200    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
201        self.created_at
202    }
203
204    /// Get the last update timestamp
205    #[inline]
206    pub fn updated_at(&self) -> chrono::DateTime<chrono::Utc> {
207        self.updated_at
208    }
209
210    /// Get trait names as a vector
211    #[inline]
212    pub fn trait_names(&self) -> Vec<&String> {
213        self.traits.keys().collect()
214    }
215
216    /// Get trait IDs as a vector
217    #[inline]
218    pub fn trait_ids(&self) -> Vec<TraitId> {
219        self.traits.values().map(|t| t.id).collect()
220    }
221
222    /// Get the number of traits
223    #[inline]
224    pub fn trait_count(&self) -> usize {
225        self.traits.len()
226    }
227
228    /// Get the number of metadata entries
229    #[inline]
230    pub fn metadata_count(&self) -> usize {
231        self.metadata.len()
232    }
233
234    /// Reserve capacity for traits
235    #[inline]
236    pub fn reserve_traits(&mut self, additional: usize) {
237        self.traits.reserve(additional);
238    }
239
240    /// Reserve capacity for metadata
241    #[inline]
242    pub fn reserve_metadata(&mut self, additional: usize) {
243        self.metadata.reserve(additional);
244    }
245
246    /// Validate that the object has required traits
247    pub fn validate_required_traits(&self, required_traits: &[&str]) -> Result<(), crate::OatsError> {
248        let missing: Vec<_> = required_traits
249            .iter()
250            .filter(|trait_name| !self.has_trait(trait_name))
251            .map(|s| s.to_string())
252            .collect();
253        
254        if !missing.is_empty() {
255            return Err(crate::OatsError::trait_not_found(
256                format!("Missing required traits: {}", missing.join(", "))
257            ));
258        }
259        Ok(())
260    }
261
262    /// Check if the object is valid (has required fields)
263    pub fn is_valid(&self) -> bool {
264        !self.name.is_empty() && !self.object_type.is_empty()
265    }
266}
267
268impl PartialEq for Object {
269    fn eq(&self, other: &Self) -> bool {
270        self.id == other.id
271    }
272}
273
274impl Eq for Object {}
275
276impl std::hash::Hash for Object {
277    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
278        self.id.hash(state);
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use crate::traits::{Trait, TraitData};
286
287    #[test]
288    fn test_object_creation() {
289        let obj = Object::new("test_object", "test_type");
290        
291        assert_eq!(obj.name(), "test_object");
292        assert_eq!(obj.object_type(), "test_type");
293        assert_eq!(obj.trait_count(), 0);
294        assert!(!obj.has_any_traits());
295    }
296
297    #[test]
298    fn test_object_with_traits() {
299        let trait1 = Trait::new("health", TraitData::Number(100.0));
300        let trait2 = Trait::new("position", TraitData::Object(HashMap::new()));
301        
302        let obj = Object::with_traits("player", "character", vec![trait1, trait2]);
303        
304        assert_eq!(obj.trait_count(), 2);
305        assert!(obj.has_trait("health"));
306        assert!(obj.has_trait("position"));
307        assert!(!obj.has_trait("nonexistent"));
308    }
309
310    #[test]
311    fn test_add_remove_trait() {
312        let mut obj = Object::new("test", "type");
313        let trait_obj = Trait::new("test_trait", TraitData::String("value".to_string()));
314        
315        obj.add_trait_internal(trait_obj);
316        assert_eq!(obj.trait_count(), 1);
317        assert!(obj.has_trait("test_trait"));
318        
319        let removed = obj.remove_trait("test_trait");
320        assert!(removed.is_some());
321        assert_eq!(obj.trait_count(), 0);
322        assert!(!obj.has_trait("test_trait"));
323    }
324
325    #[test]
326    fn test_metadata() {
327        let mut obj = Object::new("test", "type");
328        obj.set_metadata("key", "value");
329        
330        assert_eq!(obj.get_metadata("key"), Some(&"value".to_string()));
331        assert_eq!(obj.get_metadata("nonexistent"), None);
332    }
333
334    #[test]
335    fn test_trait_names_and_ids() {
336        let trait1 = Trait::new("health", TraitData::Number(100.0));
337        let trait2 = Trait::new("position", TraitData::Object(HashMap::new()));
338        
339        let obj = Object::with_traits("player", "character", vec![trait1, trait2]);
340        
341        let names = obj.trait_names();
342        assert_eq!(names.len(), 2);
343        assert!(names.contains(&&"health".to_string()));
344        assert!(names.contains(&&"position".to_string()));
345        
346        let ids = obj.trait_ids();
347        assert_eq!(ids.len(), 2);
348    }
349}