use crate::{attribute::Attribute, markdown::position::Position};
use convert_case::{Case, Casing};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
#[cfg(feature = "python")]
use pyo3::pyclass;
#[cfg(feature = "wasm")]
use tsify_next::Tsify;
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "python", pyclass(get_all, from_py_object))]
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
pub struct Object {
pub name: String,
pub attributes: Vec<Attribute>,
pub docstring: String,
pub term: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mixins: Vec<String>,
pub position: Option<Position>,
}
impl Object {
pub fn new(name: String, term: Option<String>) -> Self {
let name = name.replace(" ", "_").to_case(Case::Pascal);
Object {
name,
attributes: Vec::new(),
docstring: String::new(),
term,
mixins: Vec::new(),
position: None,
}
}
pub fn add_attribute(&mut self, attribute: Attribute) {
self.attributes.push(attribute);
}
pub fn set_docstring(&mut self, docstring: String) {
self.docstring = docstring;
}
pub fn set_position(&mut self, position: Position) {
self.position = Some(position);
}
pub fn get_last_attribute(&mut self) -> Option<&mut Attribute> {
self.attributes.last_mut()
}
pub fn create_new_attribute(&mut self, name: String, required: bool) {
let attribute = Attribute::new(name, required);
self.attributes.push(attribute);
}
pub fn has_attributes(&self) -> bool {
!self.attributes.is_empty()
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn has_any_terms(&self) -> bool {
self.attributes.iter().any(|attr| attr.has_term())
}
pub fn sort_attrs_by_required(&mut self) {
let mut top_elements: Vec<Attribute> = vec![];
let mut bottom_elements: Vec<Attribute> = vec![];
for attr in self.attributes.iter() {
if attr.required && attr.default.is_none() && !attr.is_array {
top_elements.push(attr.clone());
} else {
bottom_elements.push(attr.clone());
}
}
self.attributes = top_elements;
self.attributes.append(&mut bottom_elements);
}
pub(crate) fn same_hash(&self, other: &Object) -> bool {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
self.hash(&mut hasher1);
other.hash(&mut hasher2);
hasher1.finish() == hasher2.finish()
}
}
impl Hash for Object {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut attr_names: Vec<&String> = self.attributes.iter().map(|attr| &attr.name).collect();
attr_names.sort();
attr_names.hash(state);
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[cfg_attr(feature = "python", pyclass(get_all, from_py_object))]
#[cfg_attr(feature = "wasm", derive(Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
pub struct Enumeration {
pub name: String,
pub mappings: BTreeMap<String, String>,
pub docstring: String,
pub position: Option<Position>,
}
impl Enumeration {
pub fn has_values(&self) -> bool {
!self.mappings.is_empty()
}
pub fn set_position(&mut self, position: Position) {
self.position = Some(position);
}
pub(crate) fn same_hash(&self, other: &Enumeration) -> bool {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
self.hash(&mut hasher1);
other.hash(&mut hasher2);
hasher1.finish() == hasher2.finish()
}
}
impl Hash for Enumeration {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut keys: Vec<&String> = self.mappings.keys().collect();
keys.sort();
keys.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[test]
fn test_create_new_object() {
let object = Object::new("Person".to_string(), None);
assert_eq!(object.name, "Person");
assert_eq!(object.attributes.len(), 0);
assert_eq!(object.docstring, "");
assert_eq!(object.term, None);
}
#[test]
fn test_add_attribute() {
let mut object = Object::new("Person".to_string(), None);
let attribute = Attribute::new("name".to_string(), false);
object.add_attribute(attribute);
assert_eq!(object.attributes.len(), 1);
assert_eq!(object.attributes[0].name, "name");
}
#[test]
fn test_set_docstring() {
let mut object = Object::new("Person".to_string(), None);
object.set_docstring("This is a person object".to_string());
assert_eq!(object.docstring, "This is a person object");
}
#[test]
fn test_get_last_attribute() {
let mut object = Object::new("Person".to_string(), None);
let attribute = Attribute::new("name".to_string(), false);
object.add_attribute(attribute);
let last_attribute = object.get_last_attribute();
assert_eq!(last_attribute.unwrap().name, "name");
}
#[test]
fn test_create_new_attribute() {
let mut object = Object::new("Person".to_string(), None);
object.create_new_attribute("name".to_string(), false);
assert_eq!(object.attributes.len(), 1);
assert_eq!(object.attributes[0].name, "name");
}
fn hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
#[test]
fn test_object_hash_identical() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("age".to_string(), false);
object2.create_new_attribute("name".to_string(), false);
assert_eq!(hash(&object1), hash(&object2));
}
#[test]
fn test_object_hash_different() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("name".to_string(), false);
object2.create_new_attribute("email".to_string(), false);
assert_ne!(hash(&object1), hash(&object2));
}
#[test]
fn test_enumeration_hash_identical() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
assert_eq!(hash(&enum1), hash(&enum2));
}
#[test]
fn test_enumeration_hash_different() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("pending".to_string(), "Pending".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
assert_ne!(hash(&enum1), hash(&enum2));
}
#[test]
fn test_object_hash_reference_identical() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("age".to_string(), false);
object2.create_new_attribute("name".to_string(), false);
let ref1: &Object = &object1;
let ref2: &Object = &object2;
assert_eq!(hash(ref1), hash(ref2));
assert_eq!(hash(&object1), hash(ref1));
}
#[test]
fn test_object_hash_reference_different() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("name".to_string(), false);
object2.create_new_attribute("email".to_string(), false);
let ref1: &Object = &object1;
let ref2: &Object = &object2;
assert_ne!(hash(ref1), hash(ref2));
assert_eq!(hash(&object1), hash(ref1));
}
#[test]
fn test_enumeration_hash_reference_identical() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
let ref1: &Enumeration = &enum1;
let ref2: &Enumeration = &enum2;
assert_eq!(hash(ref1), hash(ref2));
assert_eq!(hash(&enum1), hash(ref1));
}
#[test]
fn test_enumeration_hash_reference_different() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("pending".to_string(), "Pending".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
let ref1: &Enumeration = &enum1;
let ref2: &Enumeration = &enum2;
assert_ne!(hash(ref1), hash(ref2));
assert_eq!(hash(&enum1), hash(ref1));
}
#[test]
fn test_object_has_same_hash_identical() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("age".to_string(), false);
object2.create_new_attribute("name".to_string(), false);
assert!(object1.same_hash(&object2));
}
#[test]
fn test_object_has_same_hash_different() {
let mut object1 = Object::new("Person".to_string(), None);
object1.create_new_attribute("name".to_string(), false);
object1.create_new_attribute("age".to_string(), false);
let mut object2 = Object::new("Person".to_string(), None);
object2.create_new_attribute("name".to_string(), false);
object2.create_new_attribute("email".to_string(), false);
assert!(!object1.same_hash(&object2));
}
#[test]
fn test_enumeration_has_same_hash_identical() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
assert!(enum1.same_hash(&enum2));
}
#[test]
fn test_enumeration_has_same_hash_different() {
let mut enum1 = Enumeration::default();
enum1
.mappings
.insert("active".to_string(), "Active".to_string());
enum1
.mappings
.insert("inactive".to_string(), "Inactive".to_string());
let mut enum2 = Enumeration::default();
enum2
.mappings
.insert("pending".to_string(), "Pending".to_string());
enum2
.mappings
.insert("active".to_string(), "Active".to_string());
assert!(!enum1.same_hash(&enum2));
}
}