use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
pub const BINARY_FORMAT_VERSION: u8 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
#[serde(rename = "_key")]
pub key: String,
#[serde(rename = "_id")]
pub id: String,
#[serde(rename = "_rev")]
pub rev: String,
#[serde(rename = "_created_at")]
pub created_at: DateTime<Utc>,
#[serde(rename = "_updated_at")]
pub updated_at: DateTime<Utc>,
#[serde(flatten)]
pub data: Value,
}
impl Document {
fn generate_rev() -> String {
Uuid::new_v4().to_string()
}
pub fn new(collection_name: &str, mut data: Value) -> Self {
let key = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)).to_string();
let id = format!("{}/{}", collection_name, key);
let now = Utc::now();
if let Some(obj) = data.as_object_mut() {
obj.remove("_key");
obj.remove("_id");
obj.remove("_rev");
obj.remove("_created_at");
obj.remove("_updated_at");
}
Self {
key,
id,
rev: Self::generate_rev(),
created_at: now,
updated_at: now,
data,
}
}
pub fn with_key(collection_name: &str, key: String, mut data: Value) -> Self {
let id = format!("{}/{}", collection_name, key);
let now = Utc::now();
if let Some(obj) = data.as_object_mut() {
obj.remove("_key");
obj.remove("_id");
obj.remove("_rev");
obj.remove("_created_at");
obj.remove("_updated_at");
}
Self {
key,
id,
rev: Self::generate_rev(),
created_at: now,
updated_at: now,
data,
}
}
pub fn update(&mut self, data: Value) {
if let (Some(existing), Some(new)) = (self.data.as_object_mut(), data.as_object()) {
for (key, value) in new {
if !key.starts_with('_') {
existing.insert(key.clone(), value.clone());
}
}
} else {
let mut new_data = data;
if let Some(obj) = new_data.as_object_mut() {
obj.remove("_key");
obj.remove("_id");
obj.remove("_rev");
obj.remove("_created_at");
obj.remove("_updated_at");
}
self.data = new_data;
}
self.rev = Self::generate_rev();
self.updated_at = Utc::now();
}
pub fn revision(&self) -> &str {
&self.rev
}
pub fn get(&self, field: &str) -> Option<Value> {
match field {
"_key" => Some(Value::String(self.key.clone())),
"_id" => Some(Value::String(self.id.clone())),
"_rev" => Some(Value::String(self.rev.clone())),
_ => self.data.get(field).cloned(),
}
}
pub fn to_value(&self) -> Value {
if let Value::Object(data_map) = &self.data {
let mut map = serde_json::Map::with_capacity(data_map.len() + 5);
map.insert("_key".to_string(), Value::String(self.key.clone()));
map.insert("_id".to_string(), Value::String(self.id.clone()));
map.insert("_rev".to_string(), Value::String(self.rev.clone()));
map.insert(
"_created_at".to_string(),
Value::String(self.created_at.to_rfc3339()),
);
map.insert(
"_updated_at".to_string(),
Value::String(self.updated_at.to_rfc3339()),
);
for (k, v) in data_map {
map.insert(k.clone(), v.clone());
}
Value::Object(map)
} else {
serde_json::to_value(self).unwrap_or(Value::Null)
}
}
pub fn into_value(self) -> Value {
if let Value::Object(mut data_map) = self.data {
data_map.insert("_key".to_string(), Value::String(self.key));
data_map.insert("_id".to_string(), Value::String(self.id));
data_map.insert("_rev".to_string(), Value::String(self.rev));
data_map.insert(
"_created_at".to_string(),
Value::String(self.created_at.to_rfc3339()),
);
data_map.insert(
"_updated_at".to_string(),
Value::String(self.updated_at.to_rfc3339()),
);
Value::Object(data_map)
} else {
serde_json::to_value(&self).unwrap_or(Value::Null)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_document_new() {
let data = json!({"name": "Alice", "age": 30});
let doc = Document::new("users", data);
assert!(!doc.key.is_empty());
assert!(doc.id.starts_with("users/"));
assert!(!doc.rev.is_empty());
assert_eq!(doc.data.get("name"), Some(&json!("Alice")));
assert_eq!(doc.data.get("age"), Some(&json!(30)));
}
#[test]
fn test_document_with_key() {
let data = json!({"name": "Bob"});
let doc = Document::with_key("users", "custom-key".to_string(), data);
assert_eq!(doc.key, "custom-key");
assert_eq!(doc.id, "users/custom-key");
assert!(!doc.rev.is_empty());
assert_eq!(doc.data.get("name"), Some(&json!("Bob")));
}
#[test]
fn test_document_strips_system_fields() {
let data = json!({
"_key": "should-be-removed",
"_id": "should-be-removed",
"_rev": "should-be-removed",
"_created_at": "should-be-removed",
"_updated_at": "should-be-removed",
"name": "Charlie"
});
let doc = Document::new("users", data);
assert!(doc.data.get("_key").is_none());
assert!(doc.data.get("_id").is_none());
assert!(doc.data.get("_rev").is_none());
assert_eq!(doc.data.get("name"), Some(&json!("Charlie")));
}
#[test]
fn test_document_update_merges() {
let initial = json!({"name": "Dave", "age": 25});
let mut doc = Document::with_key("users", "dave".to_string(), initial);
let original_rev = doc.rev.clone();
doc.update(json!({"age": 26, "city": "NYC"}));
assert_eq!(doc.data.get("name"), Some(&json!("Dave")));
assert_eq!(doc.data.get("age"), Some(&json!(26)));
assert_eq!(doc.data.get("city"), Some(&json!("NYC")));
assert_ne!(doc.rev, original_rev);
}
#[test]
fn test_document_update_ignores_system_fields() {
let initial = json!({"name": "Eve"});
let mut doc = Document::with_key("users", "eve".to_string(), initial);
doc.update(json!({"_key": "hacked", "email": "eve@test.com"}));
assert_eq!(doc.key, "eve");
assert_eq!(doc.data.get("email"), Some(&json!("eve@test.com")));
}
#[test]
fn test_document_get_special_fields() {
let doc = Document::with_key("users", "test".to_string(), json!({"name": "Test"}));
assert_eq!(doc.get("_key"), Some(json!("test")));
assert_eq!(doc.get("_id"), Some(json!("users/test")));
assert!(doc.get("_rev").is_some());
assert_eq!(doc.get("name"), Some(json!("Test")));
assert_eq!(doc.get("nonexistent"), None);
}
#[test]
fn test_document_revision() {
let doc = Document::new("test", json!({}));
assert!(!doc.revision().is_empty());
}
#[test]
fn test_document_to_value() {
let doc = Document::with_key("users", "id1".to_string(), json!({"name": "Frank"}));
let value = doc.to_value();
assert!(value.get("_key").is_some());
assert!(value.get("_id").is_some());
assert!(value.get("_rev").is_some());
assert!(value.get("_created_at").is_some());
assert!(value.get("_updated_at").is_some());
assert_eq!(value.get("name"), Some(&json!("Frank")));
}
#[test]
fn test_document_timestamps() {
let before = Utc::now();
let doc = Document::new("users", json!({}));
let after = Utc::now();
assert!(doc.created_at >= before && doc.created_at <= after);
assert!(doc.updated_at >= before && doc.updated_at <= after);
}
#[test]
fn test_document_update_changes_updated_at() {
let mut doc = Document::new("users", json!({}));
let original_updated = doc.updated_at;
std::thread::sleep(std::time::Duration::from_millis(10));
doc.update(json!({"new_field": true}));
assert!(doc.updated_at > original_updated);
assert_eq!(doc.created_at, doc.created_at); }
#[test]
fn test_document_serialization() {
let doc = Document::with_key("test", "key1".to_string(), json!({"x": 1}));
let serialized = serde_json::to_string(&doc).unwrap();
assert!(serialized.contains("_key"));
assert!(serialized.contains("key1"));
let deserialized: Document = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.key, doc.key);
}
}