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