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. The structural
14//! [`check_no_duplicate_keys`] guard always stays on `serde_json`: an
15//! off-by-default fidelity pass, not the hot path, keeping the duplicate-key
16//! error classification identical across backends.
17//!
18//! `simd-json` rejects integer literals outside the `i64`/`u64` range that
19//! `serde_json` accepts as `f64` and normalizes `-0` to `0`. Both backends reject
20//! NUL bytes after numbers and underscore separators in numbers. `serde_json`
21//! has a recursion limit of 128; `simd-json` limits nesting to 1024. With
22//! `reject_duplicate_keys`, the `serde_json` guard applies its limit first.
23//! `simd-json` does not honor serde_json's `arbitrary_precision` / `raw_value` /
24//! `float_roundtrip` cargo features. See the JSON connector guide's Backends
25//! section.
26//!
27//! [`Buffers`]: https://docs.rs/simd-json
28
29use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, SeqAccess, Visitor};
30use std::borrow::Cow;
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 compiled code rather than a
36/// 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, so
49/// swapping the decode backend does 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,
55    /// making it a *data* error (a type mismatch, or the injected
56    /// duplicate-key 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, with 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 pads internally, so
102/// no trailing SIMD padding is appended here. `T: DeserializeOwned`
103/// borrows nothing out of the scratch, so it is free to be overwritten next
104/// call.
105#[cfg(feature = "simd")]
106#[inline]
107pub(crate) fn decode_one<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, DecodeError> {
108    SIMD.with(|cell| {
109        let (buf, buffers) = &mut *cell.borrow_mut();
110        buf.clear();
111        buf.extend_from_slice(bytes);
112        simd_json::serde::from_slice_with_buffers::<T>(buf.as_mut_slice(), buffers).map_err(|e| {
113            DecodeError {
114                is_data: false,
115                msg: e.to_string(),
116            }
117        })
118    })
119}
120
121/// Validate that no JSON object anywhere in `bytes` contains a duplicate key.
122///
123/// serde_json is silently last-value-wins on duplicate keys; this is the
124/// opt-in guard behind `reject_duplicate_keys`. It is a separate structural
125/// pass (a document is parsed twice when the guard is on, the documented
126/// cost), independent of the decode backend, so it stays on `serde_json` even
127/// when the decode backend is a SIMD parser.
128pub(crate) fn check_no_duplicate_keys(bytes: &[u8]) -> Result<(), DecodeError> {
129    // Deserializing into `DupGuard` walks the whole tree and errors on the
130    // first repeated key; the value is discarded.
131    serde_json::from_slice::<DupGuard>(bytes)
132        .map(|_| ())
133        .map_err(DecodeError::from)
134}
135
136/// A throwaway shape that accepts any JSON value but rejects an object with a
137/// repeated key at any depth. It stores nothing and is used only for its
138/// [`Visitor`] side effect.
139struct DupGuard;
140
141impl<'de> serde::Deserialize<'de> for DupGuard {
142    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143    where
144        D: Deserializer<'de>,
145    {
146        deserializer.deserialize_any(DupVisitor)
147    }
148}
149
150struct DupVisitor;
151
152/// An object key, borrowed from the input when it carries no escapes and copied
153/// when it does.
154struct Key<'de>(Cow<'de, str>);
155
156impl<'de> serde::Deserialize<'de> for Key<'de> {
157    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158    where
159        D: Deserializer<'de>,
160    {
161        deserializer.deserialize_str(KeyVisitor)
162    }
163}
164
165struct KeyVisitor;
166
167impl<'de> Visitor<'de> for KeyVisitor {
168    type Value = Key<'de>;
169
170    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        f.write_str("an object key")
172    }
173
174    fn visit_borrowed_str<E: de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
175        Ok(Key(Cow::Borrowed(v)))
176    }
177
178    /// The deserializer unescapes into a scratch buffer it clears before the
179    /// next key, so an escaped key has to be copied out.
180    fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
181        Ok(Key(Cow::Owned(v.to_owned())))
182    }
183}
184
185impl<'de> Visitor<'de> for DupVisitor {
186    type Value = DupGuard;
187
188    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.write_str("any JSON value with unique object keys")
190    }
191
192    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
193    where
194        A: MapAccess<'de>,
195    {
196        let mut seen: HashSet<Cow<'de, str>> = HashSet::new();
197        while let Some(Key(key)) = map.next_key::<Key<'de>>()? {
198            if let Some(dup) = seen.replace(key) {
199                return Err(de::Error::custom(format!("duplicate object key `{dup}`")));
200            }
201            // Recurse so nested objects are guarded too.
202            map.next_value::<DupGuard>()?;
203        }
204        Ok(DupGuard)
205    }
206
207    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
208    where
209        A: SeqAccess<'de>,
210    {
211        while seq.next_element::<DupGuard>()?.is_some() {}
212        Ok(DupGuard)
213    }
214
215    // Scalars carry no keys, so accept and ignore.
216    fn visit_bool<E>(self, _v: bool) -> Result<Self::Value, E> {
217        Ok(DupGuard)
218    }
219    fn visit_i64<E>(self, _v: i64) -> Result<Self::Value, E> {
220        Ok(DupGuard)
221    }
222    fn visit_u64<E>(self, _v: u64) -> Result<Self::Value, E> {
223        Ok(DupGuard)
224    }
225    fn visit_f64<E>(self, _v: f64) -> Result<Self::Value, E> {
226        Ok(DupGuard)
227    }
228    fn visit_str<E>(self, _v: &str) -> Result<Self::Value, E> {
229        Ok(DupGuard)
230    }
231    fn visit_none<E>(self) -> Result<Self::Value, E> {
232        Ok(DupGuard)
233    }
234    fn visit_unit<E>(self) -> Result<Self::Value, E> {
235        Ok(DupGuard)
236    }
237    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
238    where
239        D: Deserializer<'de>,
240    {
241        deserializer.deserialize_any(DupVisitor)
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::{Key, check_no_duplicate_keys};
248    use serde::Deserialize;
249    use serde::de::{IgnoredAny, MapAccess, Visitor};
250    use std::borrow::Cow;
251    use std::fmt;
252
253    struct ObjectKeys<'de>(Vec<Cow<'de, str>>);
254
255    impl<'de> Deserialize<'de> for ObjectKeys<'de> {
256        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257        where
258            D: serde::Deserializer<'de>,
259        {
260            deserializer.deserialize_map(ObjectKeysVisitor)
261        }
262    }
263
264    struct ObjectKeysVisitor;
265
266    impl<'de> Visitor<'de> for ObjectKeysVisitor {
267        type Value = ObjectKeys<'de>;
268
269        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270            f.write_str("a JSON object")
271        }
272
273        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
274        where
275            A: MapAccess<'de>,
276        {
277            let mut keys = Vec::new();
278            while let Some(Key(key)) = map.next_key::<Key<'de>>()? {
279                map.next_value::<IgnoredAny>()?;
280                keys.push(key);
281            }
282            Ok(ObjectKeys(keys))
283        }
284    }
285
286    #[test]
287    fn an_unescaped_key_borrows_from_the_payload() {
288        let ObjectKeys(keys) = serde_json::from_slice(br#"{"abc":1,"\u0061bc":2}"#).unwrap();
289        assert!(matches!(keys[0], Cow::Borrowed("abc")));
290        assert!(matches!(keys[1], Cow::Owned(_)));
291    }
292
293    #[test]
294    fn duplicate_key_names_the_key() {
295        let err = check_no_duplicate_keys(br#"{"id":1,"id":2}"#).unwrap_err();
296        assert!(err.is_data, "{err}");
297        assert!(
298            err.to_string().starts_with("duplicate object key `id`"),
299            "{err}"
300        );
301    }
302
303    #[test]
304    fn escaped_spelling_is_the_same_key() {
305        let err = check_no_duplicate_keys(br#"{"\u0061":1,"a":2}"#).unwrap_err();
306        assert!(err.is_data, "{err}");
307        assert!(
308            err.to_string().starts_with("duplicate object key `a`"),
309            "{err}"
310        );
311    }
312
313    /// The set holds the plain spelling and the escaped one probes it, the
314    /// reverse of `escaped_spelling_is_the_same_key`. The message names the key
315    /// the set already held.
316    #[test]
317    fn plain_spelling_matches_an_escaped_duplicate() {
318        let err = check_no_duplicate_keys(br#"{"a":1,"\u0061":2}"#).unwrap_err();
319        assert!(err.is_data, "{err}");
320        assert!(
321            err.to_string().starts_with("duplicate object key `a`"),
322            "{err}"
323        );
324    }
325
326    #[test]
327    fn same_key_in_sibling_scopes_is_clean() {
328        check_no_duplicate_keys(br#"{"a":{"a":1},"b":[{"a":1},{"a":2}]}"#).unwrap();
329    }
330}