Skip to main content

keelson_core/
value.rs

1use std::any::Any;
2use std::fmt;
3use std::sync::Arc;
4
5use serde::ser::{SerializeSeq, Serializer};
6
7use crate::error::{Error, Result};
8
9/// A bound argument.
10///
11/// keelson carries its own value enum rather than being generic over a driver's
12/// parameter type. That keeps [`Expression`](crate::Expression) free of any
13/// backend type parameter, and it means a built query's arguments can be
14/// inspected — printed, compared, serialised — without a database in the loop.
15#[derive(Debug, Clone)]
16#[non_exhaustive]
17pub enum Value {
18    /// SQL `NULL`.
19    Null,
20    /// A boolean.
21    Bool(bool),
22    /// An 8-bit signed integer.
23    I8(i8),
24    /// A 16-bit signed integer.
25    I16(i16),
26    /// A 32-bit signed integer.
27    I32(i32),
28    /// A 64-bit signed integer.
29    I64(i64),
30    /// An 8-bit unsigned integer.
31    U8(u8),
32    /// A 16-bit unsigned integer.
33    U16(u16),
34    /// A 32-bit unsigned integer.
35    U32(u32),
36    /// A 64-bit unsigned integer.
37    U64(u64),
38    /// A single-precision float.
39    F32(f32),
40    /// A double-precision float.
41    F64(f64),
42    /// Character data.
43    Text(String),
44    /// Binary data — `BYTEA`, `BLOB`.
45    Bytes(Vec<u8>),
46    /// A homogeneous array, for dialects that have one (PostgreSQL).
47    Array(Vec<Value>),
48    /// A calendar date with no time and no zone — `DATE`.
49    #[cfg(feature = "chrono")]
50    Date(chrono::NaiveDate),
51    /// A wall-clock time with no date and no zone — `TIME`.
52    #[cfg(feature = "chrono")]
53    Time(chrono::NaiveTime),
54    /// A date and time with no zone — `TIMESTAMP` / `DATETIME`. What it means
55    /// depends on context the database never sees, which is why it is a
56    /// different variant from [`Value::TimestampTz`], not a special case of it.
57    #[cfg(feature = "chrono")]
58    DateTime(chrono::NaiveDateTime),
59    /// An instant, carried in UTC — `TIMESTAMPTZ`.
60    ///
61    /// There is deliberately no offset-preserving variant: every zoned
62    /// `chrono::DateTime<Tz>` is normalised to UTC on conversion, because no
63    /// target database round-trips an offset (PostgreSQL's `timestamptz`
64    /// stores UTC and renders in the session zone; MySQL's `TIMESTAMP`
65    /// converts through the session zone; SQLite has no zone at all). A
66    /// variant that pretended otherwise would promise what no backend keeps.
67    #[cfg(feature = "chrono")]
68    TimestampTz(chrono::DateTime<chrono::Utc>),
69    /// A UUID — `uuid` on PostgreSQL, hyphenated text elsewhere.
70    #[cfg(feature = "uuid")]
71    Uuid(uuid::Uuid),
72    /// An exact decimal number — `NUMERIC` / `DECIMAL`. A separate variant
73    /// from the floats because binary floating point cannot represent decimal
74    /// scale, which is the entire reason an application reaches for `Decimal`.
75    #[cfg(feature = "decimal")]
76    Decimal(rust_decimal::Decimal),
77    /// A JSON document — `jsonb` / `JSON`, serialised text elsewhere.
78    #[cfg(feature = "json")]
79    Json(serde_json::Value),
80    /// Escape hatch for dialect-specific types. Backends downcast through
81    /// [`CustomValue::as_any`].
82    Custom(Arc<dyn CustomValue>),
83}
84
85/// A dialect-specific value that keelson itself never interprets.
86///
87/// Implementors are carried through the builder untouched and handed to the
88/// backend, which recovers the concrete type with [`Self::as_any`]. This is where
89/// genuinely dialect-specific types live — PostgreSQL ranges, geometric types
90/// and the like. The types nearly every application binds (`chrono`, `uuid`,
91/// `rust_decimal`, `serde_json`) have first-class feature-gated variants
92/// instead, with their mappings recorded in `docs/type-mappings.md`.
93pub trait CustomValue: fmt::Debug + Send + Sync + 'static {
94    /// The name used in error messages and `Debug` output.
95    fn type_name(&self) -> &'static str;
96
97    /// For downcasting in a backend adapter.
98    fn as_any(&self) -> &dyn Any;
99
100    /// A plain stand-in used when the argument list is serialised, e.g. by a
101    /// logger or a golden test. Returning another [`Value::Custom`] serialises
102    /// as `null`; there is no recursion.
103    fn to_plain(&self) -> Value {
104        Value::Null
105    }
106}
107
108impl Value {
109    /// Build a [`Value::Array`] from anything iterable.
110    ///
111    /// There is deliberately no blanket `ToValue for Vec<T>`: it would collide
112    /// with `Vec<u8>`, which must stay [`Value::Bytes`] so `BYTEA`/`BLOB` binds
113    /// correctly. Arrays are therefore explicit.
114    pub fn array<T: ToValue, I: IntoIterator<Item = T>>(items: I) -> Value {
115        Value::Array(items.into_iter().map(ToValue::to_value).collect())
116    }
117
118    /// Wrap a dialect-specific value.
119    pub fn custom<C: CustomValue>(value: C) -> Value {
120        Value::Custom(Arc::new(value))
121    }
122
123    /// Whether this is [`Value::Null`].
124    pub fn is_null(&self) -> bool {
125        matches!(self, Value::Null)
126    }
127
128    /// The variant name, for error messages.
129    pub fn type_name(&self) -> &'static str {
130        match self {
131            Value::Null => "NULL",
132            Value::Bool(_) => "bool",
133            Value::I8(_) => "i8",
134            Value::I16(_) => "i16",
135            Value::I32(_) => "i32",
136            Value::I64(_) => "i64",
137            Value::U8(_) => "u8",
138            Value::U16(_) => "u16",
139            Value::U32(_) => "u32",
140            Value::U64(_) => "u64",
141            Value::F32(_) => "f32",
142            Value::F64(_) => "f64",
143            Value::Text(_) => "text",
144            Value::Bytes(_) => "bytes",
145            Value::Array(_) => "array",
146            #[cfg(feature = "chrono")]
147            Value::Date(_) => "date",
148            #[cfg(feature = "chrono")]
149            Value::Time(_) => "time",
150            #[cfg(feature = "chrono")]
151            Value::DateTime(_) => "datetime",
152            #[cfg(feature = "chrono")]
153            Value::TimestampTz(_) => "timestamptz",
154            #[cfg(feature = "uuid")]
155            Value::Uuid(_) => "uuid",
156            #[cfg(feature = "decimal")]
157            Value::Decimal(_) => "decimal",
158            #[cfg(feature = "json")]
159            Value::Json(_) => "json",
160            Value::Custom(c) => c.type_name(),
161        }
162    }
163}
164
165impl PartialEq for Value {
166    fn eq(&self, other: &Self) -> bool {
167        use Value::*;
168        match (self, other) {
169            (Null, Null) => true,
170            (Bool(a), Bool(b)) => a == b,
171            (I8(a), I8(b)) => a == b,
172            (I16(a), I16(b)) => a == b,
173            (I32(a), I32(b)) => a == b,
174            (I64(a), I64(b)) => a == b,
175            (U8(a), U8(b)) => a == b,
176            (U16(a), U16(b)) => a == b,
177            (U32(a), U32(b)) => a == b,
178            (U64(a), U64(b)) => a == b,
179            (F32(a), F32(b)) => a == b,
180            (F64(a), F64(b)) => a == b,
181            (Text(a), Text(b)) => a == b,
182            (Bytes(a), Bytes(b)) => a == b,
183            (Array(a), Array(b)) => a == b,
184            #[cfg(feature = "chrono")]
185            (Date(a), Date(b)) => a == b,
186            #[cfg(feature = "chrono")]
187            (Time(a), Time(b)) => a == b,
188            #[cfg(feature = "chrono")]
189            (DateTime(a), DateTime(b)) => a == b,
190            #[cfg(feature = "chrono")]
191            (TimestampTz(a), TimestampTz(b)) => a == b,
192            #[cfg(feature = "uuid")]
193            (Uuid(a), Uuid(b)) => a == b,
194            // rust_decimal compares numerically, so `1.10 == 1.100` here even
195            // though the two serialise differently. That is the right call for
196            // an argument list: the database would treat them as equal too.
197            #[cfg(feature = "decimal")]
198            (Decimal(a), Decimal(b)) => a == b,
199            #[cfg(feature = "json")]
200            (Json(a), Json(b)) => a == b,
201            // Custom values have no shared notion of equality, so identity is
202            // the only honest answer.
203            (Custom(a), Custom(b)) => Arc::ptr_eq(a, b),
204            _ => false,
205        }
206    }
207}
208
209/// `Value` serialises as the underlying scalar, never as a tagged enum.
210///
211/// This is what makes `Vec<Value>` comparable against a plain JSON array of
212/// arguments — `Value::I32(100)` becomes `100`, not `{"I32":100}` — which is the
213/// shape the test suite compares against.
214impl serde::Serialize for Value {
215    fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
216        match self {
217            Value::Null => s.serialize_none(),
218            Value::Bool(v) => s.serialize_bool(*v),
219            Value::I8(v) => s.serialize_i8(*v),
220            Value::I16(v) => s.serialize_i16(*v),
221            Value::I32(v) => s.serialize_i32(*v),
222            Value::I64(v) => s.serialize_i64(*v),
223            Value::U8(v) => s.serialize_u8(*v),
224            Value::U16(v) => s.serialize_u16(*v),
225            Value::U32(v) => s.serialize_u32(*v),
226            Value::U64(v) => s.serialize_u64(*v),
227            Value::F32(v) => s.serialize_f32(*v),
228            Value::F64(v) => s.serialize_f64(*v),
229            Value::Text(v) => s.serialize_str(v),
230            Value::Bytes(v) => s.serialize_bytes(v),
231            Value::Array(items) => {
232                let mut seq = s.serialize_seq(Some(items.len()))?;
233                for item in items {
234                    seq.serialize_element(item)?;
235                }
236                seq.end()
237            }
238            // The temporal types serialise as ISO 8601 strings — the one
239            // rendering every dialect, log reader and JSON consumer agrees on.
240            // Fractional seconds appear only when non-zero, in 3/6/9-digit
241            // groups, so a whole-second timestamp stays short. The exact forms
242            // are pinned in docs/type-mappings.md and by test.
243            #[cfg(feature = "chrono")]
244            Value::Date(v) => s.collect_str(&v.format("%Y-%m-%d")),
245            #[cfg(feature = "chrono")]
246            Value::Time(v) => s.collect_str(&v.format("%H:%M:%S%.f")),
247            #[cfg(feature = "chrono")]
248            Value::DateTime(v) => s.collect_str(&v.format("%Y-%m-%dT%H:%M:%S%.f")),
249            #[cfg(feature = "chrono")]
250            Value::TimestampTz(v) => {
251                s.collect_str(&v.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true))
252            }
253            // Hyphenated lowercase — the RFC 9562 text form.
254            #[cfg(feature = "uuid")]
255            Value::Uuid(v) => s.collect_str(v),
256            // A string, never a JSON number: `1.10` as a float would collapse
257            // to `1.1` (or worse), and exactness is what `Decimal` is for.
258            #[cfg(feature = "decimal")]
259            Value::Decimal(v) => s.collect_str(v),
260            // Structural passthrough, like `Array` — the document itself, not a
261            // string containing it.
262            #[cfg(feature = "json")]
263            Value::Json(v) => v.serialize(s),
264            Value::Custom(c) => match c.to_plain() {
265                Value::Custom(_) => s.serialize_none(),
266                plain => plain.serialize(s),
267            },
268        }
269    }
270}
271
272/// Conversion into a bound argument.
273pub trait ToValue {
274    /// Consume `self` and produce the argument to bind.
275    fn to_value(self) -> Value;
276}
277
278impl ToValue for Value {
279    fn to_value(self) -> Value {
280        self
281    }
282}
283
284/// `None` binds as SQL `NULL`.
285impl<T: ToValue> ToValue for Option<T> {
286    fn to_value(self) -> Value {
287        match self {
288            Some(v) => v.to_value(),
289            None => Value::Null,
290        }
291    }
292}
293
294macro_rules! to_value_direct {
295    ($($t:ty => $variant:ident),* $(,)?) => { $(
296        impl ToValue for $t {
297            fn to_value(self) -> Value {
298                Value::$variant(self)
299            }
300        }
301    )* };
302}
303
304to_value_direct! {
305    bool => Bool,
306    i8 => I8, i16 => I16, i32 => I32, i64 => I64,
307    u8 => U8, u16 => U16, u32 => U32, u64 => U64,
308    f32 => F32, f64 => F64,
309    String => Text,
310    Vec<u8> => Bytes,
311}
312
313impl ToValue for &str {
314    fn to_value(self) -> Value {
315        Value::Text(self.to_owned())
316    }
317}
318
319impl ToValue for &String {
320    fn to_value(self) -> Value {
321        Value::Text(self.clone())
322    }
323}
324
325impl ToValue for std::borrow::Cow<'_, str> {
326    fn to_value(self) -> Value {
327        Value::Text(self.into_owned())
328    }
329}
330
331impl ToValue for &[u8] {
332    fn to_value(self) -> Value {
333        Value::Bytes(self.to_vec())
334    }
335}
336
337// Pointer-width integers are normalised so a backend never has to branch on the
338// host architecture.
339impl ToValue for isize {
340    fn to_value(self) -> Value {
341        Value::I64(self as i64)
342    }
343}
344
345impl ToValue for usize {
346    fn to_value(self) -> Value {
347        Value::U64(self as u64)
348    }
349}
350
351/// The unit type binds as `NULL`, so `push_arg(())` needs no ceremony.
352impl ToValue for () {
353    fn to_value(self) -> Value {
354        Value::Null
355    }
356}
357
358impl<T: CustomValue> ToValue for Arc<T> {
359    fn to_value(self) -> Value {
360        Value::Custom(self)
361    }
362}
363
364#[cfg(feature = "chrono")]
365mod chrono_impls {
366    use super::{FromValue, ToValue, Value};
367    use crate::error::{Error, Result};
368    use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
369
370    impl ToValue for NaiveDate {
371        fn to_value(self) -> Value {
372            Value::Date(self)
373        }
374    }
375
376    impl ToValue for NaiveTime {
377        fn to_value(self) -> Value {
378            Value::Time(self)
379        }
380    }
381
382    impl ToValue for NaiveDateTime {
383        fn to_value(self) -> Value {
384            Value::DateTime(self)
385        }
386    }
387
388    /// Any zoned datetime — `Utc`, `FixedOffset`, `Local` — binds as the
389    /// instant it names, normalised to UTC. The offset is dropped because no
390    /// target database stores one; an application that needs the original
391    /// offset keeps it in its own column.
392    impl<Tz: TimeZone> ToValue for DateTime<Tz> {
393        fn to_value(self) -> Value {
394            Value::TimestampTz(self.with_timezone(&Utc))
395        }
396    }
397
398    // Reading back accepts the matching variant or its ISO 8601 text form,
399    // because SQLite has no temporal storage class at all and MySQL drivers
400    // routinely hand temporal columns back as text. The text forms accepted
401    // are exactly the ones `Value` serialises to (docs/type-mappings.md),
402    // plus the space-separated datetime that SQLite and MySQL conventionally
403    // store, so a value written through keelson always reads back.
404
405    impl FromValue for NaiveDate {
406        fn from_value(v: Value) -> Result<Self> {
407            let found = v.type_name();
408            match v {
409                Value::Date(d) => Ok(d),
410                Value::Text(s) => s
411                    .parse()
412                    .map_err(|_| Error::type_mismatch("NaiveDate", found)),
413                _ => Err(Error::type_mismatch("NaiveDate", found)),
414            }
415        }
416    }
417
418    impl FromValue for NaiveTime {
419        fn from_value(v: Value) -> Result<Self> {
420            let found = v.type_name();
421            match v {
422                Value::Time(t) => Ok(t),
423                Value::Text(s) => s
424                    .parse()
425                    .map_err(|_| Error::type_mismatch("NaiveTime", found)),
426                _ => Err(Error::type_mismatch("NaiveTime", found)),
427            }
428        }
429    }
430
431    impl FromValue for NaiveDateTime {
432        fn from_value(v: Value) -> Result<Self> {
433            let found = v.type_name();
434            match v {
435                Value::DateTime(dt) => Ok(dt),
436                Value::Text(s) => s
437                    .parse()
438                    .or_else(|_| NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S%.f"))
439                    .map_err(|_| Error::type_mismatch("NaiveDateTime", found)),
440                _ => Err(Error::type_mismatch("NaiveDateTime", found)),
441            }
442        }
443    }
444
445    impl FromValue for DateTime<Utc> {
446        fn from_value(v: Value) -> Result<Self> {
447            let found = v.type_name();
448            match v {
449                Value::TimestampTz(dt) => Ok(dt),
450                Value::Text(s) => DateTime::parse_from_rfc3339(&s)
451                    .map(|dt| dt.with_timezone(&Utc))
452                    .map_err(|_| Error::type_mismatch("DateTime<Utc>", found)),
453                _ => Err(Error::type_mismatch("DateTime<Utc>", found)),
454            }
455        }
456    }
457}
458
459#[cfg(feature = "uuid")]
460mod uuid_impls {
461    use super::{FromValue, ToValue, Value};
462    use crate::error::{Error, Result};
463    use uuid::Uuid;
464
465    impl ToValue for Uuid {
466        fn to_value(self) -> Value {
467            Value::Uuid(self)
468        }
469    }
470
471    impl FromValue for Uuid {
472        fn from_value(v: Value) -> Result<Self> {
473            let found = v.type_name();
474            match v {
475                Value::Uuid(u) => Ok(u),
476                // Text covers the standard MySQL/SQLite mapping (`CHAR(36)` /
477                // `TEXT`); 16 raw bytes covers a `BINARY(16)`/`BLOB` column an
478                // application chose for compactness.
479                Value::Text(s) => {
480                    Uuid::parse_str(&s).map_err(|_| Error::type_mismatch("Uuid", found))
481                }
482                Value::Bytes(b) => {
483                    Uuid::from_slice(&b).map_err(|_| Error::type_mismatch("Uuid", found))
484                }
485                _ => Err(Error::type_mismatch("Uuid", found)),
486            }
487        }
488    }
489}
490
491#[cfg(feature = "decimal")]
492mod decimal_impls {
493    use super::{FromValue, ToValue, Value};
494    use crate::error::{Error, Result};
495    use rust_decimal::Decimal;
496
497    impl ToValue for Decimal {
498        fn to_value(self) -> Value {
499            Value::Decimal(self)
500        }
501    }
502
503    impl FromValue for Decimal {
504        fn from_value(v: Value) -> Result<Self> {
505            let found = v.type_name();
506            // Text covers drivers that hand `NUMERIC` back as a string (the
507            // lossless wire form) and the SQLite `TEXT` mapping; integers are
508            // exact so they widen in. Floats are deliberately rejected: a
509            // binary fraction has no faithful decimal scale, and inventing one
510            // silently is the bug `Decimal` exists to prevent.
511            match v {
512                Value::Decimal(d) => Ok(d),
513                Value::Text(s) => s
514                    .parse()
515                    .map_err(|_| Error::type_mismatch("Decimal", found)),
516                Value::I8(x) => Ok(Decimal::from(x)),
517                Value::I16(x) => Ok(Decimal::from(x)),
518                Value::I32(x) => Ok(Decimal::from(x)),
519                Value::I64(x) => Ok(Decimal::from(x)),
520                Value::U8(x) => Ok(Decimal::from(x)),
521                Value::U16(x) => Ok(Decimal::from(x)),
522                Value::U32(x) => Ok(Decimal::from(x)),
523                Value::U64(x) => Ok(Decimal::from(x)),
524                _ => Err(Error::type_mismatch("Decimal", found)),
525            }
526        }
527    }
528}
529
530#[cfg(feature = "json")]
531mod json_impls {
532    use super::{FromValue, ToValue, Value};
533    use crate::error::{Error, Result};
534
535    impl ToValue for serde_json::Value {
536        fn to_value(self) -> Value {
537            Value::Json(self)
538        }
539    }
540
541    impl FromValue for serde_json::Value {
542        fn from_value(v: Value) -> Result<Self> {
543            let found = v.type_name();
544            match v {
545                Value::Json(j) => Ok(j),
546                // Every dialect's JSON type comes off the wire as text in at
547                // least one driver, so parseable text reads as the document.
548                Value::Text(s) => serde_json::from_str(&s)
549                    .map_err(|_| Error::type_mismatch("serde_json::Value", found)),
550                _ => Err(Error::type_mismatch("serde_json::Value", found)),
551            }
552        }
553    }
554}
555
556/// Conversion out of a value read back from the database.
557pub trait FromValue: Sized {
558    /// Consume a [`Value`] and produce `Self`, or explain why not.
559    fn from_value(v: Value) -> Result<Self>;
560}
561
562impl FromValue for Value {
563    fn from_value(v: Value) -> Result<Self> {
564        Ok(v)
565    }
566}
567
568/// `NULL` reads as `None`; anything else delegates to `T`.
569impl<T: FromValue> FromValue for Option<T> {
570    fn from_value(v: Value) -> Result<Self> {
571        match v {
572            Value::Null => Ok(None),
573            other => T::from_value(other).map(Some),
574        }
575    }
576}
577
578macro_rules! from_value_int {
579    ($($t:ty),* $(,)?) => { $(
580        impl FromValue for $t {
581            fn from_value(v: Value) -> Result<Self> {
582                let found = v.type_name();
583                // A driver is free to hand back any integer width, so widening
584                // is accepted and only a genuine overflow is an error.
585                let converted = match v {
586                    Value::I8(x) => <$t>::try_from(x).ok(),
587                    Value::I16(x) => <$t>::try_from(x).ok(),
588                    Value::I32(x) => <$t>::try_from(x).ok(),
589                    Value::I64(x) => <$t>::try_from(x).ok(),
590                    Value::U8(x) => <$t>::try_from(x).ok(),
591                    Value::U16(x) => <$t>::try_from(x).ok(),
592                    Value::U32(x) => <$t>::try_from(x).ok(),
593                    Value::U64(x) => <$t>::try_from(x).ok(),
594                    _ => return Err(Error::type_mismatch(stringify!($t), found)),
595                };
596                converted.ok_or(Error::type_mismatch(stringify!($t), found))
597            }
598        }
599    )* };
600}
601
602from_value_int!(i8, i16, i32, i64, u8, u16, u32, u64);
603
604macro_rules! from_value_float {
605    ($($t:ty),* $(,)?) => { $(
606        impl FromValue for $t {
607            #[allow(clippy::cast_lossless, clippy::cast_precision_loss)]
608            fn from_value(v: Value) -> Result<Self> {
609                let found = v.type_name();
610                match v {
611                    Value::F32(x) => Ok(x as $t),
612                    Value::F64(x) => Ok(x as $t),
613                    Value::I8(x) => Ok(x as $t),
614                    Value::I16(x) => Ok(x as $t),
615                    Value::I32(x) => Ok(x as $t),
616                    Value::I64(x) => Ok(x as $t),
617                    Value::U8(x) => Ok(x as $t),
618                    Value::U16(x) => Ok(x as $t),
619                    Value::U32(x) => Ok(x as $t),
620                    Value::U64(x) => Ok(x as $t),
621                    _ => Err(Error::type_mismatch(stringify!($t), found)),
622                }
623            }
624        }
625    )* };
626}
627
628from_value_float!(f32, f64);
629
630impl FromValue for bool {
631    fn from_value(v: Value) -> Result<Self> {
632        match v {
633            Value::Bool(b) => Ok(b),
634            other => Err(Error::type_mismatch("bool", other.type_name())),
635        }
636    }
637}
638
639impl FromValue for String {
640    fn from_value(v: Value) -> Result<Self> {
641        match v {
642            Value::Text(s) => Ok(s),
643            other => Err(Error::type_mismatch("String", other.type_name())),
644        }
645    }
646}
647
648impl FromValue for Vec<u8> {
649    fn from_value(v: Value) -> Result<Self> {
650        match v {
651            Value::Bytes(b) => Ok(b),
652            // MySQL hands back character columns as bytes and vice versa, so
653            // this direction is always safe.
654            Value::Text(s) => Ok(s.into_bytes()),
655            other => Err(Error::type_mismatch("Vec<u8>", other.type_name())),
656        }
657    }
658}
659
660/// Read a [`Value::Array`] element-wise.
661///
662/// A free function rather than `impl FromValue for Vec<T>`, which would collide
663/// with `Vec<u8>` = [`Value::Bytes`]. Same asymmetry as [`Value::array`], for the
664/// same reason.
665pub fn from_value_array<T: FromValue>(v: Value) -> Result<Vec<T>> {
666    match v {
667        Value::Array(items) => items.into_iter().map(T::from_value).collect(),
668        other => Err(Error::type_mismatch("array", other.type_name())),
669    }
670}
671
672#[cfg(test)]
673mod tests {
674    use super::*;
675
676    #[derive(Debug)]
677    struct Point(i32, i32);
678
679    impl CustomValue for Point {
680        fn type_name(&self) -> &'static str {
681            "point"
682        }
683
684        fn as_any(&self) -> &dyn Any {
685            self
686        }
687
688        fn to_plain(&self) -> Value {
689            Value::Text(format!("({},{})", self.0, self.1))
690        }
691    }
692
693    #[derive(Debug)]
694    struct Opaque;
695
696    impl CustomValue for Opaque {
697        fn type_name(&self) -> &'static str {
698            "opaque"
699        }
700
701        fn as_any(&self) -> &dyn Any {
702            self
703        }
704    }
705
706    fn json(v: Value) -> serde_json::Value {
707        serde_json::to_value(v).expect("Value must serialise")
708    }
709
710    #[test]
711    fn serialises_as_the_bare_scalar_not_a_tagged_variant() {
712        assert_eq!(json(Value::I32(100)), serde_json::json!(100));
713        assert_eq!(json(Value::I64(-7)), serde_json::json!(-7));
714        assert_eq!(json(Value::U8(3)), serde_json::json!(3));
715        assert_eq!(json(Value::Text("100".into())), serde_json::json!("100"));
716        assert_eq!(json(Value::Bool(true)), serde_json::json!(true));
717        assert_eq!(json(Value::F64(1.5)), serde_json::json!(1.5));
718        assert_eq!(json(Value::Null), serde_json::Value::Null);
719    }
720
721    #[test]
722    fn serialises_a_whole_arg_list_as_a_plain_json_array() {
723        // This is exactly the shape the test suite compares against.
724        let args = vec![Value::I32(100), Value::Text("Stephen".into())];
725        assert_eq!(
726            serde_json::to_value(&args).unwrap(),
727            serde_json::json!([100, "Stephen"])
728        );
729    }
730
731    #[test]
732    fn serialises_arrays_and_bytes_structurally() {
733        assert_eq!(
734            json(Value::array([1i32, 2, 3])),
735            serde_json::json!([1, 2, 3])
736        );
737        assert_eq!(json(Value::Bytes(vec![1, 2])), serde_json::json!([1, 2]));
738    }
739
740    #[test]
741    fn custom_values_serialise_through_their_plain_form() {
742        assert_eq!(json(Value::custom(Point(1, 2))), serde_json::json!("(1,2)"));
743        assert_eq!(json(Value::custom(Opaque)), serde_json::Value::Null);
744    }
745
746    #[test]
747    fn custom_values_are_downcastable_by_a_backend() {
748        let v = Value::custom(Point(3, 4));
749        let Value::Custom(c) = &v else {
750            panic!("expected a custom value");
751        };
752        let p = c.as_any().downcast_ref::<Point>().expect("downcast");
753        assert_eq!((p.0, p.1), (3, 4));
754        assert_eq!(v.type_name(), "point");
755    }
756
757    #[test]
758    fn option_none_binds_as_null() {
759        assert_eq!(None::<i32>.to_value(), Value::Null);
760        assert_eq!(Some(4i32).to_value(), Value::I32(4));
761        assert_eq!(Some("a").to_value(), Value::Text("a".into()));
762        assert!(None::<String>.to_value().is_null());
763    }
764
765    #[test]
766    fn to_value_covers_the_obvious_primitives() {
767        assert_eq!(true.to_value(), Value::Bool(true));
768        assert_eq!(1i16.to_value(), Value::I16(1));
769        assert_eq!(1u32.to_value(), Value::U32(1));
770        assert_eq!(1.5f32.to_value(), Value::F32(1.5));
771        assert_eq!("x".to_value(), Value::Text("x".into()));
772        assert_eq!(String::from("x").to_value(), Value::Text("x".into()));
773        assert_eq!(
774            std::borrow::Cow::Borrowed("x").to_value(),
775            Value::Text("x".into())
776        );
777        assert_eq!(vec![1u8, 2].to_value(), Value::Bytes(vec![1, 2]));
778        assert_eq!(9usize.to_value(), Value::U64(9));
779        assert_eq!((-9isize).to_value(), Value::I64(-9));
780        assert_eq!(().to_value(), Value::Null);
781        assert_eq!(Value::I32(1).to_value(), Value::I32(1));
782    }
783
784    #[test]
785    fn from_value_widens_and_rejects_overflow() {
786        assert_eq!(i64::from_value(Value::I32(5)).unwrap(), 5);
787        assert_eq!(u8::from_value(Value::I64(200)).unwrap(), 200);
788        assert!(u8::from_value(Value::I64(300)).is_err());
789        assert!(i32::from_value(Value::Text("3".into())).is_err());
790        assert_eq!(f64::from_value(Value::I32(2)).unwrap(), 2.0);
791        assert!(bool::from_value(Value::Bool(false)).unwrap().eq(&false));
792        assert_eq!(String::from_value(Value::Text("s".into())).unwrap(), "s");
793        assert_eq!(Option::<i32>::from_value(Value::Null).unwrap(), None);
794        assert_eq!(
795            from_value_array::<i32>(Value::array([1i32, 2])).unwrap(),
796            vec![1, 2]
797        );
798        assert!(from_value_array::<i32>(Value::I32(1)).is_err());
799    }
800
801    #[test]
802    fn type_mismatch_explains_both_sides() {
803        let e = i32::from_value(Value::Text("3".into())).unwrap_err();
804        assert_eq!(e.to_string(), "cannot read text as i32");
805    }
806
807    // Expected strings below are the ISO 8601 / RFC 3339 / RFC 9562 text forms
808    // pinned in docs/type-mappings.md, written out by hand — not copied from
809    // output.
810
811    #[cfg(feature = "chrono")]
812    mod chrono_values {
813        use super::*;
814        use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
815
816        fn date() -> NaiveDate {
817            NaiveDate::from_ymd_opt(2026, 7, 30).unwrap()
818        }
819
820        fn time() -> NaiveTime {
821            NaiveTime::from_hms_opt(12, 34, 56).unwrap()
822        }
823
824        #[test]
825        fn to_value_wraps_each_temporal_type() {
826            assert_eq!(date().to_value(), Value::Date(date()));
827            assert_eq!(time().to_value(), Value::Time(time()));
828            let dt = date().and_time(time());
829            assert_eq!(dt.to_value(), Value::DateTime(dt));
830            let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
831            assert_eq!(utc.to_value(), Value::TimestampTz(utc));
832        }
833
834        #[test]
835        fn zoned_datetimes_normalise_to_utc() {
836            // 21:34:56+09:00 names the same instant as 12:34:56Z.
837            let jst: DateTime<FixedOffset> = "2026-07-30T21:34:56+09:00".parse().unwrap();
838            let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
839            assert_eq!(jst.to_value(), Value::TimestampTz(utc));
840        }
841
842        #[test]
843        fn serialises_as_iso_8601_strings() {
844            assert_eq!(json(date().to_value()), serde_json::json!("2026-07-30"));
845            assert_eq!(json(time().to_value()), serde_json::json!("12:34:56"));
846            assert_eq!(
847                json(date().and_time(time()).to_value()),
848                serde_json::json!("2026-07-30T12:34:56")
849            );
850            let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
851            assert_eq!(
852                json(utc.to_value()),
853                serde_json::json!("2026-07-30T12:34:56Z")
854            );
855        }
856
857        #[test]
858        fn fractional_seconds_appear_only_when_non_zero() {
859            let t = NaiveTime::from_hms_milli_opt(12, 34, 56, 789).unwrap();
860            assert_eq!(json(t.to_value()), serde_json::json!("12:34:56.789"));
861            let dt = date().and_time(t);
862            assert_eq!(
863                json(dt.to_value()),
864                serde_json::json!("2026-07-30T12:34:56.789")
865            );
866            let utc = DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc);
867            assert_eq!(
868                json(utc.to_value()),
869                serde_json::json!("2026-07-30T12:34:56.789Z")
870            );
871        }
872
873        #[test]
874        fn round_trips_from_its_own_variant_and_serialised_text() {
875            let utc = Utc.with_ymd_and_hms(2026, 7, 30, 12, 34, 56).unwrap();
876            assert_eq!(NaiveDate::from_value(date().to_value()).unwrap(), date());
877            assert_eq!(
878                NaiveDate::from_value(Value::Text("2026-07-30".into())).unwrap(),
879                date()
880            );
881            assert_eq!(
882                NaiveTime::from_value(Value::Text("12:34:56".into())).unwrap(),
883                time()
884            );
885            let dt = date().and_time(time());
886            assert_eq!(
887                NaiveDateTime::from_value(Value::Text("2026-07-30T12:34:56".into())).unwrap(),
888                dt
889            );
890            // The space-separated form SQLite and MySQL conventionally store.
891            assert_eq!(
892                NaiveDateTime::from_value(Value::Text("2026-07-30 12:34:56".into())).unwrap(),
893                dt
894            );
895            assert_eq!(DateTime::<Utc>::from_value(utc.to_value()).unwrap(), utc);
896            assert_eq!(
897                DateTime::<Utc>::from_value(Value::Text("2026-07-30T21:34:56+09:00".into()))
898                    .unwrap(),
899                utc
900            );
901            assert!(NaiveDate::from_value(Value::I32(1)).is_err());
902            assert!(NaiveDate::from_value(Value::Text("not a date".into())).is_err());
903        }
904
905        #[test]
906        fn type_names_are_reported() {
907            assert_eq!(date().to_value().type_name(), "date");
908            assert_eq!(time().to_value().type_name(), "time");
909            assert_eq!(date().and_time(time()).to_value().type_name(), "datetime");
910            let utc = Utc.with_ymd_and_hms(2026, 7, 30, 0, 0, 0).unwrap();
911            assert_eq!(utc.to_value().type_name(), "timestamptz");
912        }
913    }
914
915    #[cfg(feature = "uuid")]
916    mod uuid_values {
917        use super::*;
918        use uuid::Uuid;
919
920        const HYPHENATED: &str = "550e8400-e29b-41d4-a716-446655440000";
921
922        #[test]
923        fn binds_serialises_and_round_trips() {
924            let u = Uuid::parse_str(HYPHENATED).unwrap();
925            assert_eq!(u.to_value(), Value::Uuid(u));
926            assert_eq!(u.to_value().type_name(), "uuid");
927            assert_eq!(json(u.to_value()), serde_json::json!(HYPHENATED));
928            assert_eq!(Uuid::from_value(u.to_value()).unwrap(), u);
929            assert_eq!(Uuid::from_value(Value::Text(HYPHENATED.into())).unwrap(), u);
930            assert_eq!(
931                Uuid::from_value(Value::Bytes(u.as_bytes().to_vec())).unwrap(),
932                u
933            );
934            assert!(Uuid::from_value(Value::Bytes(vec![1, 2, 3])).is_err());
935            assert!(Uuid::from_value(Value::I32(1)).is_err());
936        }
937    }
938
939    #[cfg(feature = "decimal")]
940    mod decimal_values {
941        use super::*;
942        use rust_decimal::Decimal;
943
944        #[test]
945        fn binds_serialises_and_round_trips() {
946            // 19.99 with an explicit scale of 2.
947            let d = Decimal::new(1999, 2);
948            assert_eq!(d.to_value(), Value::Decimal(d));
949            assert_eq!(d.to_value().type_name(), "decimal");
950            // A string, never a JSON number — exactness survives any reader.
951            assert_eq!(json(d.to_value()), serde_json::json!("19.99"));
952            assert_eq!(Decimal::from_value(d.to_value()).unwrap(), d);
953            assert_eq!(Decimal::from_value(Value::Text("19.99".into())).unwrap(), d);
954            assert_eq!(
955                Decimal::from_value(Value::I64(7)).unwrap(),
956                Decimal::from(7)
957            );
958            // Floats are rejected: no faithful decimal scale exists for them.
959            assert!(Decimal::from_value(Value::F64(19.99)).is_err());
960        }
961
962        #[test]
963        fn trailing_zeros_survive_serialisation() {
964            // 1.10 keeps scale 2 — `NUMERIC` preserves scale, so keelson does.
965            let d = Decimal::new(110, 2);
966            assert_eq!(json(d.to_value()), serde_json::json!("1.10"));
967            // ...while equality is numeric, like the database's.
968            assert_eq!(d.to_value(), Decimal::new(11, 1).to_value());
969        }
970    }
971
972    #[cfg(feature = "json")]
973    mod json_values {
974        use super::*;
975
976        #[test]
977        fn binds_serialises_structurally_and_round_trips() {
978            let doc = serde_json::json!({"a": [1, 2], "b": "x"});
979            assert_eq!(doc.clone().to_value(), Value::Json(doc.clone()));
980            assert_eq!(doc.clone().to_value().type_name(), "json");
981            // The document itself, not a string containing it.
982            assert_eq!(json(doc.clone().to_value()), doc);
983            assert_eq!(
984                serde_json::Value::from_value(doc.clone().to_value()).unwrap(),
985                doc
986            );
987            assert_eq!(
988                serde_json::Value::from_value(Value::Text(r#"{"a":[1,2],"b":"x"}"#.into()))
989                    .unwrap(),
990                doc
991            );
992            assert!(serde_json::Value::from_value(Value::Text("not json".into())).is_err());
993            assert!(serde_json::Value::from_value(Value::I32(1)).is_err());
994        }
995    }
996}