Skip to main content

nautilus_core/
value.rs

1//! Database value types.
2
3use std::collections::BTreeMap;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8/// Define a transparent `String` newtype with the full set of string
9/// conversions (`new`/`as_str`/`into_inner`, `From<String>`, `From<&str>`,
10/// `AsRef<str>`, `Display`) and `#[serde(transparent)]` serde derives.
11macro_rules! string_newtype {
12    ($(#[$meta:meta])* $name:ident, $what:literal) => {
13        $(#[$meta])*
14        #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15        #[serde(transparent)]
16        pub struct $name(String);
17
18        impl $name {
19            #[doc = concat!("Create a ", $what, " value from its textual representation.")]
20            pub fn new(value: impl Into<String>) -> Self {
21                Self(value.into())
22            }
23
24            /// Borrow the underlying textual representation.
25            pub fn as_str(&self) -> &str {
26                &self.0
27            }
28
29            /// Consume the wrapper and return the underlying textual representation.
30            pub fn into_inner(self) -> String {
31                self.0
32            }
33        }
34
35        impl From<String> for $name {
36            fn from(value: String) -> Self {
37                Self(value)
38            }
39        }
40
41        impl From<&str> for $name {
42            fn from(value: &str) -> Self {
43                Self(value.to_string())
44            }
45        }
46
47        impl AsRef<str> for $name {
48            fn as_ref(&self) -> &str {
49                self.as_str()
50            }
51        }
52
53        impl std::fmt::Display for $name {
54            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55                f.write_str(self.as_str())
56            }
57        }
58    };
59}
60
61string_newtype!(
62    /// PostGIS `geometry` value represented as textual WKT/EWKT or EWKB hex.
63    Geometry,
64    "geometry"
65);
66
67string_newtype!(
68    /// PostGIS `geography` value represented as textual WKT/EWKT or EWKB hex.
69    Geography,
70    "geography"
71);
72
73/// Database value type.
74///
75/// Implements custom JSON serialization for cross-language compatibility:
76/// - `Decimal` -> string (avoid precision loss)
77/// - `DateTime` -> RFC3339 string
78/// - `Uuid` -> hyphenated lowercase string
79/// - `Bytes` -> base64 string
80#[derive(Debug, Clone, PartialEq)]
81pub enum Value {
82    /// NULL value.
83    Null,
84    /// Boolean.
85    Bool(bool),
86    /// 32-bit integer.
87    I32(i32),
88    /// 64-bit integer.
89    I64(i64),
90    /// 64-bit float.
91    F64(f64),
92    /// Decimal number with arbitrary precision.
93    Decimal(rust_decimal::Decimal),
94    /// Date and time (without timezone).
95    DateTime(chrono::NaiveDateTime),
96    /// UUID.
97    Uuid(uuid::Uuid),
98    /// JSON value.
99    Json(serde_json::Value),
100    /// PostgreSQL hstore key/value map.
101    Hstore(BTreeMap<String, Option<String>>),
102    /// PostgreSQL PostGIS geometry value.
103    Geometry(String),
104    /// PostgreSQL PostGIS geography value.
105    Geography(String),
106    /// PostgreSQL pgvector dense embedding vector.
107    Vector(Vec<f32>),
108    /// String.
109    String(String),
110    /// Byte array.
111    Bytes(Vec<u8>),
112    /// Array of values (PostgreSQL native arrays).
113    Array(Vec<Value>),
114    /// 2D array of values (PostgreSQL multi-dimensional arrays).
115    Array2D(Vec<Vec<Value>>),
116    /// A database enum value with its PostgreSQL type name.
117    ///
118    /// Carries the variant string (e.g. `"ADMIN"`) together with the
119    /// lowercase PG type name (e.g. `"role"`) so that the PostgreSQL
120    /// dialect can emit the required explicit cast (`$1::role`).
121    /// All other backends treat this identically to `Value::String`.
122    Enum {
123        /// The enum variant string sent to / received from the DB.
124        value: String,
125        /// Lowercase PostgreSQL type name (e.g. `"role"`, `"poststatus"`).
126        type_name: String,
127    },
128    /// A PostgreSQL native composite type value.
129    ///
130    /// Carries the lowercase PG type name (e.g. `"championstats"`) together
131    /// with the field values in their declared order. The PostgreSQL dialect
132    /// emits the required explicit cast (`$1::championstats`) and the connector
133    /// encodes the fields as a record literal (`("0","0",…)`). Backends that
134    /// store composites as JSON never receive this variant.
135    Composite {
136        /// Lowercase PostgreSQL type name (e.g. `"championstats"`).
137        type_name: String,
138        /// Field values in the composite type's declared order.
139        fields: Vec<Value>,
140    },
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144#[serde(tag = "type", content = "value", rename_all = "snake_case")]
145enum SerdeValue {
146    Null,
147    Bool(bool),
148    I32(i32),
149    I64(i64),
150    F64(f64),
151    Decimal(String),
152    DateTime(String),
153    Uuid(String),
154    Json(serde_json::Value),
155    Hstore(BTreeMap<String, Option<String>>),
156    Geometry(String),
157    Geography(String),
158    Vector(Vec<f32>),
159    String(String),
160    Bytes(String),
161    Array(Vec<Value>),
162    Array2D(Vec<Vec<Value>>),
163    Enum {
164        value: String,
165        type_name: String,
166    },
167    Composite {
168        type_name: String,
169        fields: Vec<Value>,
170    },
171}
172
173/// Wire format shared by [`format_datetime`] and [`PlainValueRef`].
174const DATETIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.fZ";
175
176fn format_datetime(value: chrono::NaiveDateTime) -> String {
177    value.format(DATETIME_FORMAT).to_string()
178}
179
180fn parse_datetime_string(raw: &str) -> std::result::Result<chrono::NaiveDateTime, String> {
181    chrono::DateTime::parse_from_rfc3339(raw)
182        .map(|value| value.naive_utc())
183        .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M:%S%.f"))
184        .or_else(|_| chrono::NaiveDateTime::parse_from_str(raw, "%Y-%m-%d %H:%M:%S%.f"))
185        .map_err(|_| format!("invalid datetime '{}'", raw))
186}
187
188impl From<&Value> for SerdeValue {
189    fn from(value: &Value) -> Self {
190        match value {
191            Value::Null => SerdeValue::Null,
192            Value::Bool(v) => SerdeValue::Bool(*v),
193            Value::I32(v) => SerdeValue::I32(*v),
194            Value::I64(v) => SerdeValue::I64(*v),
195            Value::F64(v) => SerdeValue::F64(*v),
196            Value::Decimal(v) => SerdeValue::Decimal(v.to_string()),
197            Value::DateTime(v) => SerdeValue::DateTime(format_datetime(*v)),
198            Value::Uuid(v) => SerdeValue::Uuid(v.to_string()),
199            Value::Json(v) => SerdeValue::Json(v.clone()),
200            Value::Hstore(v) => SerdeValue::Hstore(v.clone()),
201            Value::Geometry(v) => SerdeValue::Geometry(v.clone()),
202            Value::Geography(v) => SerdeValue::Geography(v.clone()),
203            Value::Vector(v) => SerdeValue::Vector(v.clone()),
204            Value::String(v) => SerdeValue::String(v.clone()),
205            Value::Bytes(v) => {
206                use base64::Engine;
207                SerdeValue::Bytes(base64::engine::general_purpose::STANDARD.encode(v))
208            }
209            Value::Array(v) => SerdeValue::Array(v.clone()),
210            Value::Array2D(v) => SerdeValue::Array2D(v.clone()),
211            Value::Enum { value, type_name } => SerdeValue::Enum {
212                value: value.clone(),
213                type_name: type_name.clone(),
214            },
215            Value::Composite { type_name, fields } => SerdeValue::Composite {
216                type_name: type_name.clone(),
217                fields: fields.clone(),
218            },
219        }
220    }
221}
222
223impl TryFrom<SerdeValue> for Value {
224    type Error = String;
225
226    fn try_from(value: SerdeValue) -> std::result::Result<Self, Self::Error> {
227        match value {
228            SerdeValue::Null => Ok(Value::Null),
229            SerdeValue::Bool(v) => Ok(Value::Bool(v)),
230            SerdeValue::I32(v) => Ok(Value::I32(v)),
231            SerdeValue::I64(v) => Ok(Value::I64(v)),
232            SerdeValue::F64(v) => Ok(Value::F64(v)),
233            SerdeValue::Decimal(raw) => rust_decimal::Decimal::from_str(&raw)
234                .map(Value::Decimal)
235                .map_err(|e| format!("invalid decimal '{}': {}", raw, e)),
236            SerdeValue::DateTime(raw) => parse_datetime_string(&raw).map(Value::DateTime),
237            SerdeValue::Uuid(raw) => uuid::Uuid::parse_str(&raw)
238                .map(Value::Uuid)
239                .map_err(|e| format!("invalid uuid '{}': {}", raw, e)),
240            SerdeValue::Json(v) => Ok(Value::Json(v)),
241            SerdeValue::Hstore(v) => Ok(Value::Hstore(v)),
242            SerdeValue::Geometry(v) => Ok(Value::Geometry(v)),
243            SerdeValue::Geography(v) => Ok(Value::Geography(v)),
244            SerdeValue::Vector(v) => Ok(Value::Vector(v)),
245            SerdeValue::String(v) => Ok(Value::String(v)),
246            SerdeValue::Bytes(raw) => {
247                use base64::Engine;
248                base64::engine::general_purpose::STANDARD
249                    .decode(raw.as_bytes())
250                    .map(Value::Bytes)
251                    .map_err(|e| format!("invalid base64 bytes '{}': {}", raw, e))
252            }
253            SerdeValue::Array(v) => Ok(Value::Array(v)),
254            SerdeValue::Array2D(v) => Ok(Value::Array2D(v)),
255            SerdeValue::Enum { value, type_name } => Ok(Value::Enum { value, type_name }),
256            SerdeValue::Composite { type_name, fields } => {
257                Ok(Value::Composite { type_name, fields })
258            }
259        }
260    }
261}
262
263impl From<bool> for Value {
264    fn from(v: bool) -> Self {
265        Value::Bool(v)
266    }
267}
268
269impl From<i32> for Value {
270    fn from(v: i32) -> Self {
271        Value::I32(v)
272    }
273}
274
275impl From<i64> for Value {
276    fn from(v: i64) -> Self {
277        Value::I64(v)
278    }
279}
280
281impl From<f64> for Value {
282    fn from(v: f64) -> Self {
283        Value::F64(v)
284    }
285}
286
287impl From<f32> for Value {
288    fn from(v: f32) -> Self {
289        Value::F64(v as f64)
290    }
291}
292
293impl From<rust_decimal::Decimal> for Value {
294    fn from(v: rust_decimal::Decimal) -> Self {
295        Value::Decimal(v)
296    }
297}
298
299impl From<chrono::NaiveDateTime> for Value {
300    fn from(v: chrono::NaiveDateTime) -> Self {
301        Value::DateTime(v)
302    }
303}
304
305impl From<uuid::Uuid> for Value {
306    fn from(v: uuid::Uuid) -> Self {
307        Value::Uuid(v)
308    }
309}
310
311impl From<serde_json::Value> for Value {
312    fn from(v: serde_json::Value) -> Self {
313        Value::Json(v)
314    }
315}
316
317impl From<BTreeMap<String, Option<String>>> for Value {
318    fn from(v: BTreeMap<String, Option<String>>) -> Self {
319        Value::Hstore(v)
320    }
321}
322
323impl From<Geometry> for Value {
324    fn from(v: Geometry) -> Self {
325        Value::Geometry(v.into_inner())
326    }
327}
328
329impl From<Geography> for Value {
330    fn from(v: Geography) -> Self {
331        Value::Geography(v.into_inner())
332    }
333}
334
335impl From<Vec<f32>> for Value {
336    fn from(v: Vec<f32>) -> Self {
337        Value::Vector(v)
338    }
339}
340
341impl From<String> for Value {
342    fn from(v: String) -> Self {
343        Value::String(v)
344    }
345}
346
347impl From<&str> for Value {
348    fn from(v: &str) -> Self {
349        Value::String(v.to_string())
350    }
351}
352
353impl From<Vec<u8>> for Value {
354    fn from(v: Vec<u8>) -> Self {
355        Value::Bytes(v)
356    }
357}
358
359// Array conversions — generated for all scalar types that map cleanly to Value
360// via `Into<Value>`. `Vec<u8>` is intentionally excluded: it maps to
361// `Value::Bytes`, not `Value::Array`.
362macro_rules! impl_vec_from {
363    ($($t:ty),* $(,)?) => {
364        $(
365            impl From<Vec<$t>> for Value {
366                fn from(v: Vec<$t>) -> Self {
367                    Value::Array(v.into_iter().map(|x| x.into()).collect())
368                }
369            }
370
371            impl From<Vec<Vec<$t>>> for Value {
372                fn from(v: Vec<Vec<$t>>) -> Self {
373                    Value::Array2D(
374                        v.into_iter()
375                            .map(|row| row.into_iter().map(|x| x.into()).collect())
376                            .collect(),
377                    )
378                }
379            }
380        )*
381    };
382}
383
384impl_vec_from!(
385    i32,
386    i64,
387    f64,
388    bool,
389    String,
390    Geometry,
391    Geography,
392    BTreeMap<String, Option<String>>,
393    rust_decimal::Decimal,
394    uuid::Uuid,
395    chrono::NaiveDateTime,
396    serde_json::Value,
397);
398
399// Option<T> conversions — map None -> Value::Null.
400// `Option<&str>` is kept manual because `&str` requires an explicit `.to_string()`
401// and does not implement `Into<Value>` through the generic `v.into()` path.
402macro_rules! impl_option_from {
403    ($($t:ty),* $(,)?) => {
404        $(
405            impl From<Option<$t>> for Value {
406                fn from(v: Option<$t>) -> Self {
407                    v.map(|x| x.into()).unwrap_or(Value::Null)
408                }
409            }
410        )*
411    };
412}
413
414impl_option_from!(
415    bool,
416    i32,
417    i64,
418    f64,
419    String,
420    Vec<f32>,
421    Geometry,
422    Geography,
423    BTreeMap<String, Option<String>>,
424    rust_decimal::Decimal,
425    uuid::Uuid,
426    chrono::NaiveDateTime,
427);
428
429impl From<Option<&str>> for Value {
430    fn from(v: Option<&str>) -> Self {
431        v.map(|s| Value::String(s.to_string()))
432            .unwrap_or(Value::Null)
433    }
434}
435
436impl Value {
437    /// Convert this value into the plain JSON shape used on transport/wire paths.
438    ///
439    /// Unlike the serde representation of [`Value`] itself, this helper
440    /// intentionally mirrors the historic untagged encoding used by the engine
441    /// and generated raw-query helpers.
442    pub fn to_json_plain(&self) -> serde_json::Value {
443        match self {
444            Value::Null => serde_json::Value::Null,
445            Value::Bool(v) => serde_json::Value::Bool(*v),
446            Value::I32(v) => serde_json::Value::Number((*v).into()),
447            Value::I64(v) => serde_json::Value::Number((*v).into()),
448            Value::F64(v) => serde_json::Number::from_f64(*v)
449                .map(serde_json::Value::Number)
450                .unwrap_or(serde_json::Value::Null),
451            Value::Decimal(v) => serde_json::Value::String(v.to_string()),
452            Value::DateTime(v) => serde_json::Value::String(format_datetime(*v)),
453            Value::Uuid(v) => serde_json::Value::String(v.to_string()),
454            Value::Json(v) => v.clone(),
455            Value::Hstore(v) => serde_json::Value::Object(
456                v.iter()
457                    .map(|(key, value)| {
458                        (
459                            key.clone(),
460                            value
461                                .as_ref()
462                                .map(|item| serde_json::Value::String(item.clone()))
463                                .unwrap_or(serde_json::Value::Null),
464                        )
465                    })
466                    .collect(),
467            ),
468            Value::Geometry(v) | Value::Geography(v) => serde_json::Value::String(v.clone()),
469            Value::Vector(v) => serde_json::Value::Array(
470                v.iter()
471                    .map(|item| {
472                        serde_json::Number::from_f64(*item as f64)
473                            .map(serde_json::Value::Number)
474                            .unwrap_or(serde_json::Value::Null)
475                    })
476                    .collect(),
477            ),
478            Value::String(v) => serde_json::Value::String(v.clone()),
479            Value::Bytes(v) => {
480                use base64::Engine;
481                serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(v))
482            }
483            Value::Array(v) => {
484                serde_json::Value::Array(v.iter().map(Value::to_json_plain).collect())
485            }
486            Value::Array2D(v) => serde_json::Value::Array(
487                v.iter()
488                    .map(|row| {
489                        serde_json::Value::Array(row.iter().map(Value::to_json_plain).collect())
490                    })
491                    .collect(),
492            ),
493            Value::Enum { value, .. } => serde_json::Value::String(value.clone()),
494            Value::Composite { fields, .. } => {
495                serde_json::Value::Array(fields.iter().map(Value::to_json_plain).collect())
496            }
497        }
498    }
499}
500
501/// Serializes a `Display` value as a JSON string without an intermediate `String`.
502struct DisplayString<T>(T);
503
504impl<T: std::fmt::Display> Serialize for DisplayString<T> {
505    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506    where
507        S: Serializer,
508    {
509        serializer.collect_str(&self.0)
510    }
511}
512
513/// Serializes a datetime in the wire format of [`format_datetime`] without an
514/// intermediate `String`.
515struct DateTimeString(chrono::NaiveDateTime);
516
517impl Serialize for DateTimeString {
518    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
519    where
520        S: Serializer,
521    {
522        serializer.collect_str(&self.0.format(DATETIME_FORMAT))
523    }
524}
525
526/// Serializes bytes as a standard-alphabet base64 string without an
527/// intermediate `String`.
528struct Base64String<'a>(&'a [u8]);
529
530impl Serialize for Base64String<'_> {
531    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532    where
533        S: Serializer,
534    {
535        serializer.collect_str(&base64::display::Base64Display::new(
536            self.0,
537            &base64::engine::general_purpose::STANDARD,
538        ))
539    }
540}
541
542/// Borrowed mirror of [`SerdeValue`]: emits the identical tagged shape but
543/// serializes by reference instead of deep-cloning `Json`, `Hstore`, `Vector`,
544/// `String`, `Array`, `Array2D`, `Enum` and `Composite` payloads first.
545/// Deserialization keeps going through the owned [`SerdeValue`]; the two
546/// shapes are kept in sync by equivalence tests over every variant.
547#[derive(Serialize)]
548#[serde(tag = "type", content = "value", rename_all = "snake_case")]
549enum SerdeValueRef<'a> {
550    Null,
551    Bool(bool),
552    I32(i32),
553    I64(i64),
554    F64(f64),
555    Decimal(DisplayString<&'a rust_decimal::Decimal>),
556    DateTime(DateTimeString),
557    Uuid(DisplayString<&'a uuid::Uuid>),
558    Json(&'a serde_json::Value),
559    Hstore(&'a BTreeMap<String, Option<String>>),
560    Geometry(&'a str),
561    Geography(&'a str),
562    Vector(&'a [f32]),
563    String(&'a str),
564    Bytes(Base64String<'a>),
565    Array(&'a [Value]),
566    Array2D(&'a [Vec<Value>]),
567    Enum {
568        value: &'a str,
569        type_name: &'a str,
570    },
571    Composite {
572        type_name: &'a str,
573        fields: &'a [Value],
574    },
575}
576
577impl<'a> From<&'a Value> for SerdeValueRef<'a> {
578    fn from(value: &'a Value) -> Self {
579        match value {
580            Value::Null => SerdeValueRef::Null,
581            Value::Bool(v) => SerdeValueRef::Bool(*v),
582            Value::I32(v) => SerdeValueRef::I32(*v),
583            Value::I64(v) => SerdeValueRef::I64(*v),
584            Value::F64(v) => SerdeValueRef::F64(*v),
585            Value::Decimal(v) => SerdeValueRef::Decimal(DisplayString(v)),
586            Value::DateTime(v) => SerdeValueRef::DateTime(DateTimeString(*v)),
587            Value::Uuid(v) => SerdeValueRef::Uuid(DisplayString(v)),
588            Value::Json(v) => SerdeValueRef::Json(v),
589            Value::Hstore(v) => SerdeValueRef::Hstore(v),
590            Value::Geometry(v) => SerdeValueRef::Geometry(v),
591            Value::Geography(v) => SerdeValueRef::Geography(v),
592            Value::Vector(v) => SerdeValueRef::Vector(v),
593            Value::String(v) => SerdeValueRef::String(v),
594            Value::Bytes(v) => SerdeValueRef::Bytes(Base64String(v)),
595            Value::Array(v) => SerdeValueRef::Array(v),
596            Value::Array2D(v) => SerdeValueRef::Array2D(v),
597            Value::Enum { value, type_name } => SerdeValueRef::Enum { value, type_name },
598            Value::Composite { type_name, fields } => {
599                SerdeValueRef::Composite { type_name, fields }
600            }
601        }
602    }
603}
604
605/// Serializes a borrowed [`Value`] in the same plain JSON shape produced by
606/// [`Value::to_json_plain`], writing directly into the serializer.
607///
608/// Unlike `to_json_plain`, no intermediate `serde_json::Value` tree is built:
609/// strings, arrays, hstore maps and composites are serialized by reference.
610/// Used on the hot row-serialization path; `to_json_plain` remains for callers
611/// that need an owned `serde_json::Value`. The two are kept in sync by
612/// equivalence tests over every variant.
613pub struct PlainValueRef<'a>(pub &'a Value);
614
615/// Serializes an `f64` as a JSON number, or `null` when not finite —
616/// mirroring `serde_json::Number::from_f64` returning `None` for NaN/±∞.
617struct PlainF64(f64);
618
619impl Serialize for PlainF64 {
620    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
621    where
622        S: Serializer,
623    {
624        if self.0.is_finite() {
625            serializer.serialize_f64(self.0)
626        } else {
627            serializer.serialize_unit()
628        }
629    }
630}
631
632struct PlainSliceRef<'a>(&'a [Value]);
633
634impl Serialize for PlainSliceRef<'_> {
635    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
636    where
637        S: Serializer,
638    {
639        serializer.collect_seq(self.0.iter().map(PlainValueRef))
640    }
641}
642
643impl Serialize for PlainValueRef<'_> {
644    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
645    where
646        S: Serializer,
647    {
648        match self.0 {
649            Value::Null => serializer.serialize_unit(),
650            Value::Bool(v) => serializer.serialize_bool(*v),
651            Value::I32(v) => serializer.serialize_i32(*v),
652            Value::I64(v) => serializer.serialize_i64(*v),
653            Value::F64(v) => PlainF64(*v).serialize(serializer),
654            Value::Decimal(v) => serializer.collect_str(v),
655            Value::DateTime(v) => DateTimeString(*v).serialize(serializer),
656            Value::Uuid(v) => serializer.collect_str(v),
657            Value::Json(v) => v.serialize(serializer),
658            Value::Hstore(v) => serializer.collect_map(v.iter()),
659            Value::Geometry(v) | Value::Geography(v) => serializer.serialize_str(v),
660            Value::Vector(v) => serializer.collect_seq(v.iter().map(|item| PlainF64(*item as f64))),
661            Value::String(v) => serializer.serialize_str(v),
662            Value::Bytes(v) => Base64String(v).serialize(serializer),
663            Value::Array(v) => serializer.collect_seq(v.iter().map(PlainValueRef)),
664            Value::Array2D(v) => serializer.collect_seq(v.iter().map(|row| PlainSliceRef(row))),
665            Value::Enum { value, .. } => serializer.serialize_str(value),
666            Value::Composite { fields, .. } => {
667                serializer.collect_seq(fields.iter().map(PlainValueRef))
668            }
669        }
670    }
671}
672
673impl Serialize for Value {
674    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
675    where
676        S: Serializer,
677    {
678        SerdeValueRef::from(self).serialize(serializer)
679    }
680}
681
682/// Deserializes a [`Value`] from the tagged serde representation emitted by
683/// [`Serialize`] for [`Value`].
684impl<'de> Deserialize<'de> for Value {
685    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686    where
687        D: Deserializer<'de>,
688    {
689        let tagged = SerdeValue::deserialize(deserializer)?;
690        Value::try_from(tagged).map_err(serde::de::Error::custom)
691    }
692}
693
694/// Convert a `&serde_json::Value` reference to a [`Value`].
695///
696/// This is the canonical JSON->Value conversion used throughout the crate.
697/// It is `pub(crate)` so that other modules (e.g. `column.rs`) can reuse it
698/// without duplicating the logic.
699///
700/// Numbers are coerced to `I32` before `I64` when they fit, then `F64`.
701/// Arrays of arrays are **not** auto-promoted to `Array2D` here; that
702/// promotion happens in the connector stream decoders where full schema
703/// knowledge is available.
704pub(crate) fn json_to_value_ref(json: &serde_json::Value) -> Value {
705    match json {
706        serde_json::Value::Null => Value::Null,
707        serde_json::Value::Bool(b) => Value::Bool(*b),
708        serde_json::Value::Number(n) => {
709            if let Some(i) = n.as_i64() {
710                if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
711                    Value::I32(i as i32)
712                } else {
713                    Value::I64(i)
714                }
715            } else if let Some(f) = n.as_f64() {
716                Value::F64(f)
717            } else {
718                Value::String(n.to_string())
719            }
720        }
721        serde_json::Value::String(s) => Value::String(s.clone()),
722        serde_json::Value::Array(arr) => Value::Array(arr.iter().map(json_to_value_ref).collect()),
723        serde_json::Value::Object(_) => Value::Json(json.clone()),
724    }
725}
726
727#[cfg(test)]
728mod tests {
729    use core::f64;
730    use std::collections::BTreeMap;
731
732    use super::*;
733
734    #[test]
735    fn test_value_variants() {
736        assert_eq!(Value::Null, Value::Null);
737        assert_eq!(Value::Bool(true), Value::from(true));
738        assert_eq!(Value::I32(42), Value::from(42i32));
739        assert_eq!(Value::I64(42), Value::from(42i64));
740        assert_eq!(Value::F64(2.5), Value::from(2.5f64));
741        assert_eq!(Value::String("hello".to_string()), Value::from("hello"));
742        assert_eq!(Value::Bytes(vec![1, 2, 3]), Value::from(vec![1u8, 2, 3]));
743
744        use rust_decimal::Decimal;
745        let dec = Decimal::new(12345, 2);
746        assert_eq!(Value::Decimal(dec), Value::from(dec));
747
748        use chrono::NaiveDate;
749        let dt = NaiveDate::from_ymd_opt(2024, 1, 1)
750            .unwrap()
751            .and_hms_opt(12, 0, 0)
752            .unwrap();
753        assert_eq!(Value::DateTime(dt), Value::from(dt));
754
755        use uuid::Uuid;
756        let id = Uuid::nil();
757        assert_eq!(Value::Uuid(id), Value::from(id));
758
759        use serde_json::json;
760        let j = json!({"key": "value"});
761        assert_eq!(Value::Json(j.clone()), Value::from(j));
762
763        let hstore = BTreeMap::from([
764            ("display_name".to_string(), Some("Bob".to_string())),
765            ("nickname".to_string(), None),
766        ]);
767        assert_eq!(Value::Hstore(hstore.clone()), Value::from(hstore));
768
769        assert_eq!(
770            Value::Vector(vec![0.1, 0.2]),
771            Value::from(vec![0.1f32, 0.2])
772        );
773    }
774
775    #[test]
776    fn test_value_to_json_plain_primitives() {
777        assert_eq!(Value::Null.to_json_plain(), serde_json::Value::Null);
778        assert_eq!(
779            Value::Bool(true).to_json_plain(),
780            serde_json::Value::Bool(true)
781        );
782        assert_eq!(Value::I32(42).to_json_plain().as_i64(), Some(42));
783        assert_eq!(
784            Value::I64(9007199254740991).to_json_plain().as_i64(),
785            Some(9007199254740991)
786        );
787        assert_eq!(
788            Value::F64(f64::consts::PI).to_json_plain().as_f64(),
789            Some(f64::consts::PI)
790        );
791        assert_eq!(
792            Value::String("hello world".to_string())
793                .to_json_plain()
794                .as_str(),
795            Some("hello world")
796        );
797    }
798
799    #[test]
800    fn test_value_to_json_plain_special_scalars() {
801        use rust_decimal::Decimal;
802        let dec = Decimal::new(12345, 2);
803        use chrono::NaiveDate;
804        let dt = NaiveDate::from_ymd_opt(2026, 2, 18)
805            .unwrap()
806            .and_hms_opt(10, 30, 45)
807            .unwrap();
808        use uuid::Uuid;
809        let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
810        assert_eq!(Value::Decimal(dec).to_json_plain().as_str(), Some("123.45"));
811        assert!(Value::DateTime(dt)
812            .to_json_plain()
813            .as_str()
814            .unwrap()
815            .starts_with("2026-02-18T10:30:45"));
816        assert_eq!(
817            Value::Uuid(id).to_json_plain().as_str(),
818            Some("550e8400-e29b-41d4-a716-446655440000")
819        );
820        assert_eq!(
821            Value::Bytes(vec![72, 101, 108, 108, 111])
822                .to_json_plain()
823                .as_str(),
824            Some("SGVsbG8=")
825        );
826    }
827
828    #[test]
829    fn test_value_to_json_plain_json_and_arrays() {
830        use serde_json::json;
831        let object = json!({"name": "Alice", "age": 30});
832        assert_eq!(Value::Json(object.clone()).to_json_plain(), object);
833
834        let value = Value::Array(vec![
835            Value::String("a".to_string()),
836            Value::String("b".to_string()),
837            Value::String("c".to_string()),
838        ]);
839
840        let json = value.to_json_plain();
841        assert_eq!(json[0].as_str(), Some("a"));
842        assert_eq!(json[1].as_str(), Some("b"));
843        assert_eq!(json[2].as_str(), Some("c"));
844    }
845
846    #[test]
847    fn test_value_to_json_plain_hstore() {
848        let value = Value::Hstore(BTreeMap::from([
849            ("display_name".to_string(), Some("Bob".to_string())),
850            ("nickname".to_string(), None),
851        ]));
852
853        assert_eq!(
854            value.to_json_plain(),
855            serde_json::json!({
856                "display_name": "Bob",
857                "nickname": null
858            })
859        );
860    }
861
862    #[test]
863    fn test_value_to_json_plain_vector() {
864        let json = Value::Vector(vec![1.0, 2.5, 3.25]).to_json_plain();
865        assert_eq!(json, serde_json::json!([1.0, 2.5, 3.25]));
866    }
867
868    #[test]
869    fn test_value_plain_json_array2d_roundtrip_stays_untyped_without_schema() {
870        let value = Value::Array2D(vec![
871            vec![Value::I32(1), Value::I32(2)],
872            vec![Value::I32(3), Value::I32(4)],
873        ]);
874
875        let json = value.to_json_plain();
876        assert_eq!(json[0][0].as_i64(), Some(1));
877        assert_eq!(json[0][1].as_i64(), Some(2));
878        assert_eq!(json[1][0].as_i64(), Some(3));
879        assert_eq!(json[1][1].as_i64(), Some(4));
880
881        // Deserialization: without schema context the `Array2D` heuristic is
882        // intentionally absent from `json_to_value_ref`. A nested JSON array
883        // round-trips as `Array(Array(_))`. Promotion to `Array2D` is the
884        // connector stream's responsibility.
885        let expected = Value::Array(vec![
886            Value::Array(vec![Value::I32(1), Value::I32(2)]),
887            Value::Array(vec![Value::I32(3), Value::I32(4)]),
888        ]);
889        assert_eq!(json_to_value_ref(&json), expected);
890    }
891
892    #[test]
893    fn test_tagged_serde_shape_is_explicit() {
894        let value = Value::Decimal(rust_decimal::Decimal::new(12345, 2));
895        let json = serde_json::to_value(&value).unwrap();
896
897        assert_eq!(
898            json,
899            serde_json::json!({
900                "type": "decimal",
901                "value": "123.45"
902            })
903        );
904    }
905
906    #[test]
907    fn test_tagged_serde_round_trip_preserves_typed_variants() {
908        use chrono::NaiveDate;
909        use serde_json::json;
910        use uuid::Uuid;
911
912        let values = vec![
913            Value::Null,
914            Value::Bool(false),
915            Value::I32(-42),
916            Value::I64(9007199254740991), // Large I64 beyond i32 range
917            Value::F64(f64::consts::E),
918            Value::Decimal(rust_decimal::Decimal::new(314, 2)),
919            Value::DateTime(
920                NaiveDate::from_ymd_opt(2026, 2, 18)
921                    .unwrap()
922                    .and_hms_opt(10, 30, 45)
923                    .unwrap(),
924            ),
925            Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
926            Value::Bytes(vec![1, 2, 3, 4]),
927            Value::Json(json!({"ok": true})),
928            Value::Hstore(BTreeMap::from([
929                ("display_name".to_string(), Some("Bob".to_string())),
930                ("nickname".to_string(), None),
931            ])),
932            Value::Vector(vec![1.0, 2.0, 3.5]),
933            Value::String("test".to_string()),
934            Value::Array(vec![Value::I32(1), Value::I32(2)]),
935            Value::Array2D(vec![vec![Value::I32(1), Value::I32(2)]]),
936            Value::Enum {
937                value: "ADMIN".to_string(),
938                type_name: "role".to_string(),
939            },
940        ];
941
942        for value in values {
943            let json = serde_json::to_value(&value).unwrap();
944            let deserialized: Value = serde_json::from_value(json).unwrap();
945            assert_eq!(deserialized, value);
946        }
947    }
948
949    /// One sample per `Value` variant, including the edge cases that take a
950    /// non-obvious serialization path (non-finite floats -> null, fractional
951    /// datetimes, nested arrays, hstore NULLs, composite fields).
952    fn plain_equivalence_samples() -> Vec<Value> {
953        use chrono::NaiveDate;
954        use serde_json::json;
955        use uuid::Uuid;
956
957        vec![
958            Value::Null,
959            Value::Bool(true),
960            Value::I32(-42),
961            Value::I64(9007199254740991),
962            Value::F64(f64::consts::PI),
963            Value::F64(f64::NAN),
964            Value::F64(f64::INFINITY),
965            Value::Decimal(rust_decimal::Decimal::new(-12345, 2)),
966            Value::DateTime(
967                NaiveDate::from_ymd_opt(2026, 2, 18)
968                    .unwrap()
969                    .and_hms_micro_opt(10, 30, 45, 123456)
970                    .unwrap(),
971            ),
972            Value::Uuid(Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()),
973            Value::Json(json!({"nested": {"ok": true}, "list": [1, "two", null]})),
974            Value::Hstore(BTreeMap::from([
975                ("display_name".to_string(), Some("Bob".to_string())),
976                ("nickname".to_string(), None),
977            ])),
978            Value::Geometry("POINT(1 2)".to_string()),
979            Value::Geography("SRID=4326;POINT(9 45)".to_string()),
980            Value::Vector(vec![1.0, -2.5, f32::NAN]),
981            Value::String("hello \"quoted\" world".to_string()),
982            Value::Bytes(vec![72, 101, 108, 108, 111]),
983            Value::Array(vec![
984                Value::I32(1),
985                Value::String("two".to_string()),
986                Value::Null,
987                Value::Array(vec![Value::Bool(false)]),
988            ]),
989            Value::Array2D(vec![
990                vec![Value::I32(1), Value::I32(2)],
991                vec![Value::I32(3), Value::I32(4)],
992            ]),
993            Value::Enum {
994                value: "ADMIN".to_string(),
995                type_name: "role".to_string(),
996            },
997            Value::Composite {
998                type_name: "championstats".to_string(),
999                fields: vec![Value::I32(0), Value::String("x".to_string()), Value::Null],
1000            },
1001        ]
1002    }
1003
1004    #[test]
1005    fn test_plain_value_ref_matches_to_json_plain_tree() {
1006        for value in plain_equivalence_samples() {
1007            let via_ref = serde_json::to_value(PlainValueRef(&value)).unwrap();
1008            assert_eq!(via_ref, value.to_json_plain(), "variant: {:?}", value);
1009        }
1010    }
1011
1012    #[test]
1013    fn test_plain_value_ref_matches_to_json_plain_string() {
1014        for value in plain_equivalence_samples() {
1015            let via_ref = serde_json::to_string(&PlainValueRef(&value)).unwrap();
1016            let via_tree = serde_json::to_string(&value.to_json_plain()).unwrap();
1017            assert_eq!(via_ref, via_tree, "variant: {:?}", value);
1018        }
1019    }
1020
1021    #[test]
1022    fn test_tagged_serialize_borrowed_matches_owned_serde_value() {
1023        for value in plain_equivalence_samples() {
1024            let via_ref = serde_json::to_string(&value).unwrap();
1025            let via_owned = serde_json::to_string(&SerdeValue::from(&value)).unwrap();
1026            assert_eq!(via_ref, via_owned, "variant: {:?}", value);
1027        }
1028    }
1029}