use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DocumentId(pub String);
impl DocumentId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn generate() -> Self {
Self(uuid())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for DocumentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for DocumentId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for DocumentId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
fn uuid() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let nanos = duration.as_nanos();
let random: u64 = (nanos as u64).wrapping_mul(0x5851_f42d_4c95_7f2d);
format!("{:016x}{:016x}", nanos as u64, random)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[derive(Default)]
pub enum Value {
#[default]
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Array(Vec<Value>),
Object(HashMap<String, Value>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool(_))
}
pub fn is_number(&self) -> bool {
matches!(self, Self::Int(_) | Self::Float(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
pub fn is_array(&self) -> bool {
matches!(self, Self::Array(_))
}
pub fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::Int(n) => Some(*n),
Self::Float(f) => Some(*f as i64),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Float(f) => Some(*f),
Self::Int(n) => Some(*n as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn as_array(&self) -> Option<&Vec<Value>> {
match self {
Self::Array(arr) => Some(arr),
_ => None,
}
}
pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
match self {
Self::Object(obj) => Some(obj),
_ => None,
}
}
pub fn get_path(&self, path: &str) -> Option<&Value> {
let parts: Vec<&str> = path.split('.').collect();
self.get_path_parts(&parts)
}
fn get_path_parts(&self, parts: &[&str]) -> Option<&Value> {
if parts.is_empty() {
return Some(self);
}
let key = parts[0];
let rest = &parts[1..];
match self {
Self::Object(obj) => obj.get(key).and_then(|v| v.get_path_parts(rest)),
Self::Array(arr) => key
.parse::<usize>()
.ok()
.and_then(|idx| arr.get(idx))
.and_then(|v| v.get_path_parts(rest)),
_ => None,
}
}
pub fn from_json(json: JsonValue) -> Self {
match json {
JsonValue::Null => Self::Null,
JsonValue::Bool(b) => Self::Bool(b),
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
Self::Int(i)
} else if let Some(f) = n.as_f64() {
Self::Float(f)
} else {
Self::Float(0.0)
}
}
JsonValue::String(s) => Self::String(s),
JsonValue::Array(arr) => Self::Array(arr.into_iter().map(Self::from_json).collect()),
JsonValue::Object(obj) => Self::Object(
obj.into_iter()
.map(|(k, v)| (k, Self::from_json(v)))
.collect(),
),
}
}
pub fn to_json(&self) -> JsonValue {
match self {
Self::Null => JsonValue::Null,
Self::Bool(b) => JsonValue::Bool(*b),
Self::Int(n) => JsonValue::Number((*n).into()),
Self::Float(f) => serde_json::Number::from_f64(*f)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null),
Self::String(s) => JsonValue::String(s.clone()),
Self::Array(arr) => JsonValue::Array(arr.iter().map(|v| v.to_json()).collect()),
Self::Object(obj) => {
JsonValue::Object(obj.iter().map(|(k, v)| (k.clone(), v.to_json())).collect())
}
}
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Self::Bool(b)
}
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Self::Int(n)
}
}
impl From<i32> for Value {
fn from(n: i32) -> Self {
Self::Int(n as i64)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Self {
Self::Float(f)
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Self::String(s)
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Self::String(s.to_string())
}
}
impl From<Vec<Value>> for Value {
fn from(arr: Vec<Value>) -> Self {
Self::Array(arr)
}
}
impl From<HashMap<String, Value>> for Value {
fn from(obj: HashMap<String, Value>) -> Self {
Self::Object(obj)
}
}
pub fn compare_values(a: Option<&Value>, b: Option<&Value>) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(va), Some(vb)) => compare_value_inner(va, vb),
}
}
fn compare_value_inner(a: &Value, b: &Value) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Less,
(_, Value::Null) => Ordering::Greater,
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Int(a), Value::Int(b)) => a.cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
(Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal),
(Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal),
(Value::String(a), Value::String(b)) => a.cmp(b),
_ => Ordering::Equal,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
#[serde(rename = "_id")]
pub id: DocumentId,
#[serde(flatten)]
pub data: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub _created_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub _updated_at: Option<i64>,
}
impl Document {
pub fn new() -> Self {
Self {
id: DocumentId::generate(),
data: HashMap::new(),
_created_at: Some(current_timestamp()),
_updated_at: None,
}
}
pub fn with_id(id: impl Into<DocumentId>) -> Self {
Self {
id: id.into(),
data: HashMap::new(),
_created_at: Some(current_timestamp()),
_updated_at: None,
}
}
pub fn from_json(json: JsonValue) -> Option<Self> {
match json {
JsonValue::Object(obj) => {
let id = obj
.get("_id")
.and_then(|v| v.as_str())
.map(DocumentId::new)
.unwrap_or_else(DocumentId::generate);
let data: HashMap<String, Value> = obj
.into_iter()
.filter(|(k, _)| !k.starts_with('_'))
.map(|(k, v)| (k, Value::from_json(v)))
.collect();
Some(Self {
id,
data,
_created_at: Some(current_timestamp()),
_updated_at: None,
})
}
_ => None,
}
}
pub fn to_json(&self) -> JsonValue {
let mut obj = serde_json::Map::new();
obj.insert("_id".to_string(), JsonValue::String(self.id.0.clone()));
if let Some(ts) = self._created_at {
obj.insert("_created_at".to_string(), JsonValue::Number(ts.into()));
}
if let Some(ts) = self._updated_at {
obj.insert("_updated_at".to_string(), JsonValue::Number(ts.into()));
}
for (k, v) in &self.data {
obj.insert(k.clone(), v.to_json());
}
JsonValue::Object(obj)
}
pub fn get(&self, key: &str) -> Option<&Value> {
if key.contains('.') {
let parts: Vec<&str> = key.splitn(2, '.').collect();
self.data.get(parts[0]).and_then(|v| v.get_path(parts[1]))
} else {
self.data.get(key)
}
}
pub fn set(&mut self, key: impl Into<String>, value: impl Into<Value>) {
self.data.insert(key.into(), value.into());
self._updated_at = Some(current_timestamp());
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
let result = self.data.remove(key);
if result.is_some() {
self._updated_at = Some(current_timestamp());
}
result
}
pub fn contains(&self, key: &str) -> bool {
self.get(key).is_some()
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.data.keys()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl Default for Document {
fn default() -> Self {
Self::new()
}
}
fn current_timestamp() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_id() {
let id1 = DocumentId::generate();
let id2 = DocumentId::generate();
assert_ne!(id1, id2);
let id3 = DocumentId::new("custom-id");
assert_eq!(id3.as_str(), "custom-id");
}
#[test]
fn test_value_types() {
let null = Value::Null;
assert!(null.is_null());
let boolean = Value::Bool(true);
assert!(boolean.is_bool());
assert_eq!(boolean.as_bool(), Some(true));
let number = Value::Int(42);
assert!(number.is_number());
assert_eq!(number.as_i64(), Some(42));
let string = Value::String("hello".to_string());
assert!(string.is_string());
assert_eq!(string.as_str(), Some("hello"));
}
#[test]
fn test_value_path() {
let mut inner = HashMap::new();
inner.insert("city".to_string(), Value::String("NYC".to_string()));
let mut outer = HashMap::new();
outer.insert("address".to_string(), Value::Object(inner));
let value = Value::Object(outer);
assert_eq!(
value.get_path("address.city").and_then(|v| v.as_str()),
Some("NYC")
);
}
#[test]
fn test_document() {
let mut doc = Document::new();
doc.set("name", "Alice");
doc.set("age", 30i64);
assert_eq!(doc.get("name").and_then(|v| v.as_str()), Some("Alice"));
assert_eq!(doc.get("age").and_then(|v| v.as_i64()), Some(30));
assert!(doc.contains("name"));
assert!(!doc.contains("email"));
}
#[test]
fn test_document_from_json() {
let json = serde_json::json!({
"_id": "doc123",
"name": "Bob",
"active": true
});
let doc = Document::from_json(json).unwrap();
assert_eq!(doc.id.as_str(), "doc123");
assert_eq!(doc.get("name").and_then(|v| v.as_str()), Some("Bob"));
assert_eq!(doc.get("active").and_then(|v| v.as_bool()), Some(true));
}
#[test]
fn test_json_conversion() {
let mut doc = Document::with_id("test-doc");
doc.set("count", 100i64);
doc.set("ratio", 0.5f64);
let json = doc.to_json();
assert_eq!(json["_id"], "test-doc");
assert_eq!(json["count"], 100);
}
}