use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::traits::{Trait, TraitId};
pub type ObjectId = Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Object {
pub id: ObjectId,
pub name: String,
pub object_type: String,
pub traits: HashMap<String, Trait>,
pub metadata: HashMap<String, String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
impl Object {
#[inline]
pub fn new(name: impl Into<String>, object_type: impl Into<String>) -> Self {
let now = chrono::Utc::now();
Self {
id: Uuid::new_v4(),
name: name.into(),
object_type: object_type.into(),
traits: HashMap::new(),
metadata: HashMap::new(),
created_at: now,
updated_at: now,
}
}
pub fn with_traits(
name: impl Into<String>,
object_type: impl Into<String>,
traits: Vec<Trait>,
) -> Self {
let mut obj = Self::new(name, object_type);
obj.add_traits_bulk(traits);
obj
}
pub fn with_capacity(
name: impl Into<String>,
object_type: impl Into<String>,
trait_capacity: usize,
metadata_capacity: usize,
) -> Self {
let now = chrono::Utc::now();
Self {
id: Uuid::new_v4(),
name: name.into(),
object_type: object_type.into(),
traits: HashMap::with_capacity(trait_capacity),
metadata: HashMap::with_capacity(metadata_capacity),
created_at: now,
updated_at: now,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn object_type(&self) -> &str {
&self.object_type
}
pub fn id(&self) -> ObjectId {
self.id
}
pub fn add_trait(&mut self, trait_obj: Trait) {
self.traits.insert(trait_obj.name().to_string(), trait_obj);
self.updated_at = chrono::Utc::now();
}
pub fn add_traits(&mut self, traits: impl IntoIterator<Item = Trait>) {
let mut updated = false;
for trait_obj in traits {
self.traits.insert(trait_obj.name().to_string(), trait_obj);
updated = true;
}
if updated {
self.updated_at = chrono::Utc::now();
}
}
pub fn add_traits_bulk(&mut self, traits: impl IntoIterator<Item = Trait>) {
for trait_obj in traits {
self.traits.insert(trait_obj.name().to_string(), trait_obj);
}
}
pub fn add_trait_internal(&mut self, trait_obj: Trait) {
self.traits.insert(trait_obj.name().to_string(), trait_obj);
}
#[inline]
pub fn remove_trait(&mut self, trait_name: &str) -> Option<Trait> {
let result = self.traits.remove(trait_name);
if result.is_some() {
self.updated_at = chrono::Utc::now();
}
result
}
#[inline]
pub fn get_trait(&self, trait_name: &str) -> Option<&Trait> {
self.traits.get(trait_name)
}
#[inline]
pub fn get_trait_mut(&mut self, trait_name: &str) -> Option<&mut Trait> {
self.traits.get_mut(trait_name)
}
#[inline]
pub fn get_trait_data(&self, trait_name: &str) -> Option<&crate::traits::TraitData> {
self.traits.get(trait_name).map(|t| t.data())
}
#[inline]
pub fn get_trait_data_mut(&mut self, trait_name: &str) -> Option<&mut crate::traits::TraitData> {
self.traits.get_mut(trait_name).map(|t| t.data_mut())
}
#[inline]
pub fn traits(&self) -> &HashMap<String, Trait> {
&self.traits
}
#[inline]
pub fn has_trait(&self, trait_name: &str) -> bool {
self.traits.contains_key(trait_name)
}
#[inline]
pub fn has_traits(&self, trait_names: &[&str]) -> bool {
trait_names.iter().all(|name| self.traits.contains_key(*name))
}
#[inline]
pub fn has_any_traits(&self) -> bool {
!self.traits.is_empty()
}
#[inline]
pub fn get_metadata(&self, key: &str) -> Option<&String> {
self.metadata.get(key)
}
#[inline]
pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
self.updated_at = chrono::Utc::now();
}
#[inline]
pub fn metadata(&self) -> &HashMap<String, String> {
&self.metadata
}
#[inline]
pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
self.created_at
}
#[inline]
pub fn updated_at(&self) -> chrono::DateTime<chrono::Utc> {
self.updated_at
}
#[inline]
pub fn trait_names(&self) -> Vec<&String> {
self.traits.keys().collect()
}
#[inline]
pub fn trait_ids(&self) -> Vec<TraitId> {
self.traits.values().map(|t| t.id).collect()
}
#[inline]
pub fn trait_count(&self) -> usize {
self.traits.len()
}
#[inline]
pub fn metadata_count(&self) -> usize {
self.metadata.len()
}
#[inline]
pub fn reserve_traits(&mut self, additional: usize) {
self.traits.reserve(additional);
}
#[inline]
pub fn reserve_metadata(&mut self, additional: usize) {
self.metadata.reserve(additional);
}
pub fn validate_required_traits(&self, required_traits: &[&str]) -> Result<(), crate::OatsError> {
let missing: Vec<_> = required_traits
.iter()
.filter(|trait_name| !self.has_trait(trait_name))
.map(|s| s.to_string())
.collect();
if !missing.is_empty() {
return Err(crate::OatsError::trait_not_found(
format!("Missing required traits: {}", missing.join(", "))
));
}
Ok(())
}
pub fn is_valid(&self) -> bool {
!self.name.is_empty() && !self.object_type.is_empty()
}
}
impl PartialEq for Object {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Object {}
impl std::hash::Hash for Object {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{Trait, TraitData};
#[test]
fn test_object_creation() {
let obj = Object::new("test_object", "test_type");
assert_eq!(obj.name(), "test_object");
assert_eq!(obj.object_type(), "test_type");
assert_eq!(obj.trait_count(), 0);
assert!(!obj.has_any_traits());
}
#[test]
fn test_object_with_traits() {
let trait1 = Trait::new("health", TraitData::Number(100.0));
let trait2 = Trait::new("position", TraitData::Object(HashMap::new()));
let obj = Object::with_traits("player", "character", vec![trait1, trait2]);
assert_eq!(obj.trait_count(), 2);
assert!(obj.has_trait("health"));
assert!(obj.has_trait("position"));
assert!(!obj.has_trait("nonexistent"));
}
#[test]
fn test_add_remove_trait() {
let mut obj = Object::new("test", "type");
let trait_obj = Trait::new("test_trait", TraitData::String("value".to_string()));
obj.add_trait_internal(trait_obj);
assert_eq!(obj.trait_count(), 1);
assert!(obj.has_trait("test_trait"));
let removed = obj.remove_trait("test_trait");
assert!(removed.is_some());
assert_eq!(obj.trait_count(), 0);
assert!(!obj.has_trait("test_trait"));
}
#[test]
fn test_metadata() {
let mut obj = Object::new("test", "type");
obj.set_metadata("key", "value");
assert_eq!(obj.get_metadata("key"), Some(&"value".to_string()));
assert_eq!(obj.get_metadata("nonexistent"), None);
}
#[test]
fn test_trait_names_and_ids() {
let trait1 = Trait::new("health", TraitData::Number(100.0));
let trait2 = Trait::new("position", TraitData::Object(HashMap::new()));
let obj = Object::with_traits("player", "character", vec![trait1, trait2]);
let names = obj.trait_names();
assert_eq!(names.len(), 2);
assert!(names.contains(&&"health".to_string()));
assert!(names.contains(&&"position".to_string()));
let ids = obj.trait_ids();
assert_eq!(ids.len(), 2);
}
}