rusticx-core 1.0.0

Core traits, types, and abstractions for the Rusticx multi-database ORM
Documentation
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Universal value type that maps across SQL and NoSQL backends.
///
/// `Value` is the lingua franca between your Rust types and the database.
/// The `#[derive(Model)]` macro converts struct fields into `Value` for
/// `to_row()` and back again for `from_row()`.
///
/// `From` implementations exist for all common Rust primitives so you
/// can pass values inline without explicit construction:
///
/// ```rust,ignore
/// .r#where("age", CondOp::Gte, 18i32)      // i32  → Value::Int
/// .r#where("name", CondOp::Eq, "Alice")     // &str → Value::Text
/// .r#where("active", CondOp::Eq, true)      // bool → Value::Bool
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Value {
    /// SQL NULL / BSON null.
    Null,
    /// Boolean value.
    Bool(bool),
    /// 64-bit signed integer — covers i8 through i64.
    Int(i64),
    /// 64-bit float — covers f32 and f64.
    Float(f64),
    /// UTF-8 string.
    Text(String),
    /// Raw byte blob.
    Bytes(Vec<u8>),
    /// Ordered list of values (SQL arrays, BSON arrays).
    Array(Vec<Value>),
    /// Arbitrary key-value map (JSONB, BSON document embedded field).
    Map(HashMap<String, Value>),
    /// UUID stored natively in Postgres, as string in MySQL/MongoDB.
    Uuid(uuid::Uuid),
    /// UTC timestamp.
    DateTime(chrono::DateTime<chrono::Utc>),
    /// Arbitrary JSON — stored as JSONB in Postgres, JSON in MySQL, document in Mongo.
    Json(serde_json::Value),
}

impl Value {
    pub fn is_null(&self) -> bool {
        matches!(self, Value::Null)
    }

    pub fn as_str(&self) -> Option<&str> {
        match self {
            Value::Text(s) => Some(s.as_str()),
            _ => None,
        }
    }

    pub fn as_i64(&self) -> Option<i64> {
        match self {
            Value::Int(i) => Some(*i),
            _ => None,
        }
    }

    pub fn as_f64(&self) -> Option<f64> {
        match self {
            Value::Float(f) => Some(*f),
            Value::Int(i) => Some(*i as f64),
            _ => None,
        }
    }

    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Value::Bool(b) => Some(*b),
            _ => None,
        }
    }
}

impl From<bool> for Value {
    fn from(v: bool) -> Self { Value::Bool(v) }
}
impl From<i32> for Value {
    fn from(v: i32) -> Self { Value::Int(v as i64) }
}
impl From<i64> for Value {
    fn from(v: i64) -> Self { Value::Int(v) }
}
impl From<f32> for Value {
    fn from(v: f32) -> Self { Value::Float(v as f64) }
}
impl From<f64> for Value {
    fn from(v: f64) -> Self { Value::Float(v) }
}
impl From<String> for Value {
    fn from(v: String) -> Self { Value::Text(v) }
}
impl From<&str> for Value {
    fn from(v: &str) -> Self { Value::Text(v.to_owned()) }
}
impl From<uuid::Uuid> for Value {
    fn from(v: uuid::Uuid) -> Self { Value::Uuid(v) }
}
impl From<chrono::DateTime<chrono::Utc>> for Value {
    fn from(v: chrono::DateTime<chrono::Utc>) -> Self { Value::DateTime(v) }
}
impl From<serde_json::Value> for Value {
    fn from(v: serde_json::Value) -> Self { Value::Json(v) }
}
impl<T: Into<Value>> From<Option<T>> for Value {
    fn from(v: Option<T>) -> Self {
        match v {
            Some(inner) => inner.into(),
            None => Value::Null,
        }
    }
}

/// Row as ordered key-value pairs.
pub type Row = HashMap<String, Value>;