1use crate::{attribute::Attribute, markdown::position::Position};
25use convert_case::{Case, Casing};
26use serde::{Deserialize, Serialize};
27use serde_with::skip_serializing_none;
28use std::collections::BTreeMap;
29use std::hash::{Hash, Hasher};
30
31#[cfg(feature = "python")]
32use pyo3::pyclass;
33
34#[cfg(feature = "wasm")]
35use tsify_next::Tsify;
36
37#[skip_serializing_none]
38#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
39#[cfg_attr(feature = "python", pyclass(get_all, from_py_object))]
40#[cfg_attr(feature = "wasm", derive(Tsify))]
41#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
42pub struct Object {
44 pub name: String,
46 pub attributes: Vec<Attribute>,
48 pub docstring: String,
50 pub term: Option<String>,
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
54 pub mixins: Vec<String>,
55 pub position: Option<Position>,
57}
58
59impl Object {
60 pub fn new(name: String, term: Option<String>) -> Self {
71 let name = name.replace(" ", "_").to_case(Case::Pascal);
72 Object {
73 name,
74 attributes: Vec::new(),
75 docstring: String::new(),
76 term,
77 mixins: Vec::new(),
78 position: None,
79 }
80 }
81
82 pub fn add_attribute(&mut self, attribute: Attribute) {
88 self.attributes.push(attribute);
89 }
90
91 pub fn set_docstring(&mut self, docstring: String) {
97 self.docstring = docstring;
98 }
99
100 pub fn set_position(&mut self, position: Position) {
106 self.position = Some(position);
107 }
108
109 pub fn get_last_attribute(&mut self) -> Option<&mut Attribute> {
119 self.attributes.last_mut()
120 }
121
122 pub fn create_new_attribute(&mut self, name: String, required: bool) {
129 let attribute = Attribute::new(name, required);
130 self.attributes.push(attribute);
131 }
132
133 pub fn has_attributes(&self) -> bool {
139 !self.attributes.is_empty()
140 }
141
142 pub fn set_name(&mut self, name: String) {
148 self.name = name;
149 }
150
151 pub fn has_any_terms(&self) -> bool {
157 self.attributes.iter().any(|attr| attr.has_term())
158 }
159
160 pub fn sort_attrs_by_required(&mut self) {
162 let mut top_elements: Vec<Attribute> = vec![];
163 let mut bottom_elements: Vec<Attribute> = vec![];
164
165 for attr in self.attributes.iter() {
166 if attr.required && attr.default.is_none() && !attr.is_array {
167 top_elements.push(attr.clone());
168 } else {
169 bottom_elements.push(attr.clone());
170 }
171 }
172
173 self.attributes = top_elements;
174 self.attributes.append(&mut bottom_elements);
175 }
176
177 pub(crate) fn same_hash(&self, other: &Object) -> bool {
187 use std::collections::hash_map::DefaultHasher;
188 use std::hash::{Hash, Hasher};
189
190 let mut hasher1 = DefaultHasher::new();
191 let mut hasher2 = DefaultHasher::new();
192 self.hash(&mut hasher1);
193 other.hash(&mut hasher2);
194 hasher1.finish() == hasher2.finish()
195 }
196}
197
198impl Hash for Object {
199 fn hash<H: Hasher>(&self, state: &mut H) {
200 let mut attr_names: Vec<&String> = self.attributes.iter().map(|attr| &attr.name).collect();
201 attr_names.sort();
202 attr_names.hash(state);
203 }
204}
205
206#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
207#[cfg_attr(feature = "python", pyclass(get_all, from_py_object))]
208#[cfg_attr(feature = "wasm", derive(Tsify))]
209#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
210pub struct Enumeration {
212 pub name: String,
214 pub mappings: BTreeMap<String, String>,
216 pub docstring: String,
218 pub position: Option<Position>,
220}
221
222impl Enumeration {
223 pub fn has_values(&self) -> bool {
229 !self.mappings.is_empty()
230 }
231
232 pub fn set_position(&mut self, position: Position) {
238 self.position = Some(position);
239 }
240
241 pub(crate) fn same_hash(&self, other: &Enumeration) -> bool {
251 use std::collections::hash_map::DefaultHasher;
252 use std::hash::{Hash, Hasher};
253
254 let mut hasher1 = DefaultHasher::new();
255 let mut hasher2 = DefaultHasher::new();
256 self.hash(&mut hasher1);
257 other.hash(&mut hasher2);
258 hasher1.finish() == hasher2.finish()
259 }
260}
261
262impl Hash for Enumeration {
263 fn hash<H: Hasher>(&self, state: &mut H) {
264 let mut keys: Vec<&String> = self.mappings.keys().collect();
265 keys.sort();
266 keys.hash(state);
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use pretty_assertions::assert_eq;
274 use std::collections::hash_map::DefaultHasher;
275 use std::hash::{Hash, Hasher};
276
277 #[test]
278 fn test_create_new_object() {
279 let object = Object::new("Person".to_string(), None);
280 assert_eq!(object.name, "Person");
281 assert_eq!(object.attributes.len(), 0);
282 assert_eq!(object.docstring, "");
283 assert_eq!(object.term, None);
284 }
285
286 #[test]
287 fn test_add_attribute() {
288 let mut object = Object::new("Person".to_string(), None);
289 let attribute = Attribute::new("name".to_string(), false);
290 object.add_attribute(attribute);
291 assert_eq!(object.attributes.len(), 1);
292 assert_eq!(object.attributes[0].name, "name");
293 }
294
295 #[test]
296 fn test_set_docstring() {
297 let mut object = Object::new("Person".to_string(), None);
298 object.set_docstring("This is a person object".to_string());
299 assert_eq!(object.docstring, "This is a person object");
300 }
301
302 #[test]
303 fn test_get_last_attribute() {
304 let mut object = Object::new("Person".to_string(), None);
305 let attribute = Attribute::new("name".to_string(), false);
306 object.add_attribute(attribute);
307 let last_attribute = object.get_last_attribute();
308 assert_eq!(last_attribute.unwrap().name, "name");
309 }
310
311 #[test]
312 fn test_create_new_attribute() {
313 let mut object = Object::new("Person".to_string(), None);
314 object.create_new_attribute("name".to_string(), false);
315 assert_eq!(object.attributes.len(), 1);
316 assert_eq!(object.attributes[0].name, "name");
317 }
318
319 fn hash<T: Hash>(t: &T) -> u64 {
320 let mut s = DefaultHasher::new();
321 t.hash(&mut s);
322 s.finish()
323 }
324
325 #[test]
326 fn test_object_hash_identical() {
327 let mut object1 = Object::new("Person".to_string(), None);
328 object1.create_new_attribute("name".to_string(), false);
329 object1.create_new_attribute("age".to_string(), false);
330
331 let mut object2 = Object::new("Person".to_string(), None);
332 object2.create_new_attribute("age".to_string(), false);
334 object2.create_new_attribute("name".to_string(), false);
335
336 assert_eq!(hash(&object1), hash(&object2));
337 }
338
339 #[test]
340 fn test_object_hash_different() {
341 let mut object1 = Object::new("Person".to_string(), None);
342 object1.create_new_attribute("name".to_string(), false);
343 object1.create_new_attribute("age".to_string(), false);
344
345 let mut object2 = Object::new("Person".to_string(), None);
346 object2.create_new_attribute("name".to_string(), false);
347 object2.create_new_attribute("email".to_string(), false);
348
349 assert_ne!(hash(&object1), hash(&object2));
350 }
351
352 #[test]
353 fn test_enumeration_hash_identical() {
354 let mut enum1 = Enumeration::default();
355 enum1
356 .mappings
357 .insert("active".to_string(), "Active".to_string());
358 enum1
359 .mappings
360 .insert("inactive".to_string(), "Inactive".to_string());
361
362 let mut enum2 = Enumeration::default();
363 enum2
365 .mappings
366 .insert("inactive".to_string(), "Inactive".to_string());
367 enum2
368 .mappings
369 .insert("active".to_string(), "Active".to_string());
370
371 assert_eq!(hash(&enum1), hash(&enum2));
372 }
373
374 #[test]
375 fn test_enumeration_hash_different() {
376 let mut enum1 = Enumeration::default();
377 enum1
378 .mappings
379 .insert("active".to_string(), "Active".to_string());
380 enum1
381 .mappings
382 .insert("inactive".to_string(), "Inactive".to_string());
383
384 let mut enum2 = Enumeration::default();
385 enum2
386 .mappings
387 .insert("pending".to_string(), "Pending".to_string());
388 enum2
389 .mappings
390 .insert("active".to_string(), "Active".to_string());
391
392 assert_ne!(hash(&enum1), hash(&enum2));
393 }
394
395 #[test]
396 fn test_object_hash_reference_identical() {
397 let mut object1 = Object::new("Person".to_string(), None);
398 object1.create_new_attribute("name".to_string(), false);
399 object1.create_new_attribute("age".to_string(), false);
400
401 let mut object2 = Object::new("Person".to_string(), None);
402 object2.create_new_attribute("age".to_string(), false);
403 object2.create_new_attribute("name".to_string(), false);
404
405 let ref1: &Object = &object1;
406 let ref2: &Object = &object2;
407
408 assert_eq!(hash(ref1), hash(ref2));
409 assert_eq!(hash(&object1), hash(ref1));
410 }
411
412 #[test]
413 fn test_object_hash_reference_different() {
414 let mut object1 = Object::new("Person".to_string(), None);
415 object1.create_new_attribute("name".to_string(), false);
416 object1.create_new_attribute("age".to_string(), false);
417
418 let mut object2 = Object::new("Person".to_string(), None);
419 object2.create_new_attribute("name".to_string(), false);
420 object2.create_new_attribute("email".to_string(), false);
421
422 let ref1: &Object = &object1;
423 let ref2: &Object = &object2;
424
425 assert_ne!(hash(ref1), hash(ref2));
426 assert_eq!(hash(&object1), hash(ref1));
427 }
428
429 #[test]
430 fn test_enumeration_hash_reference_identical() {
431 let mut enum1 = Enumeration::default();
432 enum1
433 .mappings
434 .insert("active".to_string(), "Active".to_string());
435 enum1
436 .mappings
437 .insert("inactive".to_string(), "Inactive".to_string());
438
439 let mut enum2 = Enumeration::default();
440 enum2
441 .mappings
442 .insert("inactive".to_string(), "Inactive".to_string());
443 enum2
444 .mappings
445 .insert("active".to_string(), "Active".to_string());
446
447 let ref1: &Enumeration = &enum1;
448 let ref2: &Enumeration = &enum2;
449
450 assert_eq!(hash(ref1), hash(ref2));
451 assert_eq!(hash(&enum1), hash(ref1));
452 }
453
454 #[test]
455 fn test_enumeration_hash_reference_different() {
456 let mut enum1 = Enumeration::default();
457 enum1
458 .mappings
459 .insert("active".to_string(), "Active".to_string());
460 enum1
461 .mappings
462 .insert("inactive".to_string(), "Inactive".to_string());
463
464 let mut enum2 = Enumeration::default();
465 enum2
466 .mappings
467 .insert("pending".to_string(), "Pending".to_string());
468 enum2
469 .mappings
470 .insert("active".to_string(), "Active".to_string());
471
472 let ref1: &Enumeration = &enum1;
473 let ref2: &Enumeration = &enum2;
474
475 assert_ne!(hash(ref1), hash(ref2));
476 assert_eq!(hash(&enum1), hash(ref1));
477 }
478
479 #[test]
480 fn test_object_has_same_hash_identical() {
481 let mut object1 = Object::new("Person".to_string(), None);
482 object1.create_new_attribute("name".to_string(), false);
483 object1.create_new_attribute("age".to_string(), false);
484
485 let mut object2 = Object::new("Person".to_string(), None);
486 object2.create_new_attribute("age".to_string(), false);
488 object2.create_new_attribute("name".to_string(), false);
489
490 assert!(object1.same_hash(&object2));
491 }
492
493 #[test]
494 fn test_object_has_same_hash_different() {
495 let mut object1 = Object::new("Person".to_string(), None);
496 object1.create_new_attribute("name".to_string(), false);
497 object1.create_new_attribute("age".to_string(), false);
498
499 let mut object2 = Object::new("Person".to_string(), None);
500 object2.create_new_attribute("name".to_string(), false);
501 object2.create_new_attribute("email".to_string(), false);
502
503 assert!(!object1.same_hash(&object2));
504 }
505
506 #[test]
507 fn test_enumeration_has_same_hash_identical() {
508 let mut enum1 = Enumeration::default();
509 enum1
510 .mappings
511 .insert("active".to_string(), "Active".to_string());
512 enum1
513 .mappings
514 .insert("inactive".to_string(), "Inactive".to_string());
515
516 let mut enum2 = Enumeration::default();
517 enum2
519 .mappings
520 .insert("inactive".to_string(), "Inactive".to_string());
521 enum2
522 .mappings
523 .insert("active".to_string(), "Active".to_string());
524
525 assert!(enum1.same_hash(&enum2));
526 }
527
528 #[test]
529 fn test_enumeration_has_same_hash_different() {
530 let mut enum1 = Enumeration::default();
531 enum1
532 .mappings
533 .insert("active".to_string(), "Active".to_string());
534 enum1
535 .mappings
536 .insert("inactive".to_string(), "Inactive".to_string());
537
538 let mut enum2 = Enumeration::default();
539 enum2
540 .mappings
541 .insert("pending".to_string(), "Pending".to_string());
542 enum2
543 .mappings
544 .insert("active".to_string(), "Active".to_string());
545
546 assert!(!enum1.same_hash(&enum2));
547 }
548}