use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SerializationFormat {
Json,
MessagePack,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum NumberValue {
Integer(i64),
Float(f64),
Decimal(Decimal),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[allow(clippy::upper_case_acronyms)]
pub enum FieldType {
String(String),
Integer(i64),
Float(f64),
Number(NumberValue),
Boolean(bool),
Object(HashMap<String, FieldType>),
Array(Vec<FieldType>),
Set(Vec<FieldType>),
Vector(Vec<FieldType>),
DateTime(DateTime<Utc>),
UUID(Uuid),
Decimal(Decimal),
Duration(Duration),
Binary(Vec<u8>),
Bytes(Vec<u8>),
Null,
}
impl FieldType {
pub fn string(s: impl Into<String>) -> Self {
FieldType::String(s.into())
}
pub fn integer(i: i64) -> Self {
FieldType::Integer(i)
}
pub fn float(f: f64) -> Self {
FieldType::Float(f)
}
pub fn number_int(i: i64) -> Self {
FieldType::Number(NumberValue::Integer(i))
}
pub fn number_float(f: f64) -> Self {
FieldType::Number(NumberValue::Float(f))
}
pub fn number_decimal(d: Decimal) -> Self {
FieldType::Number(NumberValue::Decimal(d))
}
pub fn boolean(b: bool) -> Self {
FieldType::Boolean(b)
}
pub fn array(items: Vec<FieldType>) -> Self {
FieldType::Array(items)
}
pub fn set(items: Vec<FieldType>) -> Self {
FieldType::Set(items)
}
pub fn vector(embeddings: Vec<f64>) -> Self {
FieldType::Vector(embeddings.into_iter().map(FieldType::Float).collect())
}
pub fn datetime(dt: DateTime<Utc>) -> Self {
FieldType::DateTime(dt)
}
pub fn uuid(uuid: Uuid) -> Self {
FieldType::UUID(uuid)
}
pub fn decimal(d: Decimal) -> Self {
FieldType::Decimal(d)
}
pub fn null() -> Self {
FieldType::Null
}
pub fn as_string(&self) -> Option<&str> {
match self {
FieldType::String(s) => Some(s),
FieldType::Object(map) => {
if let Some(FieldType::String(v)) = map.get("value") {
Some(v)
} else {
None
}
}
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
FieldType::Boolean(b) => Some(*b),
FieldType::Object(map) => {
if let Some(FieldType::Boolean(b)) = map.get("value") {
Some(*b)
} else {
None
}
}
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Record(HashMap<String, FieldType>);
impl Record {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<FieldType>) {
self.0.insert(key.into(), value.into());
}
pub fn field(mut self, key: impl Into<String>, value: impl Into<FieldType>) -> Self {
self.0.insert(key.into(), value.into());
self
}
pub fn get(&self, key: &str) -> Option<&FieldType> {
self.0.get(key)
}
pub fn get_string(&self, key: &str) -> Option<&str> {
self.0.get(key).and_then(|f| f.as_string())
}
pub fn get_bool(&self, key: &str) -> Option<bool> {
self.0.get(key).and_then(|f| f.as_bool())
}
pub fn remove(&mut self, key: &str) -> Option<FieldType> {
self.0.remove(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.0.contains_key(key)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn with_ttl(mut self, duration: impl Into<String>) -> Self {
self.0
.insert("ttl".to_string(), FieldType::String(duration.into()));
self
}
pub fn with_ttl_update_on_access(
mut self,
duration: impl Into<String>,
update_on_access: bool,
) -> Self {
self.0
.insert("ttl".to_string(), FieldType::String(duration.into()));
self.0.insert(
"ttl_update_on_access".to_string(),
FieldType::Boolean(update_on_access),
);
self
}
}
impl Default for Record {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for Record {
type Target = HashMap<String, FieldType>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for Record {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<HashMap<String, FieldType>> for Record {
fn from(map: HashMap<String, FieldType>) -> Self {
Self(map)
}
}
impl From<Record> for HashMap<String, FieldType> {
fn from(record: Record) -> Self {
record.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum QueryOperator {
Eq(FieldType),
Ne(FieldType),
Gt(FieldType),
Gte(FieldType),
Lt(FieldType),
Lte(FieldType),
In(Vec<FieldType>),
#[serde(rename = "NotIn")]
Nin(Vec<FieldType>),
Regex(String),
Exists(bool),
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Query {
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skip: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub join: Option<serde_json::Value>,
#[serde(default)]
pub bypass_cache: Option<bool>,
#[serde(default)]
pub select_fields: Option<Vec<String>>,
#[serde(default)]
pub exclude_fields: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bypass_ripple: Option<bool>,
}
impl Query {
pub fn new() -> Self {
Self::default()
}
pub fn filter(mut self, filter: serde_json::Value) -> Self {
self.filter = Some(filter);
self
}
pub fn sort(mut self, sort: serde_json::Value) -> Self {
self.sort = Some(sort);
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn skip(mut self, skip: usize) -> Self {
self.skip = Some(skip);
self
}
pub fn bypass_cache(mut self, bypass: bool) -> Self {
self.bypass_cache = Some(bypass);
self
}
pub fn bypass_ripple(mut self, bypass: bool) -> Self {
self.bypass_ripple = Some(bypass);
self
}
pub fn join(mut self, join: serde_json::Value) -> Self {
self.join = Some(join);
self
}
pub fn select_fields(mut self, fields: Vec<String>) -> Self {
self.select_fields = Some(fields);
self
}
pub fn exclude_fields(mut self, fields: Vec<String>) -> Self {
self.exclude_fields = Some(fields);
self
}
}
impl From<String> for FieldType {
fn from(s: String) -> Self {
FieldType::String(s)
}
}
impl From<&str> for FieldType {
fn from(s: &str) -> Self {
FieldType::String(s.to_string())
}
}
impl From<i64> for FieldType {
fn from(i: i64) -> Self {
FieldType::Integer(i)
}
}
impl From<i32> for FieldType {
fn from(i: i32) -> Self {
FieldType::Integer(i as i64)
}
}
impl From<f64> for FieldType {
fn from(f: f64) -> Self {
FieldType::Float(f)
}
}
impl From<bool> for FieldType {
fn from(b: bool) -> Self {
FieldType::Boolean(b)
}
}
impl From<DateTime<Utc>> for FieldType {
fn from(dt: DateTime<Utc>) -> Self {
FieldType::DateTime(dt)
}
}
impl From<Uuid> for FieldType {
fn from(uuid: Uuid) -> Self {
FieldType::UUID(uuid)
}
}
impl From<Decimal> for FieldType {
fn from(d: Decimal) -> Self {
FieldType::Decimal(d)
}
}
impl From<serde_json::Value> for FieldType {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::Null => FieldType::Null,
serde_json::Value::Bool(b) => FieldType::Boolean(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
FieldType::Integer(i)
} else if let Some(f) = n.as_f64() {
FieldType::Float(f)
} else {
FieldType::String(n.to_string())
}
}
serde_json::Value::String(s) => FieldType::String(s),
serde_json::Value::Array(arr) => {
FieldType::Array(arr.into_iter().map(FieldType::from).collect())
}
serde_json::Value::Object(obj) => FieldType::Object(
obj.into_iter()
.map(|(k, v)| (k, FieldType::from(v)))
.collect(),
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
#[test]
fn test_field_type_string() {
let field: FieldType = "hello".into();
assert!(matches!(field, FieldType::String(_)));
}
#[test]
fn test_field_type_integer() {
let field: FieldType = 42i64.into();
assert!(matches!(field, FieldType::Integer(42)));
}
#[test]
fn test_field_type_float() {
let field: FieldType = 3.15f64.into();
assert!(matches!(field, FieldType::Float(_)));
}
#[test]
fn test_field_type_boolean() {
let field: FieldType = true.into();
assert!(matches!(field, FieldType::Boolean(true)));
}
#[test]
fn test_field_type_datetime() {
let now = Utc::now();
let field: FieldType = now.into();
assert!(matches!(field, FieldType::DateTime(_)));
}
#[test]
fn test_field_type_uuid() {
let uuid = Uuid::new_v4();
let field: FieldType = uuid.into();
assert!(matches!(field, FieldType::UUID(_)));
}
#[test]
fn test_field_type_decimal() {
let decimal = Decimal::new(12345, 2);
let field: FieldType = decimal.into();
assert!(matches!(field, FieldType::Decimal(_)));
}
#[test]
fn test_number_value_integer() {
let num = NumberValue::Integer(42);
assert!(matches!(num, NumberValue::Integer(42)));
}
#[test]
fn test_number_value_float() {
let num = NumberValue::Float(3.15);
assert!(matches!(num, NumberValue::Float(_)));
}
#[test]
fn test_number_value_decimal() {
let decimal = Decimal::new(12345, 2);
let num = NumberValue::Decimal(decimal);
assert!(matches!(num, NumberValue::Decimal(_)));
}
#[test]
fn test_query_operator_eq() {
let op = QueryOperator::Eq(FieldType::String("test".to_string()));
assert!(matches!(op, QueryOperator::Eq(_)));
}
#[test]
fn test_query_operator_ne() {
let op = QueryOperator::Ne(FieldType::Integer(42));
assert!(matches!(op, QueryOperator::Ne(_)));
}
#[test]
fn test_query_operator_gt() {
let op = QueryOperator::Gt(FieldType::Integer(10));
assert!(matches!(op, QueryOperator::Gt(_)));
}
#[test]
fn test_query_operator_gte() {
let op = QueryOperator::Gte(FieldType::Integer(10));
assert!(matches!(op, QueryOperator::Gte(_)));
}
#[test]
fn test_query_operator_lt() {
let op = QueryOperator::Lt(FieldType::Integer(100));
assert!(matches!(op, QueryOperator::Lt(_)));
}
#[test]
fn test_query_operator_lte() {
let op = QueryOperator::Lte(FieldType::Integer(100));
assert!(matches!(op, QueryOperator::Lte(_)));
}
#[test]
fn test_query_operator_in() {
let op = QueryOperator::In(vec![FieldType::Integer(1), FieldType::Integer(2)]);
assert!(matches!(op, QueryOperator::In(_)));
}
#[test]
fn test_query_operator_nin() {
let op = QueryOperator::Nin(vec![FieldType::Integer(1), FieldType::Integer(2)]);
assert!(matches!(op, QueryOperator::Nin(_)));
}
#[test]
fn test_query_operator_regex() {
let op = QueryOperator::Regex("^test".to_string());
assert!(matches!(op, QueryOperator::Regex(_)));
}
#[test]
fn test_query_operator_exists() {
let op = QueryOperator::Exists(true);
assert!(matches!(op, QueryOperator::Exists(true)));
}
#[test]
fn test_query_default() {
let query = Query::default();
assert!(query.filter.is_none());
assert!(query.sort.is_none());
assert!(query.limit.is_none());
assert!(query.skip.is_none());
}
#[test]
fn test_query_serialization() {
let query = Query::new()
.filter(serde_json::json!({"name": "test"}))
.limit(10);
let json = serde_json::to_value(&query).unwrap();
assert!(json["filter"].is_object());
assert_eq!(json["limit"], 10);
}
#[test]
fn test_query_deserialization() {
let json = serde_json::json!({
"filter": {"name": "test"},
"limit": 10,
"skip": 5
});
let query: Query = serde_json::from_value(json).unwrap();
assert!(query.filter.is_some());
assert_eq!(query.limit, Some(10));
assert_eq!(query.skip, Some(5));
}
#[test]
fn test_record_serialization() {
let mut record = Record::new();
record.insert("name", "test");
record.insert("age", 30);
let json = serde_json::to_value(&record).unwrap();
assert!(json.is_object());
}
#[test]
fn test_record_deserialization() {
let json = serde_json::json!({
"name": "test",
"age": 30
});
let record: Record = serde_json::from_value(json).unwrap();
assert!(record.contains_key("name"));
assert!(record.contains_key("age"));
}
#[test]
fn test_as_string_direct() {
let field = FieldType::String("hello".into());
assert_eq!(field.as_string(), Some("hello"));
}
#[test]
fn test_as_string_typed_wrapper() {
let field = FieldType::Object(
[
("type".into(), FieldType::String("String".into())),
("value".into(), FieldType::String("hello".into())),
]
.into_iter()
.collect(),
);
assert_eq!(field.as_string(), Some("hello"));
}
#[test]
fn test_as_string_non_string() {
let field = FieldType::Integer(42);
assert_eq!(field.as_string(), None);
}
#[test]
fn test_as_bool_direct() {
assert_eq!(FieldType::Boolean(true).as_bool(), Some(true));
assert_eq!(FieldType::Boolean(false).as_bool(), Some(false));
}
#[test]
fn test_as_bool_typed_wrapper() {
let field = FieldType::Object(
[
("type".into(), FieldType::String("Boolean".into())),
("value".into(), FieldType::Boolean(true)),
]
.into_iter()
.collect(),
);
assert_eq!(field.as_bool(), Some(true));
}
#[test]
fn test_record_get_string() {
let record = Record::new().field("name", "direct").field(
"wrapped",
FieldType::Object(
[
("type".into(), FieldType::String("String".into())),
("value".into(), FieldType::String("wrapped_val".into())),
]
.into_iter()
.collect(),
),
);
assert_eq!(record.get_string("name"), Some("direct"));
assert_eq!(record.get_string("wrapped"), Some("wrapped_val"));
assert_eq!(record.get_string("missing"), None);
}
#[test]
fn test_record_get_bool() {
let record = Record::new().field("flag", true);
assert_eq!(record.get_bool("flag"), Some(true));
assert_eq!(record.get_bool("missing"), None);
}
}