Skip to main content

rusticx_core/
value.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Universal value type that maps across SQL and NoSQL backends.
5///
6/// `Value` is the lingua franca between your Rust types and the database.
7/// The `#[derive(Model)]` macro converts struct fields into `Value` for
8/// `to_row()` and back again for `from_row()`.
9///
10/// `From` implementations exist for all common Rust primitives so you
11/// can pass values inline without explicit construction:
12///
13/// ```rust,ignore
14/// .r#where("age", CondOp::Gte, 18i32)      // i32  → Value::Int
15/// .r#where("name", CondOp::Eq, "Alice")     // &str → Value::Text
16/// .r#where("active", CondOp::Eq, true)      // bool → Value::Bool
17/// ```
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(untagged)]
20pub enum Value {
21    /// SQL NULL / BSON null.
22    Null,
23    /// Boolean value.
24    Bool(bool),
25    /// 64-bit signed integer — covers i8 through i64.
26    Int(i64),
27    /// 64-bit float — covers f32 and f64.
28    Float(f64),
29    /// UTF-8 string.
30    Text(String),
31    /// Raw byte blob.
32    Bytes(Vec<u8>),
33    /// Ordered list of values (SQL arrays, BSON arrays).
34    Array(Vec<Value>),
35    /// Arbitrary key-value map (JSONB, BSON document embedded field).
36    Map(HashMap<String, Value>),
37    /// UUID stored natively in Postgres, as string in MySQL/MongoDB.
38    Uuid(uuid::Uuid),
39    /// UTC timestamp.
40    DateTime(chrono::DateTime<chrono::Utc>),
41    /// Arbitrary JSON — stored as JSONB in Postgres, JSON in MySQL, document in Mongo.
42    Json(serde_json::Value),
43}
44
45impl Value {
46    pub fn is_null(&self) -> bool {
47        matches!(self, Value::Null)
48    }
49
50    pub fn as_str(&self) -> Option<&str> {
51        match self {
52            Value::Text(s) => Some(s.as_str()),
53            _ => None,
54        }
55    }
56
57    pub fn as_i64(&self) -> Option<i64> {
58        match self {
59            Value::Int(i) => Some(*i),
60            _ => None,
61        }
62    }
63
64    pub fn as_f64(&self) -> Option<f64> {
65        match self {
66            Value::Float(f) => Some(*f),
67            Value::Int(i) => Some(*i as f64),
68            _ => None,
69        }
70    }
71
72    pub fn as_bool(&self) -> Option<bool> {
73        match self {
74            Value::Bool(b) => Some(*b),
75            _ => None,
76        }
77    }
78}
79
80impl From<bool> for Value {
81    fn from(v: bool) -> Self { Value::Bool(v) }
82}
83impl From<i32> for Value {
84    fn from(v: i32) -> Self { Value::Int(v as i64) }
85}
86impl From<i64> for Value {
87    fn from(v: i64) -> Self { Value::Int(v) }
88}
89impl From<f32> for Value {
90    fn from(v: f32) -> Self { Value::Float(v as f64) }
91}
92impl From<f64> for Value {
93    fn from(v: f64) -> Self { Value::Float(v) }
94}
95impl From<String> for Value {
96    fn from(v: String) -> Self { Value::Text(v) }
97}
98impl From<&str> for Value {
99    fn from(v: &str) -> Self { Value::Text(v.to_owned()) }
100}
101impl From<uuid::Uuid> for Value {
102    fn from(v: uuid::Uuid) -> Self { Value::Uuid(v) }
103}
104impl From<chrono::DateTime<chrono::Utc>> for Value {
105    fn from(v: chrono::DateTime<chrono::Utc>) -> Self { Value::DateTime(v) }
106}
107impl From<serde_json::Value> for Value {
108    fn from(v: serde_json::Value) -> Self { Value::Json(v) }
109}
110impl<T: Into<Value>> From<Option<T>> for Value {
111    fn from(v: Option<T>) -> Self {
112        match v {
113            Some(inner) => inner.into(),
114            None => Value::Null,
115        }
116    }
117}
118
119/// Row as ordered key-value pairs.
120pub type Row = HashMap<String, Value>;