Skip to main content

toasty_sql/
json.rs

1//! JSON encoding for `stmt::Value`s stored in document-backed columns
2//! (MySQL `JSON`, SQLite TEXT via the JSON1 extension, and PostgreSQL `jsonb`
3//! for `#[document]`-marked fields).
4//!
5//! The conversion does **not** go through an intermediate [`serde_json::Value`]
6//! tree. Encoding streams a `stmt::Value` straight to JSON text via a
7//! [`serde::Serialize`] wrapper ([`Encode`]); decoding parses JSON tokens
8//! straight into a correctly-typed `stmt::Value` via a type-directed
9//! [`serde::de::DeserializeSeed`] ([`Seed`]).
10//!
11//! The serde impls live on *local wrappers*, not on `stmt::Value` itself. The
12//! encoding is opinionated (UUIDs / decimals / timestamps as JSON strings, to
13//! match the per-column TEXT encoding the same scalar has at the SQL level) and
14//! backends with typed document storage (BSON, DynamoDB) need different
15//! representations — so `stmt::Value` is deliberately left without a canonical
16//! serde representation. Decoding is *type-directed* for scalars: `Value::Uuid`
17//! vs `Value::String` are both JSON strings on the wire, and `Value::I64` vs
18//! `Value::U64` are both JSON numbers; only the caller's `stmt::Type`
19//! distinguishes them, which is why decode carries the type as a seed rather
20//! than a plain `Deserialize`.
21//!
22//! A `#[document]` column is typed by the structural `stmt::Type::Object` and
23//! decodes *shape-directed*: a JSON object becomes a named `Value::Object` in
24//! wire key order, and its interior leaves take their wire shapes (strings
25//! stay strings, numbers decode by integer fit). The query engine — the only
26//! party that knows which embedded model the column stores — raises the wire
27//! object into a typed positional record; no schema is consulted here.
28
29use serde::de::{DeserializeSeed, Deserializer, Error as _, MapAccess, SeqAccess, Visitor};
30use serde::ser::{Error as _, Serialize, SerializeMap, SerializeSeq, Serializer};
31use std::fmt;
32use toasty_core::stmt::{self, Value};
33
34// ============================================================================
35// Encoding: stmt::Value -> JSON text (no serde_json::Value intermediate)
36// ============================================================================
37
38/// A [`serde::Serialize`] wrapper that streams a `stmt::Value` as JSON.
39///
40/// Object entries whose value is [`Value::Null`] are omitted entirely — an
41/// `Option::None` field produces a missing key, not an explicit `null`.
42/// Non-finite floats (NaN / infinity, which have no JSON form) encode as
43/// `null`. Shapes with no JSON representation (`Record`, `Bytes`,
44/// `SparseRecord`) are a serialization error — document-stored values reach
45/// the driver as `Object` / `List`, never `Record`.
46pub struct Encode<'a>(pub &'a Value);
47
48impl Serialize for Encode<'_> {
49    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
50        match self.0 {
51            Value::Null => s.serialize_unit(),
52            Value::Bool(v) => s.serialize_bool(*v),
53            Value::I8(v) => s.serialize_i8(*v),
54            Value::I16(v) => s.serialize_i16(*v),
55            Value::I32(v) => s.serialize_i32(*v),
56            Value::I64(v) => s.serialize_i64(*v),
57            Value::U8(v) => s.serialize_u8(*v),
58            Value::U16(v) => s.serialize_u16(*v),
59            Value::U32(v) => s.serialize_u32(*v),
60            Value::U64(v) => s.serialize_u64(*v),
61            // NaN / infinity have no JSON representation; encode as null.
62            Value::F32(v) if v.is_finite() => s.serialize_f32(*v),
63            Value::F32(_) => s.serialize_unit(),
64            Value::F64(v) if v.is_finite() => s.serialize_f64(*v),
65            Value::F64(_) => s.serialize_unit(),
66            Value::String(v) => s.serialize_str(v),
67            Value::Uuid(v) => s.collect_str(v),
68            Value::List(items) => {
69                let mut seq = s.serialize_seq(Some(items.len()))?;
70                for item in items {
71                    seq.serialize_element(&Encode(item))?;
72                }
73                seq.end()
74            }
75            Value::Object(object) => {
76                let mut map = s.serialize_map(None)?;
77                for (k, v) in object.iter() {
78                    // `Option::None` -> omit the key entirely.
79                    if v.is_null() {
80                        continue;
81                    }
82                    map.serialize_entry(k, &Encode(v))?;
83                }
84                map.end()
85            }
86            // Decimals and jiff temporal scalars store the shared document
87            // text form ([`Value::document_storage_text`]): decimals as their
88            // `Display` form, temporals as ISO 8601 / RFC 3339 text truncated
89            // to microseconds (the precision the SQL temporal types hold) and
90            // printed with fixed six-digit subsecond precision so
91            // text-comparing backends (SQLite) order document leaves
92            // chronologically. The engine's document lowering builds
93            // comparison operands through the same method, so the stored form
94            // and a bound operand cannot drift apart. `Zoned` is rejected at
95            // schema-build (its RFC 9557 annotation has no SQL cast), so it
96            // never reaches a document column.
97            #[cfg(feature = "rust_decimal")]
98            v @ Value::Decimal(_) => s.collect_str(
99                &v.document_storage_text()
100                    .expect("decimal value has a document text form"),
101            ),
102            #[cfg(feature = "bigdecimal")]
103            v @ Value::BigDecimal(_) => s.collect_str(
104                &v.document_storage_text()
105                    .expect("decimal value has a document text form"),
106            ),
107            #[cfg(feature = "jiff")]
108            v @ (Value::Timestamp(_) | Value::Date(_) | Value::Time(_) | Value::DateTime(_)) => {
109                let text = v
110                    .document_storage_text()
111                    .expect("temporal value has a document text form");
112                s.collect_str(&text)
113            }
114            #[cfg(feature = "jiff")]
115            Value::Zoned(v) => s.collect_str(v),
116            #[cfg(feature = "net")]
117            v @ (Value::Cidr(_) | Value::Inet(_) | Value::MacAddr(_) | Value::MacAddr8(_)) => s
118                .collect_str(
119                    &v.document_storage_text()
120                        .expect("network address value has a document text form"),
121                ),
122            other => Err(S::Error::custom(format!("cannot encode {other:?} as JSON"))),
123        }
124    }
125}
126
127/// Encode a `stmt::Value` as a JSON string.
128pub fn to_string(value: &Value) -> Result<String, serde_json::Error> {
129    serde_json::to_string(&Encode(value))
130}
131
132/// Encode a `stmt::Value` as JSON UTF-8 bytes.
133pub fn to_vec(value: &Value) -> Result<Vec<u8>, serde_json::Error> {
134    serde_json::to_vec(&Encode(value))
135}
136
137// ============================================================================
138// Decoding: JSON text -> stmt::Value, directed by stmt::Type
139// ============================================================================
140
141/// A type-directed [`serde::de::DeserializeSeed`] that decodes JSON straight
142/// into a `stmt::Value` of the seed's `stmt::Type` — no `serde_json::Value`
143/// intermediate. The type is required because the wire form is ambiguous for
144/// scalars (`Uuid`/`String` are both strings; the integer widths are all
145/// numbers). A structural `Type::Object` position switches to shape-directed
146/// decoding ([`AnySeed`]).
147pub struct Seed<'a> {
148    /// The expected type of the value being decoded.
149    pub ty: &'a stmt::Type,
150}
151
152impl<'de> DeserializeSeed<'de> for Seed<'_> {
153    type Value = Value;
154
155    fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<Value, D::Error> {
156        // JSON is self-describing, so `deserialize_any` lets the parser drive
157        // the visit method by token; each method coerces using `self.ty`.
158        de.deserialize_any(ValueVisitor { ty: self.ty })
159    }
160}
161
162struct ValueVisitor<'a> {
163    ty: &'a stmt::Type,
164}
165
166/// A shape-directed [`serde::de::DeserializeSeed`] for the interior of a
167/// document: every JSON token decodes to its wire-natural `stmt::Value` —
168/// strings stay `String`, numbers decode by integer fit (`I64`, then `U64`,
169/// then `F64`), objects become named `Value::Object`s in wire key order. The
170/// engine casts the leaves to their field types when it raises the object to
171/// a positional record.
172struct AnySeed;
173
174impl<'de> DeserializeSeed<'de> for AnySeed {
175    type Value = Value;
176
177    fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<Value, D::Error> {
178        de.deserialize_any(AnyVisitor)
179    }
180}
181
182struct AnyVisitor;
183
184impl<'de> Visitor<'de> for AnyVisitor {
185    type Value = Value;
186
187    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        write!(f, "a JSON value inside a document")
189    }
190
191    fn visit_unit<E: serde::de::Error>(self) -> Result<Value, E> {
192        Ok(Value::Null)
193    }
194
195    fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<Value, E> {
196        Ok(Value::Bool(v))
197    }
198
199    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Value, E> {
200        Ok(Value::I64(v))
201    }
202
203    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Value, E> {
204        // Integer fit: values representable as `i64` decode to `I64` so the
205        // same stored number always has the same wire shape.
206        Ok(match i64::try_from(v) {
207            Ok(v) => Value::I64(v),
208            Err(_) => Value::U64(v),
209        })
210    }
211
212    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Value, E> {
213        Ok(Value::F64(v))
214    }
215
216    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Value, E> {
217        Ok(Value::String(v.to_owned()))
218    }
219
220    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Value, A::Error> {
221        let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
222        while let Some(value) = seq.next_element_seed(AnySeed)? {
223            items.push(value);
224        }
225        Ok(Value::List(items))
226    }
227
228    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Value, A::Error> {
229        let mut entries = Vec::new();
230        while let Some(key) = map.next_key::<String>()? {
231            entries.push((key, map.next_value_seed(AnySeed)?));
232        }
233        Ok(Value::Object(stmt::ValueObject::from_vec(entries)))
234    }
235}
236
237impl<'de> Visitor<'de> for ValueVisitor<'_> {
238    type Value = Value;
239
240    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        write!(f, "a JSON value decodable as {:?}", self.ty)
242    }
243
244    fn visit_unit<E: serde::de::Error>(self) -> Result<Value, E> {
245        // A JSON `null` — or an absent object key — is `Value::Null`,
246        // regardless of the expected type (round-trips an `Option::None`).
247        Ok(Value::Null)
248    }
249
250    fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<Value, E> {
251        match self.ty {
252            stmt::Type::Bool => Ok(Value::Bool(v)),
253            other => Err(E::custom(format!(
254                "unexpected JSON bool for type {other:?}"
255            ))),
256        }
257    }
258
259    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Value, E> {
260        int_to_value(self.ty, v as i128)
261    }
262
263    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Value, E> {
264        int_to_value(self.ty, v as i128)
265    }
266
267    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Value, E> {
268        match self.ty {
269            stmt::Type::F32 => Ok(Value::F32(v as f32)),
270            stmt::Type::F64 => Ok(Value::F64(v)),
271            other => Err(E::custom(format!(
272                "unexpected JSON float for type {other:?}"
273            ))),
274        }
275    }
276
277    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Value, E> {
278        match self.ty {
279            stmt::Type::String => Ok(Value::String(v.to_owned())),
280            stmt::Type::Uuid => Ok(Value::Uuid(v.parse().map_err(E::custom)?)),
281            #[cfg(feature = "rust_decimal")]
282            stmt::Type::Decimal => Ok(Value::Decimal(v.parse().map_err(E::custom)?)),
283            #[cfg(feature = "bigdecimal")]
284            stmt::Type::BigDecimal => Ok(Value::BigDecimal(v.parse().map_err(E::custom)?)),
285            #[cfg(feature = "jiff")]
286            stmt::Type::Timestamp => Ok(Value::Timestamp(v.parse().map_err(E::custom)?)),
287            #[cfg(feature = "jiff")]
288            stmt::Type::Zoned => Ok(Value::Zoned(v.parse().map_err(E::custom)?)),
289            #[cfg(feature = "jiff")]
290            stmt::Type::Date => Ok(Value::Date(v.parse().map_err(E::custom)?)),
291            #[cfg(feature = "jiff")]
292            stmt::Type::Time => Ok(Value::Time(v.parse().map_err(E::custom)?)),
293            #[cfg(feature = "jiff")]
294            stmt::Type::DateTime => Ok(Value::DateTime(v.parse().map_err(E::custom)?)),
295            #[cfg(feature = "net")]
296            stmt::Type::Cidr => Ok(Value::Cidr(v.parse().map_err(E::custom)?)),
297            #[cfg(feature = "net")]
298            stmt::Type::Inet => Ok(Value::Inet(v.parse().map_err(E::custom)?)),
299            #[cfg(feature = "net")]
300            stmt::Type::MacAddr => Ok(Value::MacAddr(v.parse().map_err(E::custom)?)),
301            #[cfg(feature = "net")]
302            stmt::Type::MacAddr8 => Ok(Value::MacAddr8(v.parse().map_err(E::custom)?)),
303            other => Err(E::custom(format!(
304                "unexpected JSON string for type {other:?}"
305            ))),
306        }
307    }
308
309    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Value, A::Error> {
310        match self.ty {
311            stmt::Type::List(elem) => read_seq(seq, elem),
312            other => Err(A::Error::custom(format!(
313                "unexpected JSON array for type {other:?}"
314            ))),
315        }
316    }
317
318    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Value, A::Error> {
319        // A `#[document]` column is typed by the structural `Type::Object`:
320        // decode the JSON object shape-directed, keys as stored. The engine
321        // raises the result to the embed's positional record — the field
322        // layout is a model-level concept this codec does not know.
323        match self.ty {
324            stmt::Type::Object => AnyVisitor.visit_map(map),
325            other => Err(A::Error::custom(format!(
326                "unexpected JSON object for type {other:?}"
327            ))),
328        }
329    }
330}
331
332/// Coerce a JSON integer (widened to `i128` so signed and unsigned tokens share
333/// one path) into the integer or float `Value` named by `ty`. Integer
334/// conversions are checked: a stored value outside the target type's range
335/// (e.g. `-1` decoded as `u64`) is a decode error naming the mismatch, not a
336/// silent wraparound.
337fn int_to_value<E: serde::de::Error>(ty: &stmt::Type, v: i128) -> Result<Value, E> {
338    fn checked<T: TryFrom<i128>, E: serde::de::Error>(v: i128, ty: &stmt::Type) -> Result<T, E> {
339        T::try_from(v)
340            .map_err(|_| E::custom(format!("JSON integer {v} is out of range for type {ty:?}")))
341    }
342
343    Ok(match ty {
344        stmt::Type::I8 => Value::I8(checked(v, ty)?),
345        stmt::Type::I16 => Value::I16(checked(v, ty)?),
346        stmt::Type::I32 => Value::I32(checked(v, ty)?),
347        stmt::Type::I64 => Value::I64(checked(v, ty)?),
348        stmt::Type::U8 => Value::U8(checked(v, ty)?),
349        stmt::Type::U16 => Value::U16(checked(v, ty)?),
350        stmt::Type::U32 => Value::U32(checked(v, ty)?),
351        stmt::Type::U64 => Value::U64(checked(v, ty)?),
352        stmt::Type::F32 => Value::F32(v as f32),
353        stmt::Type::F64 => Value::F64(v as f64),
354        other => {
355            return Err(E::custom(format!(
356                "unexpected JSON integer for type {other:?}"
357            )));
358        }
359    })
360}
361
362/// Decode a JSON array into a `Value::List`, seeding each element with
363/// `elem_ty`. Shared by [`ValueVisitor::visit_seq`] and the list helpers.
364fn read_seq<'de, A: SeqAccess<'de>>(mut seq: A, elem_ty: &stmt::Type) -> Result<Value, A::Error> {
365    let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
366    while let Some(value) = seq.next_element_seed(Seed { ty: elem_ty })? {
367        items.push(value);
368    }
369    Ok(Value::List(items))
370}
371
372/// A [`Visitor`] that accepts only a JSON array and decodes it into a
373/// `Value::List` of `elem_ty` elements. Used by the list helpers, whose caller
374/// holds the element type rather than a `Type::List`.
375struct ListVisitor<'a> {
376    elem_ty: &'a stmt::Type,
377}
378
379impl<'de> Visitor<'de> for ListVisitor<'_> {
380    type Value = Value;
381
382    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        write!(f, "a JSON array of {:?}", self.elem_ty)
384    }
385
386    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Value, A::Error> {
387        read_seq(seq, self.elem_ty)
388    }
389}
390
391/// Decode a JSON document (string) into a `stmt::Value` of type `ty`.
392pub fn from_str(text: &str, ty: &stmt::Type) -> Result<Value, serde_json::Error> {
393    let mut de = serde_json::Deserializer::from_str(text);
394    let value = Seed { ty }.deserialize(&mut de)?;
395    de.end()?;
396    Ok(value)
397}
398
399/// Decode a JSON document (UTF-8 bytes) into a `stmt::Value` of type `ty`.
400pub fn from_slice(bytes: &[u8], ty: &stmt::Type) -> Result<Value, serde_json::Error> {
401    let mut de = serde_json::Deserializer::from_slice(bytes);
402    let value = Seed { ty }.deserialize(&mut de)?;
403    de.end()?;
404    Ok(value)
405}
406
407/// Decode a JSON array (string) into a `Value::List`, using `elem_ty` as the
408/// per-element type.
409pub fn list_from_str(text: &str, elem_ty: &stmt::Type) -> Result<Value, serde_json::Error> {
410    let mut de = serde_json::Deserializer::from_str(text);
411    let value = de.deserialize_seq(ListVisitor { elem_ty })?;
412    de.end()?;
413    Ok(value)
414}
415
416/// Decode a JSON array (UTF-8 bytes) into a `Value::List`, using `elem_ty` as
417/// the per-element type.
418pub fn list_from_slice(bytes: &[u8], elem_ty: &stmt::Type) -> Result<Value, serde_json::Error> {
419    let mut de = serde_json::Deserializer::from_slice(bytes);
420    let value = de.deserialize_seq(ListVisitor { elem_ty })?;
421    de.end()?;
422    Ok(value)
423}