use crate::collection::Document;
use crate::common::util::get_current_time_or_zero;
use crate::common::{Value, CREATED_TIME, OWNER, UNIQUE_ID};
use crate::errors::NitriteResult;
use indexmap::IndexMap;
use std::fmt::Display;
use uuid::Uuid;
pub trait AttributeAware {
fn attributes(&self) -> NitriteResult<Option<Attributes>>;
fn set_attributes(&self, attributes: Attributes) -> NitriteResult<()>;
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Attributes {
attributes: IndexMap<String, Value>,
}
impl Attributes {
pub fn new() -> Self {
let mut attributes = IndexMap::with_capacity(2);
attributes.insert(
CREATED_TIME.to_string(),
Value::String(get_current_time_or_zero().to_string()),
);
attributes.insert(
UNIQUE_ID.to_string(),
Value::String(Uuid::new_v4().to_string()),
);
Attributes { attributes }
}
pub fn new_for_collection(collection: &str) -> Self {
let mut attributes = IndexMap::with_capacity(3);
attributes.insert(OWNER.to_string(), Value::String(collection.to_string()));
attributes.insert(
CREATED_TIME.to_string(),
Value::String(get_current_time_or_zero().to_string()),
);
attributes.insert(
UNIQUE_ID.to_string(),
Value::String(Uuid::new_v4().to_string()),
);
Attributes { attributes }
}
pub fn new_with_id(collection: &str) -> Self {
let mut attributes = IndexMap::with_capacity(3);
attributes.insert(OWNER.to_string(), Value::String(collection.to_string()));
attributes.insert(
CREATED_TIME.to_string(),
Value::String(get_current_time_or_zero().to_string()),
);
attributes.insert(
UNIQUE_ID.to_string(),
Value::String(Uuid::new_v4().to_string()),
);
Attributes { attributes }
}
pub fn from_document(document: &Document) -> Self {
let mut attributes = IndexMap::with_capacity(10);
for (key, value) in document.iter() {
attributes.insert(key.to_string(), value.clone());
}
Attributes { attributes }
}
#[inline]
pub fn get(&self, key: &str) -> Option<&Value> {
self.attributes.get(key)
}
#[inline]
pub fn put(&mut self, key: &str, value: Value) {
self.attributes.insert(key.to_string(), value);
}
#[inline]
pub fn has_key(&self, key: &str) -> bool {
self.attributes.contains_key(key)
}
#[inline]
pub fn to_document(&self) -> Document {
let mut document = Document::new();
for (key, value) in self.attributes.iter() {
document.put(key, value.clone()).unwrap();
}
document
}
}
impl Display for Attributes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut result = String::new();
for (key, value) in self.attributes.iter() {
result.push_str(&format!("{}: {}, ", key, value));
}
write!(f, "{}", result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collection::Document;
use crate::common::{atomic, Atomic, ReadExecutor, Value, WriteExecutor};
use crate::errors::NitriteResult;
#[test]
fn test_attributes_new() {
let attributes = Attributes::new();
assert!(attributes.has_key(CREATED_TIME));
assert!(attributes.has_key(UNIQUE_ID));
}
#[test]
fn test_attributes_new_with_id() {
let collection = "test_collection";
let attributes = Attributes::new_with_id(collection);
assert!(attributes.has_key(OWNER));
assert_eq!(attributes.get(OWNER).unwrap(), &Value::String(collection.to_string()));
}
#[test]
fn test_attributes_from_document() {
let mut document = Document::new();
document.put("key1", Value::String("value1".to_string())).unwrap();
document.put("key2", Value::String("value2".to_string())).unwrap();
let attributes = Attributes::from_document(&document);
assert_eq!(attributes.get("key1").unwrap(), &Value::String("value1".to_string()));
assert_eq!(attributes.get("key2").unwrap(), &Value::String("value2".to_string()));
}
#[test]
fn test_attributes_get() {
let mut attributes = Attributes::new();
attributes.put("key", Value::String("value".to_string()));
assert_eq!(attributes.get("key").unwrap(), &Value::String("value".to_string()));
}
#[test]
fn test_attributes_get_none() {
let attributes = Attributes::new();
assert!(attributes.get("nonexistent_key").is_none());
}
#[test]
fn test_attributes_put() {
let mut attributes = Attributes::new();
attributes.put("key", Value::String("value".to_string()));
assert_eq!(attributes.get("key").unwrap(), &Value::String("value".to_string()));
}
#[test]
fn test_attributes_has_key() {
let mut attributes = Attributes::new();
attributes.put("key", Value::String("value".to_string()));
assert!(attributes.has_key("key"));
}
#[test]
fn test_attributes_has_key_false() {
let attributes = Attributes::new();
assert!(!attributes.has_key("nonexistent_key"));
}
#[test]
fn test_attributes_to_document() {
let mut attributes = Attributes::new();
attributes.put("key", Value::String("value".to_string()));
let document = attributes.to_document();
assert_eq!(document.get("key").unwrap(), Value::String("value".to_string()));
}
#[test]
fn test_attributes_display() {
let mut attributes = Attributes::new();
attributes.put("key", Value::String("value".to_string()));
let display = format!("{}", attributes);
assert!(display.contains("key: \"value\""));
}
#[test]
fn test_attribute_aware_trait() {
struct TestStruct {
attributes: Atomic<Attributes>,
}
impl AttributeAware for TestStruct {
fn attributes(&self) -> NitriteResult<Option<Attributes>> {
self.attributes.read_with(|attributes| Ok(Some(attributes.clone())))
}
fn set_attributes(&self, attributes: Attributes) -> NitriteResult<()> {
self.attributes.write_with(|current_attributes| {
*current_attributes = attributes;
Ok(())
})
}
}
let test_struct = TestStruct {
attributes: atomic(Attributes::new()),
};
let new_attributes = Attributes::new_with_id("test_collection");
test_struct.set_attributes(new_attributes.clone()).unwrap();
assert_eq!(test_struct.attributes().unwrap().unwrap(), new_attributes);
}
#[test]
fn bench_attributes_creation() {
for _ in 0..1000 {
let _ = Attributes::new();
}
}
#[test]
fn bench_attributes_for_collection() {
for i in 0..500 {
let _ = Attributes::new_for_collection(&format!("collection_{}", i));
}
}
#[test]
fn bench_attributes_access_and_put() {
let mut attributes = Attributes::new_for_collection("test");
for i in 0..100 {
attributes.put(&format!("key_{}", i), Value::I32(i));
let _ = attributes.get(&format!("key_{}", i));
}
}
}