use crate::common::{Convertible, Value, DOC_ID, UNIQUE_INDEX};
use crate::errors::{ErrorKind, NitriteError, NitriteResult};
use crate::filter::{and, field, Filter};
use crate::FIELD_SEPARATOR;
pub trait NitriteEntity: Default {
type Id: Convertible + Send + Sync + 'static;
fn entity_name(&self) -> String;
fn entity_indexes(&self) -> Option<Vec<EntityIndex>>;
fn entity_id(&self) -> Option<EntityId>;
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct EntityIndex {
fields: Vec<String>,
index_type: String,
}
impl EntityIndex {
pub fn new(fields: Vec<&str>, index_type: Option<&str>) -> Self {
EntityIndex {
fields: fields.iter().map(|field| field.to_string()).collect(),
index_type: index_type.unwrap_or(UNIQUE_INDEX).to_string(),
}
}
pub fn field_names(&self) -> &Vec<String> {
&self.fields
}
pub fn index_type(&self) -> &str {
&self.index_type
}
}
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct EntityId {
field_name: String,
is_nitrite_id: bool,
embedded_fields: Vec<String>,
}
impl EntityId {
pub fn new(
field_name: &str,
is_nitrite_id: Option<bool>,
embedded_fields: Option<Vec<&str>>,
) -> Self {
EntityId {
field_name: field_name.to_string(),
is_nitrite_id: is_nitrite_id.unwrap_or(false),
embedded_fields: embedded_fields
.unwrap_or_default()
.iter()
.map(|field| field.to_string())
.collect(),
}
}
pub fn field_name(&self) -> &str {
&self.field_name
}
pub fn is_nitrite_id(&self) -> bool {
self.is_nitrite_id
}
pub fn embedded_fields(&self) -> &Vec<String> {
&self.embedded_fields
}
pub fn encoded_field_names(&self) -> Vec<String> {
let mut result = Vec::with_capacity(self.embedded_fields.len());
let separator = FIELD_SEPARATOR.read();
for field in &self.embedded_fields {
result.push(format!("{}{}{}", self.field_name, separator, field));
}
result
}
pub fn is_embedded(&self) -> bool {
!self.embedded_fields.is_empty()
}
pub fn create_unique_filter<T: Into<Value>>(&self, element: T) -> NitriteResult<Filter> {
if self.is_embedded() {
self.create_embedded_id_filter(element)
} else {
let filter = field(&self.field_name).eq(element);
Ok(filter)
}
}
pub fn create_id_filter<T: Into<Value>>(&self, id: T) -> NitriteResult<Filter> {
if self.is_embedded() {
self.create_embedded_id_filter(id)
} else if self.is_nitrite_id() {
let filter = field(DOC_ID).eq(id);
Ok(filter)
} else {
let filter = field(&self.field_name).eq(id);
Ok(filter)
}
}
fn create_embedded_id_filter<T: Into<Value>>(&self, element: T) -> NitriteResult<Filter> {
let value = element.into();
if self.embedded_fields.len() == 1 && !value.is_document() {
let separator = FIELD_SEPARATOR.read();
let filter_field = format!(
"{}{}{}",
self.field_name,
separator,
self.embedded_fields[0]
);
let filter = field(&filter_field).eq(value);
Ok(filter)
} else {
if !value.is_document() {
log::error!("Embedded field value should be a document");
return Err(NitriteError::new(
"Embedded field value should be a document",
ErrorKind::InvalidOperation,
));
}
let mut filters = Vec::with_capacity(self.embedded_fields.len());
let document = match value.as_document() {
Some(doc) => doc.clone(),
None => {
log::error!("Failed to convert value to document for embedded field filter");
return Err(NitriteError::new(
"Embedded field value should be a document",
ErrorKind::InvalidOperation,
));
}
};
let separator = FIELD_SEPARATOR.read();
for embedded_field in &self.embedded_fields {
let filter_field = format!(
"{}{}{}",
self.field_name,
separator,
embedded_field
);
let field_value = document.get(embedded_field)?;
let filter = field(&filter_field).eq(field_value.clone());
filters.push(filter);
}
Ok(and(filters))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collection::Document;
use crate::common::Value;
use crate::errors::NitriteError;
use crate::filter::{is_and_filter, is_equals_filter};
#[derive(Default)]
struct TestEntity;
impl NitriteEntity for TestEntity {
type Id = String;
fn entity_name(&self) -> String {
"TestEntity".to_string()
}
fn entity_indexes(&self) -> Option<Vec<EntityIndex>> {
Some(vec![EntityIndex::new(vec!["field1"], Some("index_type"))])
}
fn entity_id(&self) -> Option<EntityId> {
Some(EntityId::new("id", Some(true), Some(vec!["sub_id"])))
}
}
#[test]
fn test_entity_index_new() {
let index = EntityIndex::new(vec!["field1", "field2"], Some("index_type"));
assert_eq!(index.field_names(), &vec!["field1".to_string(), "field2".to_string()]);
assert_eq!(index.index_type(), "index_type");
}
#[test]
fn test_entity_id_new() {
let id = EntityId::new("id", Some(true), Some(vec!["sub_id1", "sub_id2"]));
assert_eq!(id.field_name(), "id");
assert!(id.is_nitrite_id());
assert_eq!(id.embedded_fields(), &vec!["sub_id1".to_string(), "sub_id2".to_string()]);
}
#[test]
fn test_entity_id_encoded_field_names() {
let id = EntityId::new("id", Some(true), Some(vec!["sub_id1", "sub_id2"]));
let encoded_fields = id.encoded_field_names();
assert_eq!(encoded_fields, vec!["id.sub_id1", "id.sub_id2"]);
}
#[test]
fn test_entity_id_is_embedded() {
let id = EntityId::new("id", Some(true), Some(vec!["sub_id1", "sub_id2"]));
assert!(id.is_embedded());
}
#[test]
fn test_entity_id_create_unique_filter() {
let id = EntityId::new("id", Some(false), Some(vec!["sub_id1", "sub_id2"]));
let mut doc = Document::new();
doc.put("sub_id1", Value::String("value1".to_string())).unwrap();
doc.put("sub_id2", Value::String("value2".to_string())).unwrap();
let filter = id.create_unique_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_entity_id_create_id_filter() {
let id = EntityId::new("id", Some(true), None);
let filter = id.create_id_filter(Value::String("value".to_string())).unwrap();
assert!(is_equals_filter(&filter));
}
#[test]
fn test_entity_id_create_embedded_id_filter() {
let id = EntityId::new("id", Some(false), Some(vec!["sub_id1", "sub_id2"]));
let mut doc = Document::new();
doc.put("sub_id1", Value::String("value1".to_string())).unwrap();
doc.put("sub_id2", Value::String("value2".to_string())).unwrap();
let filter = id.create_embedded_id_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_entity_id_create_embedded_id_filter_invalid() {
let id = EntityId::new("id", Some(false), Some(vec!["sub_id1", "sub_id2"]));
let result = id.create_embedded_id_filter(Value::String("invalid".to_string()));
assert!(matches!(result, Err(NitriteError { .. })));
}
#[test]
fn test_embedded_filter_with_numeric_values() {
let id = EntityId::new("address", Some(false), Some(vec!["zip", "country"]));
let mut doc = Document::new();
doc.put("zip", Value::I32(12345)).unwrap();
doc.put("country", Value::String("USA".to_string())).unwrap();
let filter = id.create_embedded_id_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_embedded_filter_with_single_field() {
let id = EntityId::new("contact", Some(false), Some(vec!["phone"]));
let value = Value::String("555-1234".to_string());
let filter = id.create_embedded_id_filter(value).unwrap();
assert!(is_equals_filter(&filter));
}
#[test]
fn test_embedded_filter_with_corrupted_type() {
let id = EntityId::new("metadata", Some(false), Some(vec!["key", "value"]));
let result = id.create_embedded_id_filter(Value::I32(42));
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.kind(), &ErrorKind::InvalidOperation);
}
}
#[test]
fn test_embedded_filter_multi_field_all_types() {
let id = EntityId::new("details", Some(false), Some(vec!["count", "active", "label"]));
let mut doc = Document::new();
doc.put("count", Value::I32(100)).unwrap();
doc.put("active", Value::Bool(true)).unwrap();
doc.put("label", Value::String("test".to_string())).unwrap();
let filter = id.create_embedded_id_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_encoded_field_names_pre_allocation() {
let id = EntityId::new("address", Some(false), Some(vec!["street", "city", "zip", "country"]));
let encoded = id.encoded_field_names();
assert_eq!(encoded.len(), 4);
assert!(encoded[0].contains("address"));
assert!(encoded[0].contains("street"));
}
#[test]
fn test_encoded_field_names_multiple_calls() {
let id = EntityId::new("location", Some(false), Some(vec!["lat", "lon"]));
let encoded1 = id.encoded_field_names();
let encoded2 = id.encoded_field_names();
assert_eq!(encoded1, encoded2);
assert_eq!(encoded1.len(), 2);
}
#[test]
fn test_create_embedded_id_filter_pre_allocation() {
let id = EntityId::new("metadata", Some(false), Some(vec!["key", "value", "type", "priority"]));
let mut doc = Document::new();
doc.put("key", Value::String("k1".to_string())).unwrap();
doc.put("value", Value::String("v1".to_string())).unwrap();
doc.put("type", Value::String("t1".to_string())).unwrap();
doc.put("priority", Value::I32(1)).unwrap();
let filter = id.create_embedded_id_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_create_embedded_id_filter_separator_caching() {
let id = EntityId::new("contact", Some(false), Some(vec!["phone", "email", "address"]));
let mut doc = Document::new();
doc.put("phone", Value::String("555-1234".to_string())).unwrap();
doc.put("email", Value::String("test@example.com".to_string())).unwrap();
doc.put("address", Value::String("123 Main St".to_string())).unwrap();
let filter = id.create_embedded_id_filter(Value::Document(doc)).unwrap();
assert!(is_and_filter(&filter));
}
#[test]
fn test_single_embedded_field_separator_caching() {
let id = EntityId::new("profile", Some(false), Some(vec!["name"]));
let value = Value::String("John".to_string());
let filter = id.create_embedded_id_filter(value).unwrap();
assert!(is_equals_filter(&filter));
}
#[test]
fn test_encoded_field_names_empty() {
let id = EntityId::new("simple", Some(false), Some(vec![]));
let encoded = id.encoded_field_names();
assert_eq!(encoded.len(), 0);
}
}