1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5pub type TraitId = Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Trait {
11 pub id: TraitId,
13 pub name: String,
15 pub version: u32,
17 pub data: TraitData,
19 pub metadata: HashMap<String, String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum TraitData {
26 String(String),
28 Number(f64),
30 Boolean(bool),
32 Object(HashMap<String, serde_json::Value>),
34 Array(Vec<serde_json::Value>),
36 Binary(Vec<u8>),
38}
39
40impl Trait {
41 #[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 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 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 #[inline]
85 pub fn name(&self) -> &str {
86 &self.name
87 }
88
89 #[inline]
91 pub fn data(&self) -> &TraitData {
92 &self.data
93 }
94
95 #[inline]
97 pub fn data_mut(&mut self) -> &mut TraitData {
98 &mut self.data
99 }
100
101 #[inline]
103 pub fn id(&self) -> TraitId {
104 self.id
105 }
106
107 #[inline]
109 pub fn version(&self) -> u32 {
110 self.version
111 }
112
113 #[inline]
115 pub fn get_metadata(&self, key: &str) -> Option<&String> {
116 self.metadata.get(key)
117 }
118
119 #[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 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 #[inline]
138 pub fn metadata_count(&self) -> usize {
139 self.metadata.len()
140 }
141
142 #[inline]
144 pub fn reserve_metadata(&mut self, additional: usize) {
145 self.metadata.reserve(additional);
146 }
147
148 #[inline]
150 pub fn clear_metadata(&mut self) {
151 self.metadata.clear();
152 }
153}
154
155impl TraitData {
156 pub fn is_string(&self) -> bool {
158 matches!(self, TraitData::String(_))
159 }
160
161 pub fn is_number(&self) -> bool {
163 matches!(self, TraitData::Number(_))
164 }
165
166 pub fn is_boolean(&self) -> bool {
168 matches!(self, TraitData::Boolean(_))
169 }
170
171 pub fn is_object(&self) -> bool {
173 matches!(self, TraitData::Object(_))
174 }
175
176 pub fn is_array(&self) -> bool {
178 matches!(self, TraitData::Array(_))
179 }
180
181 pub fn is_binary(&self) -> bool {
183 matches!(self, TraitData::Binary(_))
184 }
185
186 pub fn as_string(&self) -> Option<&String> {
188 match self {
189 TraitData::String(s) => Some(s),
190 _ => None,
191 }
192 }
193
194 pub fn as_number(&self) -> Option<f64> {
196 match self {
197 TraitData::Number(n) => Some(*n),
198 _ => None,
199 }
200 }
201
202 pub fn as_boolean(&self) -> Option<bool> {
204 match self {
205 TraitData::Boolean(b) => Some(*b),
206 _ => None,
207 }
208 }
209
210 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 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 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}