1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4use crate::traits::{Trait, TraitId};
5
6pub type ObjectId = Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Object {
12 pub id: ObjectId,
14 pub name: String,
16 pub object_type: String,
18 pub traits: HashMap<String, Trait>,
20 pub metadata: HashMap<String, String>,
22 pub created_at: chrono::DateTime<chrono::Utc>,
24 pub updated_at: chrono::DateTime<chrono::Utc>,
26}
27
28impl Object {
29 #[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 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 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 pub fn name(&self) -> &str {
76 &self.name
77 }
78
79 pub fn object_type(&self) -> &str {
81 &self.object_type
82 }
83
84 pub fn id(&self) -> ObjectId {
86 self.id
87 }
88
89 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 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 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 }
114
115 pub fn add_trait_internal(&mut self, trait_obj: Trait) {
117 self.traits.insert(trait_obj.name().to_string(), trait_obj);
118 }
120
121 #[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 #[inline]
133 pub fn get_trait(&self, trait_name: &str) -> Option<&Trait> {
134 self.traits.get(trait_name)
135 }
136
137 #[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 #[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 #[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 #[inline]
157 pub fn traits(&self) -> &HashMap<String, Trait> {
158 &self.traits
159 }
160
161 #[inline]
163 pub fn has_trait(&self, trait_name: &str) -> bool {
164 self.traits.contains_key(trait_name)
165 }
166
167 #[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 #[inline]
175 pub fn has_any_traits(&self) -> bool {
176 !self.traits.is_empty()
177 }
178
179 #[inline]
181 pub fn get_metadata(&self, key: &str) -> Option<&String> {
182 self.metadata.get(key)
183 }
184
185 #[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 #[inline]
194 pub fn metadata(&self) -> &HashMap<String, String> {
195 &self.metadata
196 }
197
198 #[inline]
200 pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
201 self.created_at
202 }
203
204 #[inline]
206 pub fn updated_at(&self) -> chrono::DateTime<chrono::Utc> {
207 self.updated_at
208 }
209
210 #[inline]
212 pub fn trait_names(&self) -> Vec<&String> {
213 self.traits.keys().collect()
214 }
215
216 #[inline]
218 pub fn trait_ids(&self) -> Vec<TraitId> {
219 self.traits.values().map(|t| t.id).collect()
220 }
221
222 #[inline]
224 pub fn trait_count(&self) -> usize {
225 self.traits.len()
226 }
227
228 #[inline]
230 pub fn metadata_count(&self) -> usize {
231 self.metadata.len()
232 }
233
234 #[inline]
236 pub fn reserve_traits(&mut self, additional: usize) {
237 self.traits.reserve(additional);
238 }
239
240 #[inline]
242 pub fn reserve_metadata(&mut self, additional: usize) {
243 self.metadata.reserve(additional);
244 }
245
246 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 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}