Skip to main content

spate_json/
backend.rs

1//! The byte-slice → value decode seam.
2//!
3//! Every JSON document is decoded from an in-memory `&[u8]` slice — never
4//! `from_reader`, which serde_json's own docs note is slower than reading to a
5//! slice first and cannot borrow. The default backend is `serde_json`; the
6//! opt-in `simd` Cargo feature swaps [`decode_one`] to `simd-json`, leaving the
7//! framing and emit logic in `deser.rs` untouched.
8//!
9//! `simd-json` parses a *mutable* buffer in place (it unescapes strings into the
10//! buffer), so the borrowed payload is copied into a reused thread-local scratch
11//! first, and the parser's own scratch [`Buffers`] are likewise reused across
12//! calls — both are per-message allocations a production integration avoids, so
13//! the backend is charged only the unavoidable memcpy (measured negligible; see
14//! the deserialization-formats benchmark study for the serde_json/simd-json
15//! A/B). The structural [`check_no_duplicate_keys`] guard always stays on
16//! `serde_json`: an off-by-default fidelity pass, not the hot path, keeping the
17//! duplicate-key error classification identical across backends.
18//!
19//! Decode itself is **not** byte-for-byte identical across the two backends —
20//! `simd-json` is a different parser. It rejects integer literals outside the
21//! `i64`/`u64` range that `serde_json` accepts (coercing to `f64`), so under
22//! `simd` such a document surfaces as a `malformed` decode error where
23//! `serde_json` would succeed; it normalizes `-0` to `0`; and, being a distinct
24//! parser, it does not honor serde_json's `arbitrary_precision` / `raw_value` /
25//! `float_roundtrip` cargo features. This is inherent to swapping parsers, not a
26//! bug to reconcile here — see the JSON connector guide's Backends section.
27//!
28//! [`Buffers`]: https://docs.rs/simd-json
29
30use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
31use std::collections::HashSet;
32use std::fmt;
33
34/// Identifier of the compiled decode backend, surfaced for benchmark and
35/// telemetry labels so an arm is tagged from the *actually compiled* code
36/// rather than a hand-passed label. The default backend is `serde_json`; the
37/// opt-in `simd` Cargo feature overrides it.
38#[cfg(feature = "simd")]
39pub const BACKEND_ID: &str = "simd-json";
40/// Identifier of the compiled decode backend (see the `simd` variant).
41#[cfg(not(feature = "simd"))]
42pub const BACKEND_ID: &str = "serde_json";
43
44/// A backend-agnostic decode failure at the seam.
45///
46/// The framing/error-policy layer in `deser.rs` must classify a failure
47/// (`is_data`, to label a metric `duplicate_key` vs `malformed`) and report it
48/// (`Display`), but must not name a concrete backend's error type — swapping
49/// the decode backend must not ripple into `deser.rs`. Each backend maps its
50/// native error into this on the way out of [`decode_one`] /
51/// [`check_no_duplicate_keys`].
52#[derive(Debug)]
53pub(crate) struct DecodeError {
54    /// True when the input was well-formed JSON but semantically rejected — a
55    /// *data* error (a type mismatch, or the injected duplicate-key
56    /// rejection) — as opposed to a syntax/EOF error. Drives the
57    /// `duplicate_key` vs `malformed` metric label.
58    pub(crate) is_data: bool,
59    msg: String,
60}
61
62impl fmt::Display for DecodeError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.write_str(&self.msg)
65    }
66}
67
68impl From<serde_json::Error> for DecodeError {
69    fn from(e: serde_json::Error) -> Self {
70        DecodeError {
71            is_data: e.is_data(),
72            msg: e.to_string(),
73        }
74    }
75}
76
77/// Decode one complete JSON document from `bytes` into `T` (serde_json backend).
78///
79/// serde_json borrows the immutable payload slice directly — no copy.
80#[cfg(not(feature = "simd"))]
81#[inline]
82pub(crate) fn decode_one<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, DecodeError> {
83    serde_json::from_slice(bytes).map_err(DecodeError::from)
84}
85
86// Reused per-thread scratch for the simd-json backend: the mutable copy target
87// plus the parser's own `Buffers` (tape/string/structural indexes). simd-json
88// parses destructively in place and allocates parser scratch per call by
89// default; reusing both across calls charges the backend only the unavoidable
90// memcpy, matching a production integration. `bytes` (the borrowed source
91// payload) is never mutated, so at-least-once replay is safe.
92#[cfg(feature = "simd")]
93thread_local! {
94    static SIMD: std::cell::RefCell<(Vec<u8>, simd_json::Buffers)> =
95        std::cell::RefCell::new((Vec::new(), simd_json::Buffers::new(0)));
96}
97
98/// Decode one complete JSON document from `bytes` into `T` (simd-json backend).
99///
100/// Copies `bytes` into the reused thread-local scratch and parses that in place
101/// with reused [`Buffers`](simd_json::Buffers). simd-json 0.17 pads internally
102/// (the RUSTSEC-2019-0008 fix reads the final block through a padded stack
103/// buffer), so no trailing SIMD padding is appended. `T: DeserializeOwned`
104/// borrows nothing out of the scratch, so it is free to be overwritten next
105/// call.
106#[cfg(feature = "simd")]
107#[inline]
108pub(crate) fn decode_one<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, DecodeError> {
109    SIMD.with(|cell| {
110        let (buf, buffers) = &mut *cell.borrow_mut();
111        buf.clear();
112        buf.extend_from_slice(bytes);
113        simd_json::serde::from_slice_with_buffers::<T>(buf.as_mut_slice(), buffers).map_err(|e| {
114            DecodeError {
115                is_data: false,
116                msg: e.to_string(),
117            }
118        })
119    })
120}
121
122/// Validate that no JSON object anywhere in `bytes` contains a duplicate key.
123///
124/// serde_json is silently last-value-wins on duplicate keys; this is the
125/// opt-in guard behind `reject_duplicate_keys`. It is a separate structural
126/// pass (a document is parsed twice when the guard is on — the documented
127/// cost), independent of the decode backend, so it stays on `serde_json` even
128/// when the decode backend is a SIMD parser.
129pub(crate) fn check_no_duplicate_keys(bytes: &[u8]) -> Result<(), DecodeError> {
130    // Deserializing into `DupGuard` walks the whole tree and errors on the
131    // first repeated key; the value is discarded.
132    serde_json::from_slice::<DupGuard>(bytes)
133        .map(|_| ())
134        .map_err(DecodeError::from)
135}
136
137/// A throwaway shape that accepts any JSON value but rejects an object with a
138/// repeated key at any depth. It stores nothing — it exists only for its
139/// [`Visitor`] side effect.
140struct DupGuard;
141
142impl<'de> serde::Deserialize<'de> for DupGuard {
143    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144    where
145        D: Deserializer<'de>,
146    {
147        deserializer.deserialize_any(DupVisitor)
148    }
149}
150
151struct DupVisitor;
152
153impl<'de> Visitor<'de> for DupVisitor {
154    type Value = DupGuard;
155
156    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.write_str("any JSON value with unique object keys")
158    }
159
160    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
161    where
162        A: MapAccess<'de>,
163    {
164        let mut seen: HashSet<String> = HashSet::new();
165        while let Some(key) = map.next_key::<String>()? {
166            if !seen.insert(key.clone()) {
167                return Err(de::Error::custom(format!("duplicate object key `{key}`")));
168            }
169            // Recurse so nested objects are guarded too.
170            map.next_value::<DupGuard>()?;
171        }
172        Ok(DupGuard)
173    }
174
175    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
176    where
177        A: SeqAccess<'de>,
178    {
179        while seq.next_element::<DupGuard>()?.is_some() {}
180        Ok(DupGuard)
181    }
182
183    // Scalars carry no keys — accept and ignore.
184    fn visit_bool<E>(self, _v: bool) -> Result<Self::Value, E> {
185        Ok(DupGuard)
186    }
187    fn visit_i64<E>(self, _v: i64) -> Result<Self::Value, E> {
188        Ok(DupGuard)
189    }
190    fn visit_u64<E>(self, _v: u64) -> Result<Self::Value, E> {
191        Ok(DupGuard)
192    }
193    fn visit_f64<E>(self, _v: f64) -> Result<Self::Value, E> {
194        Ok(DupGuard)
195    }
196    fn visit_str<E>(self, _v: &str) -> Result<Self::Value, E> {
197        Ok(DupGuard)
198    }
199    fn visit_none<E>(self) -> Result<Self::Value, E> {
200        Ok(DupGuard)
201    }
202    fn visit_unit<E>(self) -> Result<Self::Value, E> {
203        Ok(DupGuard)
204    }
205    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
206    where
207        D: Deserializer<'de>,
208    {
209        deserializer.deserialize_any(DupVisitor)
210    }
211}