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            other => Err(S::Error::custom(format!("cannot encode {other:?} as JSON"))),
117        }
118    }
119}
120
121/// Encode a `stmt::Value` as a JSON string.
122pub fn to_string(value: &Value) -> Result<String, serde_json::Error> {
123    serde_json::to_string(&Encode(value))
124}
125
126/// Encode a `stmt::Value` as JSON UTF-8 bytes.
127pub fn to_vec(value: &Value) -> Result<Vec<u8>, serde_json::Error> {
128    serde_json::to_vec(&Encode(value))
129}
130
131// ============================================================================
132// Decoding: JSON text -> stmt::Value, directed by stmt::Type
133// ============================================================================
134
135/// A type-directed [`serde::de::DeserializeSeed`] that decodes JSON straight
136/// into a `stmt::Value` of the seed's `stmt::Type` — no `serde_json::Value`
137/// intermediate. The type is required because the wire form is ambiguous for
138/// scalars (`Uuid`/`String` are both strings; the integer widths are all
139/// numbers). A structural `Type::Object` position switches to shape-directed
140/// decoding ([`AnySeed`]).
141pub struct Seed<'a> {
142    /// The expected type of the value being decoded.
143    pub ty: &'a stmt::Type,
144}
145
146impl<'de> DeserializeSeed<'de> for Seed<'_> {
147    type Value = Value;
148
149    fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<Value, D::Error> {
150        // JSON is self-describing, so `deserialize_any` lets the parser drive
151        // the visit method by token; each method coerces using `self.ty`.
152        de.deserialize_any(ValueVisitor { ty: self.ty })
153    }
154}
155
156struct ValueVisitor<'a> {
157    ty: &'a stmt::Type,
158}
159
160/// A shape-directed [`serde::de::DeserializeSeed`] for the interior of a
161/// document: every JSON token decodes to its wire-natural `stmt::Value` —
162/// strings stay `String`, numbers decode by integer fit (`I64`, then `U64`,
163/// then `F64`), objects become named `Value::Object`s in wire key order. The
164/// engine casts the leaves to their field types when it raises the object to
165/// a positional record.
166struct AnySeed;
167
168impl<'de> DeserializeSeed<'de> for AnySeed {
169    type Value = Value;
170
171    fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<Value, D::Error> {
172        de.deserialize_any(AnyVisitor)
173    }
174}
175
176struct AnyVisitor;
177
178impl<'de> Visitor<'de> for AnyVisitor {
179    type Value = Value;
180
181    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        write!(f, "a JSON value inside a document")
183    }
184
185    fn visit_unit<E: serde::de::Error>(self) -> Result<Value, E> {
186        Ok(Value::Null)
187    }
188
189    fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<Value, E> {
190        Ok(Value::Bool(v))
191    }
192
193    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Value, E> {
194        Ok(Value::I64(v))
195    }
196
197    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Value, E> {
198        // Integer fit: values representable as `i64` decode to `I64` so the
199        // same stored number always has the same wire shape.
200        Ok(match i64::try_from(v) {
201            Ok(v) => Value::I64(v),
202            Err(_) => Value::U64(v),
203        })
204    }
205
206    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Value, E> {
207        Ok(Value::F64(v))
208    }
209
210    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Value, E> {
211        Ok(Value::String(v.to_owned()))
212    }
213
214    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Value, A::Error> {
215        let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
216        while let Some(value) = seq.next_element_seed(AnySeed)? {
217            items.push(value);
218        }
219        Ok(Value::List(items))
220    }
221
222    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Value, A::Error> {
223        let mut entries = Vec::new();
224        while let Some(key) = map.next_key::<String>()? {
225            entries.push((key, map.next_value_seed(AnySeed)?));
226        }
227        Ok(Value::Object(stmt::ValueObject::from_vec(entries)))
228    }
229}
230
231impl<'de> Visitor<'de> for ValueVisitor<'_> {
232    type Value = Value;
233
234    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        write!(f, "a JSON value decodable as {:?}", self.ty)
236    }
237
238    fn visit_unit<E: serde::de::Error>(self) -> Result<Value, E> {
239        // A JSON `null` — or an absent object key — is `Value::Null`,
240        // regardless of the expected type (round-trips an `Option::None`).
241        Ok(Value::Null)
242    }
243
244    fn visit_bool<E: serde::de::Error>(self, v: bool) -> Result<Value, E> {
245        match self.ty {
246            stmt::Type::Bool => Ok(Value::Bool(v)),
247            other => Err(E::custom(format!(
248                "unexpected JSON bool for type {other:?}"
249            ))),
250        }
251    }
252
253    fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Value, E> {
254        int_to_value(self.ty, v as i128)
255    }
256
257    fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Value, E> {
258        int_to_value(self.ty, v as i128)
259    }
260
261    fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Value, E> {
262        match self.ty {
263            stmt::Type::F32 => Ok(Value::F32(v as f32)),
264            stmt::Type::F64 => Ok(Value::F64(v)),
265            other => Err(E::custom(format!(
266                "unexpected JSON float for type {other:?}"
267            ))),
268        }
269    }
270
271    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Value, E> {
272        match self.ty {
273            stmt::Type::String => Ok(Value::String(v.to_owned())),
274            stmt::Type::Uuid => Ok(Value::Uuid(v.parse().map_err(E::custom)?)),
275            #[cfg(feature = "rust_decimal")]
276            stmt::Type::Decimal => Ok(Value::Decimal(v.parse().map_err(E::custom)?)),
277            #[cfg(feature = "bigdecimal")]
278            stmt::Type::BigDecimal => Ok(Value::BigDecimal(v.parse().map_err(E::custom)?)),
279            #[cfg(feature = "jiff")]
280            stmt::Type::Timestamp => Ok(Value::Timestamp(v.parse().map_err(E::custom)?)),
281            #[cfg(feature = "jiff")]
282            stmt::Type::Zoned => Ok(Value::Zoned(v.parse().map_err(E::custom)?)),
283            #[cfg(feature = "jiff")]
284            stmt::Type::Date => Ok(Value::Date(v.parse().map_err(E::custom)?)),
285            #[cfg(feature = "jiff")]
286            stmt::Type::Time => Ok(Value::Time(v.parse().map_err(E::custom)?)),
287            #[cfg(feature = "jiff")]
288            stmt::Type::DateTime => Ok(Value::DateTime(v.parse().map_err(E::custom)?)),
289            other => Err(E::custom(format!(
290                "unexpected JSON string for type {other:?}"
291            ))),
292        }
293    }
294
295    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Value, A::Error> {
296        match self.ty {
297            stmt::Type::List(elem) => read_seq(seq, elem),
298            other => Err(A::Error::custom(format!(
299                "unexpected JSON array for type {other:?}"
300            ))),
301        }
302    }
303
304    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Value, A::Error> {
305        // A `#[document]` column is typed by the structural `Type::Object`:
306        // decode the JSON object shape-directed, keys as stored. The engine
307        // raises the result to the embed's positional record — the field
308        // layout is a model-level concept this codec does not know.
309        match self.ty {
310            stmt::Type::Object => AnyVisitor.visit_map(map),
311            other => Err(A::Error::custom(format!(
312                "unexpected JSON object for type {other:?}"
313            ))),
314        }
315    }
316}
317
318/// Coerce a JSON integer (widened to `i128` so signed and unsigned tokens share
319/// one path) into the integer or float `Value` named by `ty`. Integer
320/// conversions are checked: a stored value outside the target type's range
321/// (e.g. `-1` decoded as `u64`) is a decode error naming the mismatch, not a
322/// silent wraparound.
323fn int_to_value<E: serde::de::Error>(ty: &stmt::Type, v: i128) -> Result<Value, E> {
324    fn checked<T: TryFrom<i128>, E: serde::de::Error>(v: i128, ty: &stmt::Type) -> Result<T, E> {
325        T::try_from(v)
326            .map_err(|_| E::custom(format!("JSON integer {v} is out of range for type {ty:?}")))
327    }
328
329    Ok(match ty {
330        stmt::Type::I8 => Value::I8(checked(v, ty)?),
331        stmt::Type::I16 => Value::I16(checked(v, ty)?),
332        stmt::Type::I32 => Value::I32(checked(v, ty)?),
333        stmt::Type::I64 => Value::I64(checked(v, ty)?),
334        stmt::Type::U8 => Value::U8(checked(v, ty)?),
335        stmt::Type::U16 => Value::U16(checked(v, ty)?),
336        stmt::Type::U32 => Value::U32(checked(v, ty)?),
337        stmt::Type::U64 => Value::U64(checked(v, ty)?),
338        stmt::Type::F32 => Value::F32(v as f32),
339        stmt::Type::F64 => Value::F64(v as f64),
340        other => {
341            return Err(E::custom(format!(
342                "unexpected JSON integer for type {other:?}"
343            )));
344        }
345    })
346}
347
348/// Decode a JSON array into a `Value::List`, seeding each element with
349/// `elem_ty`. Shared by [`ValueVisitor::visit_seq`] and the list helpers.
350fn read_seq<'de, A: SeqAccess<'de>>(mut seq: A, elem_ty: &stmt::Type) -> Result<Value, A::Error> {
351    let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
352    while let Some(value) = seq.next_element_seed(Seed { ty: elem_ty })? {
353        items.push(value);
354    }
355    Ok(Value::List(items))
356}
357
358/// A [`Visitor`] that accepts only a JSON array and decodes it into a
359/// `Value::List` of `elem_ty` elements. Used by the list helpers, whose caller
360/// holds the element type rather than a `Type::List`.
361struct ListVisitor<'a> {
362    elem_ty: &'a stmt::Type,
363}
364
365impl<'de> Visitor<'de> for ListVisitor<'_> {
366    type Value = Value;
367
368    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        write!(f, "a JSON array of {:?}", self.elem_ty)
370    }
371
372    fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Value, A::Error> {
373        read_seq(seq, self.elem_ty)
374    }
375}
376
377/// Decode a JSON document (string) into a `stmt::Value` of type `ty`.
378pub fn from_str(text: &str, ty: &stmt::Type) -> Result<Value, serde_json::Error> {
379    let mut de = serde_json::Deserializer::from_str(text);
380    let value = Seed { ty }.deserialize(&mut de)?;
381    de.end()?;
382    Ok(value)
383}
384
385/// Decode a JSON document (UTF-8 bytes) into a `stmt::Value` of type `ty`.
386pub fn from_slice(bytes: &[u8], ty: &stmt::Type) -> Result<Value, serde_json::Error> {
387    let mut de = serde_json::Deserializer::from_slice(bytes);
388    let value = Seed { ty }.deserialize(&mut de)?;
389    de.end()?;
390    Ok(value)
391}
392
393/// Decode a JSON array (string) into a `Value::List`, using `elem_ty` as the
394/// per-element type.
395pub fn list_from_str(text: &str, elem_ty: &stmt::Type) -> Result<Value, serde_json::Error> {
396    let mut de = serde_json::Deserializer::from_str(text);
397    let value = de.deserialize_seq(ListVisitor { elem_ty })?;
398    de.end()?;
399    Ok(value)
400}
401
402/// Decode a JSON array (UTF-8 bytes) into a `Value::List`, using `elem_ty` as
403/// the per-element type.
404pub fn list_from_slice(bytes: &[u8], elem_ty: &stmt::Type) -> Result<Value, serde_json::Error> {
405    let mut de = serde_json::Deserializer::from_slice(bytes);
406    let value = de.deserialize_seq(ListVisitor { elem_ty })?;
407    de.end()?;
408    Ok(value)
409}