facet_json/
lib.rs

1#![warn(missing_docs)]
2// Allow unsafe code when cranelift feature is enabled (required for JIT compilation)
3#![cfg_attr(not(feature = "cranelift"), deny(unsafe_code))]
4#![doc = include_str!("../README.md")]
5
6extern crate alloc;
7
8// Re-export span types from facet-reflect
9pub use facet_reflect::{Span, Spanned};
10
11mod deserialize;
12pub use deserialize::{
13    JsonDeserializer, JsonError, JsonErrorKind, from_slice, from_slice_borrowed, from_str,
14    from_str_borrowed,
15};
16
17mod serialize;
18pub use serialize::*;
19
20mod scanner;
21pub use scanner::{
22    NumberHint, ScanError, ScanErrorKind, Scanner, SpannedToken, Token as ScanToken,
23};
24
25mod adapter;
26pub use adapter::{
27    AdapterError, AdapterErrorKind, SliceAdapter, SpannedAdapterToken, Token as AdapterToken,
28    TokenSource,
29};
30
31#[cfg(feature = "streaming")]
32mod streaming;
33#[cfg(feature = "futures-io")]
34pub use streaming::from_async_reader_futures;
35#[cfg(feature = "tokio")]
36pub use streaming::from_async_reader_tokio;
37#[cfg(feature = "streaming")]
38pub use streaming::{StreamingAdapter, from_reader};
39
40mod scan_buffer;
41pub use scan_buffer::ScanBuffer;
42
43#[cfg(feature = "std")]
44mod reader;
45#[cfg(feature = "tokio")]
46pub use reader::AsyncJsonReader;
47#[cfg(feature = "futures-io")]
48pub use reader::FuturesJsonReader;
49#[cfg(feature = "std")]
50pub use reader::{JsonReader, JsonToken, ReaderError, SpannedJsonToken};
51
52mod raw_json;
53pub use raw_json::RawJson;
54
55mod json;
56pub use json::Json;
57
58/// JIT-compiled JSON deserialization using Cranelift.
59///
60/// This module provides a JIT-compiled JSON deserializer that generates native
61/// code specialized for each type's exact memory layout. On first call for a type,
62/// it compiles a specialized deserializer using Cranelift. Subsequent calls use
63/// the cached native code directly.
64///
65/// # Example
66///
67/// ```ignore
68/// use facet::Facet;
69/// use facet_json::cranelift;
70///
71/// #[derive(Facet)]
72/// struct Point { x: f64, y: f64 }
73///
74/// let point: Point = cranelift::from_str(r#"{"x": 1.0, "y": 2.0}"#).unwrap();
75/// ```
76#[cfg(feature = "cranelift")]
77pub mod cranelift;
78
79#[cfg(feature = "axum")]
80mod axum;
81#[cfg(feature = "axum")]
82pub use self::axum::JsonRejection;
83
84/// Re-export the `Write` trait from facet-core for backwards compatibility.
85///
86/// This trait is used by the JSON serializer to write output without depending
87/// on `std::io::Write`, enabling `no_std` support.
88pub use facet_core::Write as JsonWrite;
89
90/// Properly escapes and writes a JSON string
91#[inline]
92fn write_json_string<W: JsonWrite>(writer: &mut W, s: &str) {
93    // Just a little bit of text on how it works. There are two main steps:
94    // 1. Check if the string is completely ASCII and doesn't contain any quotes or backslashes or
95    //    control characters. This is the fast path, because it means that the bytes can be written
96    //    as they are, without any escaping needed. In this case we go over the string in windows
97    //    of 16 bytes (which is completely arbitrary, maybe find some real world data to tune this
98    //    with? I don't know and you don't have to do this dear reader.) and we just feed them into
99    //    the writer.
100    // 2. If the string is not completely ASCII or contains quotes or backslashes or control
101    //    characters, we need to escape them. This is the slow path, because it means that we need
102    //    to write the bytes one by one, and we need to figure out where to put the escapes. So we
103    //    just call `write_json_escaped_char` for each character.
104
105    const STEP_SIZE: usize = Window::BITS as usize / 8;
106    type Window = u128;
107    type Chunk = [u8; STEP_SIZE];
108
109    writer.write(b"\"");
110
111    let mut s = s;
112    while let Some(Ok(chunk)) = s.as_bytes().get(..STEP_SIZE).map(Chunk::try_from) {
113        let window = Window::from_ne_bytes(chunk);
114        // Our window is a concatenation of u8 values. For each value, we need to make sure that:
115        // 1. It is ASCII (i.e. the first bit of the u8 is 0, so u8 & 0x80 == 0)
116        // 2. It does not contain quotes (i.e. 0x22)
117        // 3. It does not contain backslashes (i.e. 0x5c)
118        // 4. It does not contain control characters (i.e. characters below 32, including 0)
119        //    This means the bit above the 1st, 2nd or 3rd bit must be set, so u8 & 0xe0 != 0
120        let completely_ascii = window & 0x80808080808080808080808080808080 == 0;
121        let quote_free = !contains_0x22(window);
122        let backslash_free = !contains_0x5c(window);
123        let control_char_free = top_three_bits_set(window);
124        if completely_ascii && quote_free && backslash_free && control_char_free {
125            // Yay! Whack it into the writer!
126            writer.write(&chunk);
127            s = &s[STEP_SIZE..];
128        } else {
129            // Ahw one of the conditions not met. Let's take our time and artisanally handle each
130            // character.
131            let mut chars = s.chars();
132            let mut count = STEP_SIZE;
133            for c in &mut chars {
134                write_json_escaped_char(writer, c);
135                count = count.saturating_sub(c.len_utf8());
136                if count == 0 {
137                    // Done with our chunk
138                    break;
139                }
140            }
141            s = chars.as_str();
142        }
143    }
144
145    // In our loop we checked that we were able to consume at least `STEP_SIZE` bytes every
146    // iteration. That means there might be a small remnant at the end that we can handle in the
147    // slow method.
148    for c in s.chars() {
149        write_json_escaped_char(writer, c);
150    }
151
152    writer.write(b"\"")
153}
154
155/// Writes a single JSON escaped character
156#[inline]
157fn write_json_escaped_char<W: JsonWrite>(writer: &mut W, c: char) {
158    match c {
159        '"' => writer.write(b"\\\""),
160        '\\' => writer.write(b"\\\\"),
161        '\n' => writer.write(b"\\n"),
162        '\r' => writer.write(b"\\r"),
163        '\t' => writer.write(b"\\t"),
164        '\u{08}' => writer.write(b"\\b"),
165        '\u{0C}' => writer.write(b"\\f"),
166        c if c.is_ascii_control() => {
167            let code_point = c as u32;
168            // Extract individual hex digits (nibbles) from the code point
169            let to_hex = |d: u32| char::from_digit(d, 16).unwrap() as u8;
170            let buf = [
171                b'\\',
172                b'u',
173                to_hex((code_point >> 12) & 0xF),
174                to_hex((code_point >> 8) & 0xF),
175                to_hex((code_point >> 4) & 0xF),
176                to_hex(code_point & 0xF),
177            ];
178            writer.write(&buf);
179        }
180        c if c.is_ascii() => {
181            writer.write(&[c as u8]);
182        }
183        c => {
184            let mut buf = [0; 4];
185            let len = c.encode_utf8(&mut buf).len();
186            writer.write(&buf[..len])
187        }
188    }
189}
190
191#[inline]
192fn contains_0x22(val: u128) -> bool {
193    let xor_result = val ^ 0x22222222222222222222222222222222;
194    let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
195        & !xor_result
196        & 0x80808080808080808080808080808080;
197    has_zero != 0
198}
199
200#[inline]
201fn contains_0x5c(val: u128) -> bool {
202    let xor_result = val ^ 0x5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c;
203    let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
204        & !xor_result
205        & 0x80808080808080808080808080808080;
206    has_zero != 0
207}
208
209/// For each of the 16 u8s that make up a u128, check if the top three bits are set.
210#[inline]
211fn top_three_bits_set(value: u128) -> bool {
212    let xor_result = value & 0xe0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0;
213    let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
214        & !xor_result
215        & 0x80808080808080808080808080808080;
216    has_zero == 0
217}
218
219// ============================================================================
220// Test helpers for multi-mode testing
221// ============================================================================
222
223/// Macro to run deserialize tests in multiple modes (slice, streaming, cranelift).
224///
225/// Generates test modules that run the same test body with different
226/// deserializers:
227/// - `slice` - uses `from_str` (slice-based parsing) - always enabled
228/// - `streaming` - uses `from_reader` (streaming from a reader) - requires `streaming` feature
229/// - `cranelift` - uses `cranelift::from_str` (JIT-compiled) - requires `cranelift` feature
230///
231/// # Usage
232///
233/// ```ignore
234/// facet_json::test_modes! {
235///     #[test]
236///     fn my_test() {
237///         #[derive(facet::Facet, Debug, PartialEq)]
238///         struct Foo { x: i32 }
239///
240///         let result: Foo = deserialize(r#"{"x": 42}"#).unwrap();
241///         assert_eq!(result, Foo { x: 42 });
242///     }
243/// }
244/// ```
245///
246/// The macro provides a `deserialize` function that takes `&str` and returns
247/// `Result<T, JsonError>`.
248///
249/// # Skipping streaming mode
250///
251/// Use `#[skip_streaming]` to skip a test in streaming mode
252/// (for features like `#[facet(flatten)]` or `RawJson` that aren't supported):
253///
254/// ```ignore
255/// facet_json::test_modes! {
256///     #[skip_streaming]
257///     #[test]
258///     fn test_flatten() {
259///         // This test only runs in slice and cranelift modes
260///     }
261/// }
262/// ```
263// =============================================================================
264// Case 1: Both streaming and cranelift features enabled
265// =============================================================================
266#[macro_export]
267#[cfg(all(feature = "streaming", feature = "cranelift"))]
268macro_rules! test_modes {
269    ($($content:tt)*) => {
270        /// Tests using from_str (slice-based)
271        mod slice {
272            #[allow(unused_imports)]
273            use super::*;
274
275            #[allow(dead_code)]
276            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
277                $crate::from_str(input)
278            }
279
280            $crate::__test_modes_inner!(@skip_none $($content)*);
281        }
282
283        /// Tests using from_reader (streaming)
284        mod streaming {
285            #[allow(unused_imports)]
286            use super::*;
287
288            #[allow(dead_code)]
289            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
290                $crate::from_reader(::std::io::Cursor::new(input))
291            }
292
293            $crate::__test_modes_inner!(@skip_streaming $($content)*);
294        }
295
296        /// Tests using JIT-compiled deserializer
297        mod cranelift {
298            #[allow(unused_imports)]
299            use super::*;
300
301            #[allow(dead_code)]
302            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
303                $crate::cranelift::from_str(input)
304            }
305
306            $crate::__test_modes_inner!(@skip_none $($content)*);
307        }
308    };
309}
310
311#[macro_export]
312#[cfg(all(feature = "streaming", feature = "cranelift"))]
313#[doc(hidden)]
314macro_rules! __test_modes_inner {
315    // Skip #[skip_streaming] when in streaming mode
316    (@skip_streaming #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
317        $crate::__test_modes_inner!(@skip_streaming $($rest)*);
318    };
319
320    // In non-streaming modes, ignore the #[skip_streaming] attribute
321    (@skip_none #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
322        #[test]
323        fn $name() {
324            ::facet_testhelpers::setup();
325            $body
326        }
327        $crate::__test_modes_inner!(@skip_none $($rest)*);
328    };
329
330    // Regular test - emit it
331    (@$mode:ident #[test] fn $name:ident() $body:block $($rest:tt)*) => {
332        #[test]
333        fn $name() {
334            ::facet_testhelpers::setup();
335            $body
336        }
337        $crate::__test_modes_inner!(@$mode $($rest)*);
338    };
339
340    // Base case - done
341    (@$mode:ident) => {};
342}
343
344// =============================================================================
345// Case 2: Only streaming feature enabled (no cranelift)
346// =============================================================================
347/// See the main `test_modes` documentation above.
348#[macro_export]
349#[cfg(all(feature = "streaming", not(feature = "cranelift")))]
350macro_rules! test_modes {
351    ($($content:tt)*) => {
352        /// Tests using from_str (slice-based)
353        mod slice {
354            #[allow(unused_imports)]
355            use super::*;
356
357            #[allow(dead_code)]
358            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
359                $crate::from_str(input)
360            }
361
362            $crate::__test_modes_inner!(@skip_none $($content)*);
363        }
364
365        /// Tests using from_reader (streaming)
366        mod streaming {
367            #[allow(unused_imports)]
368            use super::*;
369
370            #[allow(dead_code)]
371            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
372                $crate::from_reader(::std::io::Cursor::new(input))
373            }
374
375            $crate::__test_modes_inner!(@skip_streaming $($content)*);
376        }
377    };
378}
379
380#[macro_export]
381#[cfg(all(feature = "streaming", not(feature = "cranelift")))]
382#[doc(hidden)]
383macro_rules! __test_modes_inner {
384    // Skip #[skip_streaming] when in streaming mode
385    (@skip_streaming #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
386        $crate::__test_modes_inner!(@skip_streaming $($rest)*);
387    };
388
389    // In non-streaming modes, ignore the #[skip_streaming] attribute
390    (@skip_none #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
391        #[test]
392        fn $name() {
393            ::facet_testhelpers::setup();
394            $body
395        }
396        $crate::__test_modes_inner!(@skip_none $($rest)*);
397    };
398
399    // Regular test - emit it
400    (@$mode:ident #[test] fn $name:ident() $body:block $($rest:tt)*) => {
401        #[test]
402        fn $name() {
403            ::facet_testhelpers::setup();
404            $body
405        }
406        $crate::__test_modes_inner!(@$mode $($rest)*);
407    };
408
409    // Base case - done
410    (@$mode:ident) => {};
411}
412
413// =============================================================================
414// Case 3: Only cranelift feature enabled (no streaming)
415// =============================================================================
416/// See the main `test_modes` documentation above.
417#[macro_export]
418#[cfg(all(not(feature = "streaming"), feature = "cranelift"))]
419macro_rules! test_modes {
420    ($($content:tt)*) => {
421        /// Tests using from_str (slice-based)
422        mod slice {
423            #[allow(unused_imports)]
424            use super::*;
425
426            #[allow(dead_code)]
427            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
428                $crate::from_str(input)
429            }
430
431            $crate::__test_modes_inner!(@skip_none $($content)*);
432        }
433
434        /// Tests using JIT-compiled deserializer
435        mod cranelift {
436            #[allow(unused_imports)]
437            use super::*;
438
439            #[allow(dead_code)]
440            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
441                $crate::cranelift::from_str(input)
442            }
443
444            $crate::__test_modes_inner!(@skip_none $($content)*);
445        }
446    };
447}
448
449#[macro_export]
450#[cfg(all(not(feature = "streaming"), feature = "cranelift"))]
451#[doc(hidden)]
452macro_rules! __test_modes_inner {
453    // Ignore #[skip_streaming] when not in streaming mode
454    (@skip_none #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
455        #[test]
456        fn $name() {
457            ::facet_testhelpers::setup();
458            $body
459        }
460        $crate::__test_modes_inner!(@skip_none $($rest)*);
461    };
462
463    // Regular test - emit it
464    (@$mode:ident #[test] fn $name:ident() $body:block $($rest:tt)*) => {
465        #[test]
466        fn $name() {
467            ::facet_testhelpers::setup();
468            $body
469        }
470        $crate::__test_modes_inner!(@$mode $($rest)*);
471    };
472
473    // Base case - done
474    (@$mode:ident) => {};
475}
476
477// =============================================================================
478// Case 4: Neither streaming nor cranelift features enabled (slice only)
479// =============================================================================
480/// See the main `test_modes` documentation above.
481#[macro_export]
482#[cfg(all(not(feature = "streaming"), not(feature = "cranelift")))]
483macro_rules! test_modes {
484    ($($content:tt)*) => {
485        /// Tests using from_str (slice-based)
486        mod slice {
487            #[allow(unused_imports)]
488            use super::*;
489
490            #[allow(dead_code)]
491            fn deserialize<T: ::facet::Facet<'static>>(input: &str) -> Result<T, $crate::JsonError> {
492                $crate::from_str(input)
493            }
494
495            $crate::__test_modes_inner!(@skip_none $($content)*);
496        }
497    };
498}
499
500#[macro_export]
501#[cfg(all(not(feature = "streaming"), not(feature = "cranelift")))]
502#[doc(hidden)]
503macro_rules! __test_modes_inner {
504    // Ignore #[skip_streaming] in non-streaming build
505    (@skip_none #[skip_streaming] #[test] fn $name:ident() $body:block $($rest:tt)*) => {
506        #[test]
507        fn $name() {
508            ::facet_testhelpers::setup();
509            $body
510        }
511        $crate::__test_modes_inner!(@skip_none $($rest)*);
512    };
513
514    // Regular test
515    (@$mode:ident #[test] fn $name:ident() $body:block $($rest:tt)*) => {
516        #[test]
517        fn $name() {
518            ::facet_testhelpers::setup();
519            $body
520        }
521        $crate::__test_modes_inner!(@$mode $($rest)*);
522    };
523
524    // Base case
525    (@$mode:ident) => {};
526}