floz-orm 0.1.7

A lightweight, typesafe Rust ORM — unifying DAO and DSL from a single schema
Documentation
//! The `Value` enum — a type-safe wrapper for all SQL parameter values.
//!
//! Every nullable type has a dedicated `Option*` variant instead of a generic `Null`,
//! because PostgreSQL requires typed NULLs for parameter binding. Binding an untyped NULL
//! causes: `ERROR: could not determine data type of parameter $N`.

use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use uuid::Uuid;

/// Represents a SQL parameter value with full type information.
///
/// # Design Decision: Typed Nulls
///
/// There is no generic `Value::Null` variant. Each nullable type has its own `Option*`
/// variant (e.g., `OptionInt(Option<i32>)`) so that PostgreSQL receives the correct
/// type OID even when the value is NULL.
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    // ── Non-nullable types ──
    Short(i16),
    Int(i32),
    BigInt(i64),
    Real(f32),
    Double(f64),
    Bool(bool),
    String(String),
    Bytes(Vec<u8>),
    Uuid(Uuid),
    DateTime(DateTime<Utc>),
    NaiveDateTime(NaiveDateTime),
    NaiveDate(NaiveDate),
    NaiveTime(NaiveTime),
    Json(serde_json::Value),
    Jsonb(serde_json::Value),

    // ── Nullable types (preserves PostgreSQL type OID for NULL) ──
    OptionShort(Option<i16>),
    OptionInt(Option<i32>),
    OptionBigInt(Option<i64>),
    OptionReal(Option<f32>),
    OptionDouble(Option<f64>),
    OptionBool(Option<bool>),
    OptionString(Option<String>),
    OptionBytes(Option<Vec<u8>>),
    OptionUuid(Option<Uuid>),
    OptionDateTime(Option<DateTime<Utc>>),
    OptionNaiveDateTime(Option<NaiveDateTime>),
    OptionNaiveDate(Option<NaiveDate>),
    OptionNaiveTime(Option<NaiveTime>),
    OptionJson(Option<serde_json::Value>),
    OptionJsonb(Option<serde_json::Value>),
}

// ── From<T> implementations for non-nullable types ──

impl From<i16> for Value {
    fn from(v: i16) -> Self { Value::Short(v) }
}

impl From<i32> for Value {
    fn from(v: i32) -> Self { Value::Int(v) }
}

impl From<i64> for Value {
    fn from(v: i64) -> Self { Value::BigInt(v) }
}

impl From<f32> for Value {
    fn from(v: f32) -> Self { Value::Real(v) }
}

impl From<f64> for Value {
    fn from(v: f64) -> Self { Value::Double(v) }
}

impl From<bool> for Value {
    fn from(v: bool) -> Self { Value::Bool(v) }
}

impl From<String> for Value {
    fn from(v: String) -> Self { Value::String(v) }
}

impl From<&str> for Value {
    fn from(v: &str) -> Self { Value::String(v.to_owned()) }
}

impl From<Vec<u8>> for Value {
    fn from(v: Vec<u8>) -> Self { Value::Bytes(v) }
}

impl From<Uuid> for Value {
    fn from(v: Uuid) -> Self { Value::Uuid(v) }
}

impl From<DateTime<Utc>> for Value {
    fn from(v: DateTime<Utc>) -> Self { Value::DateTime(v) }
}

impl From<NaiveDateTime> for Value {
    fn from(v: NaiveDateTime) -> Self { Value::NaiveDateTime(v) }
}

impl From<NaiveDate> for Value {
    fn from(v: NaiveDate) -> Self { Value::NaiveDate(v) }
}

impl From<NaiveTime> for Value {
    fn from(v: NaiveTime) -> Self { Value::NaiveTime(v) }
}

// ── From<Option<T>> implementations for nullable types ──

impl From<Option<i16>> for Value {
    fn from(v: Option<i16>) -> Self { Value::OptionShort(v) }
}

impl From<Option<i32>> for Value {
    fn from(v: Option<i32>) -> Self { Value::OptionInt(v) }
}

impl From<Option<i64>> for Value {
    fn from(v: Option<i64>) -> Self { Value::OptionBigInt(v) }
}

impl From<Option<f32>> for Value {
    fn from(v: Option<f32>) -> Self { Value::OptionReal(v) }
}

impl From<Option<f64>> for Value {
    fn from(v: Option<f64>) -> Self { Value::OptionDouble(v) }
}

impl From<Option<bool>> for Value {
    fn from(v: Option<bool>) -> Self { Value::OptionBool(v) }
}

impl From<Option<String>> for Value {
    fn from(v: Option<String>) -> Self { Value::OptionString(v) }
}

impl From<Option<Vec<u8>>> for Value {
    fn from(v: Option<Vec<u8>>) -> Self { Value::OptionBytes(v) }
}

impl From<Option<Uuid>> for Value {
    fn from(v: Option<Uuid>) -> Self { Value::OptionUuid(v) }
}

impl From<Option<DateTime<Utc>>> for Value {
    fn from(v: Option<DateTime<Utc>>) -> Self { Value::OptionDateTime(v) }
}

impl From<Option<NaiveDateTime>> for Value {
    fn from(v: Option<NaiveDateTime>) -> Self { Value::OptionNaiveDateTime(v) }
}

impl From<Option<NaiveDate>> for Value {
    fn from(v: Option<NaiveDate>) -> Self { Value::OptionNaiveDate(v) }
}

impl From<Option<NaiveTime>> for Value {
    fn from(v: Option<NaiveTime>) -> Self { Value::OptionNaiveTime(v) }
}

// ── Tests ──

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn value_from_i16() {
        assert!(matches!(Value::from(42i16), Value::Short(42)));
    }

    #[test]
    fn value_from_i32() {
        assert!(matches!(Value::from(42i32), Value::Int(42)));
    }

    #[test]
    fn value_from_i64() {
        assert!(matches!(Value::from(42i64), Value::BigInt(42)));
    }

    #[test]
    fn value_from_f32() {
        let v = Value::from(3.14f32);
        assert!(matches!(v, Value::Real(_)));
    }

    #[test]
    fn value_from_f64() {
        let v = Value::from(3.14f64);
        assert!(matches!(v, Value::Double(_)));
    }

    #[test]
    fn value_from_bool() {
        assert!(matches!(Value::from(true), Value::Bool(true)));
        assert!(matches!(Value::from(false), Value::Bool(false)));
    }

    #[test]
    fn value_from_string() {
        let v = Value::from("hello".to_string());
        assert!(matches!(v, Value::String(ref s) if s == "hello"));
    }

    #[test]
    fn value_from_str() {
        let v = Value::from("hello");
        assert!(matches!(v, Value::String(ref s) if s == "hello"));
    }

    #[test]
    fn value_from_uuid() {
        let id = Uuid::new_v4();
        let v = Value::from(id);
        assert!(matches!(v, Value::Uuid(_)));
    }

    #[test]
    fn value_from_bytes() {
        let v = Value::from(vec![1u8, 2, 3]);
        assert!(matches!(v, Value::Bytes(ref b) if b.len() == 3));
    }

    // ── Nullable type tests ──

    #[test]
    fn value_from_none_i32() {
        let v = Value::from(None::<i32>);
        assert!(matches!(v, Value::OptionInt(None)));
    }

    #[test]
    fn value_from_some_i32() {
        let v = Value::from(Some(42i32));
        assert!(matches!(v, Value::OptionInt(Some(42))));
    }

    #[test]
    fn value_from_none_string() {
        let v = Value::from(None::<String>);
        assert!(matches!(v, Value::OptionString(None)));
    }

    #[test]
    fn value_from_some_string() {
        let v = Value::from(Some("hi".to_string()));
        assert!(matches!(v, Value::OptionString(Some(ref s)) if s == "hi"));
    }

    #[test]
    fn value_from_none_uuid() {
        let v = Value::from(None::<Uuid>);
        assert!(matches!(v, Value::OptionUuid(None)));
    }

    #[test]
    fn value_from_some_uuid() {
        let id = Uuid::new_v4();
        let v = Value::from(Some(id));
        assert!(matches!(v, Value::OptionUuid(Some(_))));
    }

    #[test]
    fn value_from_none_bool() {
        let v = Value::from(None::<bool>);
        assert!(matches!(v, Value::OptionBool(None)));
    }

    #[test]
    fn value_from_none_datetime() {
        let v = Value::from(None::<DateTime<Utc>>);
        assert!(matches!(v, Value::OptionDateTime(None)));
    }

    #[test]
    fn value_from_none_naive_date() {
        let v = Value::from(None::<NaiveDate>);
        assert!(matches!(v, Value::OptionNaiveDate(None)));
    }

    #[test]
    fn value_from_none_naive_time() {
        let v = Value::from(None::<NaiveTime>);
        assert!(matches!(v, Value::OptionNaiveTime(None)));
    }

    #[test]
    fn value_from_none_bytes() {
        let v = Value::from(None::<Vec<u8>>);
        assert!(matches!(v, Value::OptionBytes(None)));
    }

    // ── Equality tests ──

    #[test]
    fn value_equality() {
        assert_eq!(Value::Int(42), Value::Int(42));
        assert_ne!(Value::Int(42), Value::Int(43));
        assert_ne!(Value::Int(42), Value::Short(42));
    }

    #[test]
    fn value_option_equality() {
        assert_eq!(Value::OptionInt(Some(1)), Value::OptionInt(Some(1)));
        assert_eq!(Value::OptionInt(None), Value::OptionInt(None));
        assert_ne!(Value::OptionInt(None), Value::OptionString(None));
    }
}