facet_json/
deserialize.rs

1use core::num::{
2    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
3    NonZeroU64, NonZeroUsize,
4};
5
6#[cfg(feature = "rich-diagnostics")]
7use ariadne::{Color, Config, IndexType, Label, Report, ReportKind, Source};
8use facet_core::{Def, Facet, ScalarAffinity};
9use facet_reflect::{HeapValue, Wip};
10use log::trace;
11use yansi::Paint as _;
12
13use alloc::format;
14use alloc::string::{String, ToString};
15use alloc::vec::Vec;
16
17/// A JSON parse error, with context. Never would've guessed huh.
18#[derive(Debug)]
19pub struct JsonParseErrorWithContext<'input> {
20    input: &'input [u8],
21    pos: usize,
22    kind: JsonErrorKind,
23    path: String,
24}
25
26impl<'input> JsonParseErrorWithContext<'input> {
27    /// Creates a new `JsonParseErrorWithContext`.
28    ///
29    /// # Arguments
30    ///
31    /// * `kind` - The kind of JSON error encountered.
32    /// * `input` - The original input being parsed.
33    /// * `pos` - The position in the input where the error occurred.
34    pub fn new(kind: JsonErrorKind, input: &'input [u8], pos: usize, path: String) -> Self {
35        Self {
36            input,
37            pos,
38            kind,
39            path,
40        }
41    }
42
43    /// Returns a human-readable error message for this JSON error.
44    pub fn message(&self) -> String {
45        match &self.kind {
46            JsonErrorKind::UnexpectedEof => "Unexpected end of file".to_string(),
47            JsonErrorKind::UnexpectedCharacter(c) => format!("Unexpected character: '{}'", c),
48            JsonErrorKind::NumberOutOfRange(n) => format!("Number out of range: {}", n),
49            JsonErrorKind::StringAsNumber(s) => format!("Expected a string but got number: {}", s),
50        }
51    }
52}
53
54/// An error kind for JSON parsing.
55#[derive(Debug)]
56pub enum JsonErrorKind {
57    /// The input ended unexpectedly while parsing JSON.
58    UnexpectedEof,
59    /// An unexpected character was encountered in the input.
60    UnexpectedCharacter(char),
61    /// A number is out of range.
62    NumberOutOfRange(f64),
63    /// An unexpected String was encountered in the input.
64    StringAsNumber(String),
65}
66
67#[cfg(not(feature = "rich-diagnostics"))]
68impl core::fmt::Display for JsonParseErrorWithContext<'_> {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        write!(
71            f,
72            "{} at byte {} in path {}",
73            self.message(),
74            self.pos,
75            self.path
76        )
77    }
78}
79
80#[cfg(feature = "rich-diagnostics")]
81impl core::fmt::Display for JsonParseErrorWithContext<'_> {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        let Ok(input_str) = core::str::from_utf8(self.input) else {
84            return write!(f, "(JSON input was invalid UTF-8)");
85        };
86
87        let source_id = "json";
88
89        let (span_start, span_end) = match &self.kind {
90            JsonErrorKind::StringAsNumber(s) => (self.pos - s.len(), self.pos),
91            _ => {
92                let span_end = if self.pos < self.input.len() {
93                    self.pos + 1
94                } else {
95                    self.input.len()
96                };
97                (self.pos, span_end)
98            }
99        };
100
101        let mut report = Report::build(ReportKind::Error, (source_id, span_start..span_end))
102            .with_message(format!("Error at {}", self.path.fg(Color::Yellow)))
103            .with_config(Config::new().with_index_type(IndexType::Byte));
104
105        let label = Label::new((source_id, span_start..span_end))
106            .with_message(self.message())
107            .with_color(Color::Red);
108
109        report = report.with_label(label);
110
111        let source = Source::from(input_str);
112
113        let mut writer = Vec::new();
114        let cache = (source_id, &source);
115
116        if report.finish().write(cache, &mut writer).is_err() {
117            return write!(f, "Error formatting with ariadne");
118        }
119
120        if let Ok(output) = String::from_utf8(writer) {
121            write!(f, "{}", output)
122        } else {
123            write!(f, "Error converting ariadne output to string")
124        }
125    }
126}
127
128impl core::error::Error for JsonParseErrorWithContext<'_> {}
129
130/// Deserializes a JSON string into a value of type `T` that implements `Facet`.
131///
132/// This function takes a JSON string representation and converts it into a Rust
133/// value of the specified type `T`. The type must implement the `Facet` trait
134/// to provide the necessary type information for deserialization.
135pub fn from_str<T: Facet>(json: &str) -> Result<T, JsonParseErrorWithContext<'_>> {
136    from_slice(json.as_bytes())
137}
138
139/// Deserialize JSON from a slice
140///
141/// # Arguments
142///
143/// * `json` - A slice of bytes representing the JSON input.
144///
145/// # Returns
146///
147/// A result containing the deserialized value of type `T` or a `JsonParseErrorWithContext`.
148pub fn from_slice<T: Facet>(json: &[u8]) -> Result<T, JsonParseErrorWithContext<'_>> {
149    let wip = Wip::alloc::<T>();
150    let heap_value = from_slice_wip(wip, json)?;
151    Ok(heap_value.materialize::<T>().unwrap())
152}
153
154/// Deserialize a JSON string into a Wip object.
155///
156/// # Arguments
157///
158/// * `wip` - A mutable Wip object to deserialize into.
159/// * `input` - A byte slice representing the JSON input.
160///
161/// # Returns
162///
163/// A result containing the updated `Wip` or a `JsonParseErrorWithContext`.
164pub fn from_slice_wip<'input, 'a>(
165    mut wip: Wip<'a>,
166    input: &'input [u8],
167) -> Result<HeapValue<'a>, JsonParseErrorWithContext<'input>> {
168    let mut pos = 0;
169
170    macro_rules! err {
171        ($kind:expr) => {
172            Err(JsonParseErrorWithContext::new(
173                $kind,
174                input,
175                pos,
176                wip.path(),
177            ))
178        };
179    }
180    macro_rules! bail {
181        ($kind:expr) => {
182            return err!($kind);
183        };
184    }
185
186    /// Indicates why we are expecting a value in the parsing stack.
187    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
188    enum WhyValue {
189        /// At the top level of the JSON input.
190        TopLevel,
191        /// Expecting an object key.
192        ObjectKey,
193        /// Expecting an object value.
194        ObjectValue,
195        /// Expecting an array element.
196        ArrayElement,
197    }
198
199    /// Indicates the context for a comma separator in JSON (object or array).
200    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
201    enum WhyComma {
202        /// A comma in an object context.
203        Object,
204        /// A comma in an array context.
205        Array,
206    }
207
208    /// Indicates the type of separator expected (colon or comma).
209    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
210    enum Separator {
211        /// Expecting a colon separator in object key-value pairs.
212        Colon,
213        /// Expecting a comma separator (in objects or arrays).
214        Comma(WhyComma),
215    }
216
217    /// Represents the next expected token or structure while parsing.
218    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
219    enum Expect {
220        /// Expecting a value, with its reason/context.
221        Value(WhyValue),
222        /// Expecting a separator (colon or comma).
223        Separator(Separator),
224        /// We did `push_some` and now we need to pop it
225        PopOption,
226    }
227
228    let mut stack: Vec<Expect> = Vec::new();
229    stack.push(Expect::Value(WhyValue::TopLevel));
230
231    loop {
232        // skip over whitespace
233        while let Some(c) = input.get(pos).copied() {
234            match c {
235                b' ' | b'\t' | b'\n' | b'\r' => {
236                    pos += 1;
237                }
238                _ => break,
239            }
240        }
241
242        let frame_count = wip.frames_count();
243        let expect = match stack.pop() {
244            Some(expect) => expect,
245            None => {
246                if frame_count == 1 {
247                    return Ok(wip.build().unwrap());
248                } else {
249                    bail!(JsonErrorKind::UnexpectedEof);
250                }
251            }
252        };
253        trace!("[{frame_count}] Expecting {expect:?}");
254
255        let Some(c) = input.get(pos).copied() else {
256            bail!(JsonErrorKind::UnexpectedEof);
257        };
258
259        match expect {
260            Expect::PopOption => {
261                // that's all, carry on
262                trace!("Popping option");
263                wip = wip.pop().unwrap();
264            }
265            Expect::Value(why) => {
266                if let Def::Option(_) = wip.shape().def {
267                    wip = wip.push_some().unwrap();
268                    stack.push(Expect::PopOption);
269                }
270
271                match c {
272                    b'{' => {
273                        pos += 1;
274                        let Some(c) = input.get(pos).copied() else {
275                            bail!(JsonErrorKind::UnexpectedEof);
276                        };
277                        match c {
278                            b'}' => {
279                                pos += 1;
280                                if frame_count > 1 {
281                                    // just finished reading a value I guess
282                                    wip = wip.pop().unwrap();
283                                }
284                            }
285                            _ => {
286                                // okay, next we expect a "key: value"
287                                stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
288                                stack.push(Expect::Value(WhyValue::ObjectValue));
289                                stack.push(Expect::Separator(Separator::Colon));
290                                stack.push(Expect::Value(WhyValue::ObjectKey));
291                            }
292                        }
293                    }
294                    b'[' => {
295                        pos += 1;
296                        let Some(c) = input.get(pos).copied() else {
297                            bail!(JsonErrorKind::UnexpectedEof);
298                        };
299
300                        wip = wip.begin_pushback().unwrap();
301                        match c {
302                            b']' => {
303                                // an array just closed, somewhere
304                                pos += 1;
305                            }
306                            _ => {
307                                // okay, next we expect an item and a separator (or the end of the array)
308                                stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
309                                stack.push(Expect::Value(WhyValue::ArrayElement));
310                                wip = wip.push().unwrap();
311                            }
312                        }
313                    }
314                    b'"' => {
315                        pos += 1;
316                        // our value is a string
317                        let mut value = String::new();
318                        loop {
319                            let Some(c) = input.get(pos).copied() else {
320                                bail!(JsonErrorKind::UnexpectedEof);
321                            };
322                            match c {
323                                b'"' => {
324                                    pos += 1;
325                                    break;
326                                }
327                                b'\\' => {
328                                    pos += 2;
329                                    value.push('\\');
330                                }
331                                _ => {
332                                    pos += 1;
333                                    value.push(c as char);
334                                }
335                            }
336                        }
337                        trace!(
338                            "Parsed string value: {:?} for shape {}",
339                            value.yellow(),
340                            wip.shape()
341                        );
342
343                        match why {
344                            WhyValue::TopLevel => {
345                                wip = wip.parse(&value).unwrap();
346                            }
347                            WhyValue::ArrayElement => {
348                                wip = wip.parse(&value).unwrap();
349                                wip = wip.pop().unwrap();
350                            }
351                            WhyValue::ObjectValue => {
352                                wip = wip.parse(&value).unwrap();
353                                wip = wip.pop().unwrap();
354                            }
355                            WhyValue::ObjectKey => {
356                                wip = wip.field_named(&value).unwrap();
357                            }
358                        }
359                    }
360                    b'0'..=b'9' | b'-' => {
361                        pos += 1;
362                        let start = pos - 1;
363                        while let Some(c) = input.get(pos) {
364                            match c {
365                                b'0'..=b'9' | b'.' => {
366                                    pos += 1;
367                                }
368                                _ => break,
369                            }
370                        }
371                        let number = &input[start..pos];
372                        let number = core::str::from_utf8(number).unwrap();
373                        trace!("Parsed number value: {:?}", number.yellow());
374                        let number = number.parse::<f64>().unwrap();
375                        trace!("Parsed number value: {:?}", number.yellow());
376
377                        let shape = wip.shape();
378                        match shape.def {
379                            Def::Scalar(sd) => match sd.affinity {
380                                ScalarAffinity::Number(_na) => {
381                                    if shape.is_type::<u8>() {
382                                        if number >= 0.0 && number <= u8::MAX as f64 {
383                                            let value = number as u8;
384                                            wip = wip.put::<u8>(value).unwrap();
385                                        } else {
386                                            bail!(JsonErrorKind::NumberOutOfRange(number));
387                                        }
388                                    } else if shape.is_type::<u16>() {
389                                        if number >= 0.0 && number <= u16::MAX as f64 {
390                                            let value = number as u16;
391                                            wip = wip.put::<u16>(value).unwrap();
392                                        } else {
393                                            bail!(JsonErrorKind::NumberOutOfRange(number));
394                                        }
395                                    } else if shape.is_type::<u32>() {
396                                        if number >= 0.0 && number <= u32::MAX as f64 {
397                                            let value = number as u32;
398                                            wip = wip.put::<u32>(value).unwrap();
399                                        } else {
400                                            bail!(JsonErrorKind::NumberOutOfRange(number));
401                                        }
402                                    } else if shape.is_type::<u64>() {
403                                        if number >= 0.0 && number <= u64::MAX as f64 {
404                                            let value = number as u64;
405                                            wip = wip.put::<u64>(value).unwrap();
406                                        } else {
407                                            bail!(JsonErrorKind::NumberOutOfRange(number));
408                                        }
409                                    } else if shape.is_type::<i8>() {
410                                        if number >= i8::MIN as f64 && number <= i8::MAX as f64 {
411                                            let value = number as i8;
412                                            wip = wip.put::<i8>(value).unwrap();
413                                        } else {
414                                            bail!(JsonErrorKind::NumberOutOfRange(number));
415                                        }
416                                    } else if shape.is_type::<i16>() {
417                                        if number >= i16::MIN as f64 && number <= i16::MAX as f64 {
418                                            let value = number as i16;
419                                            wip = wip.put::<i16>(value).unwrap();
420                                        } else {
421                                            bail!(JsonErrorKind::NumberOutOfRange(number));
422                                        }
423                                    } else if shape.is_type::<i32>() {
424                                        if number >= i32::MIN as f64 && number <= i32::MAX as f64 {
425                                            let value = number as i32;
426                                            wip = wip.put::<i32>(value).unwrap();
427                                        } else {
428                                            bail!(JsonErrorKind::NumberOutOfRange(number));
429                                        }
430                                    } else if shape.is_type::<i64>() {
431                                        // Note: f64 might lose precision for large i64 values, but this is a common limitation.
432                                        if number >= i64::MIN as f64 && number <= i64::MAX as f64 {
433                                            let value = number as i64;
434                                            wip = wip.put::<i64>(value).unwrap();
435                                        } else {
436                                            bail!(JsonErrorKind::NumberOutOfRange(number));
437                                        }
438                                    } else if shape.is_type::<f32>() {
439                                        if number >= f32::MIN as f64 && number <= f32::MAX as f64 {
440                                            let value = number as f32;
441                                            wip = wip.put::<f32>(value).unwrap();
442                                        } else {
443                                            bail!(JsonErrorKind::NumberOutOfRange(number));
444                                        }
445                                    } else if shape.is_type::<f64>() {
446                                        wip = wip.put::<f64>(number).unwrap();
447                                    } else if shape.is_type::<NonZeroU8>() {
448                                        if number >= 1.0 && number <= u8::MAX as f64 {
449                                            let value = NonZeroU8::new(number as u8).unwrap();
450                                            wip = wip.put::<NonZeroU8>(value).unwrap();
451                                        } else {
452                                            bail!(JsonErrorKind::NumberOutOfRange(number));
453                                        }
454                                    } else if shape.is_type::<NonZeroU16>() {
455                                        if number >= 1.0 && number <= u16::MAX as f64 {
456                                            let value = NonZeroU16::new(number as u16).unwrap();
457                                            wip = wip.put::<NonZeroU16>(value).unwrap();
458                                        } else {
459                                            bail!(JsonErrorKind::NumberOutOfRange(number));
460                                        }
461                                    } else if shape.is_type::<NonZeroU32>() {
462                                        if number >= 1.0 && number <= u32::MAX as f64 {
463                                            let value = NonZeroU32::new(number as u32).unwrap();
464                                            wip = wip.put::<NonZeroU32>(value).unwrap();
465                                        } else {
466                                            bail!(JsonErrorKind::NumberOutOfRange(number));
467                                        }
468                                    } else if shape.is_type::<NonZeroU64>() {
469                                        if number >= 1.0 && number <= u64::MAX as f64 {
470                                            let value = NonZeroU64::new(number as u64).unwrap();
471                                            wip = wip.put::<NonZeroU64>(value).unwrap();
472                                        } else {
473                                            bail!(JsonErrorKind::NumberOutOfRange(number));
474                                        }
475                                    } else if shape.is_type::<NonZeroUsize>() {
476                                        if number >= 1.0 && number <= usize::MAX as f64 {
477                                            let value = NonZeroUsize::new(number as usize).unwrap();
478                                            wip = wip.put::<NonZeroUsize>(value).unwrap();
479                                        } else {
480                                            bail!(JsonErrorKind::NumberOutOfRange(number));
481                                        }
482                                    } else if shape.is_type::<NonZeroI8>() {
483                                        if number >= 1.0 && number <= i8::MAX as f64 {
484                                            let value = NonZeroI8::new(number as i8).unwrap();
485                                            wip = wip.put::<NonZeroI8>(value).unwrap();
486                                        } else {
487                                            bail!(JsonErrorKind::NumberOutOfRange(number));
488                                        }
489                                    } else if shape.is_type::<NonZeroI16>() {
490                                        if number >= 1.0 && number <= i16::MAX as f64 {
491                                            let value = NonZeroI16::new(number as i16).unwrap();
492                                            wip = wip.put::<NonZeroI16>(value).unwrap();
493                                        } else {
494                                            bail!(JsonErrorKind::NumberOutOfRange(number));
495                                        }
496                                    } else if shape.is_type::<NonZeroI32>() {
497                                        if number >= 1.0 && number <= i32::MAX as f64 {
498                                            let value = NonZeroI32::new(number as i32).unwrap();
499                                            wip = wip.put::<NonZeroI32>(value).unwrap();
500                                        } else {
501                                            bail!(JsonErrorKind::NumberOutOfRange(number));
502                                        }
503                                    } else if shape.is_type::<NonZeroI64>() {
504                                        if number >= 1.0 && number <= i64::MAX as f64 {
505                                            let value = NonZeroI64::new(number as i64).unwrap();
506                                            wip = wip.put::<NonZeroI64>(value).unwrap();
507                                        } else {
508                                            bail!(JsonErrorKind::NumberOutOfRange(number));
509                                        }
510                                    } else if shape.is_type::<NonZeroIsize>() {
511                                        if number >= 1.0 && number <= isize::MAX as f64 {
512                                            let value = NonZeroIsize::new(number as isize).unwrap();
513                                            wip = wip.put::<NonZeroIsize>(value).unwrap();
514                                        } else {
515                                            bail!(JsonErrorKind::NumberOutOfRange(number));
516                                        }
517                                    } else {
518                                        todo!("number type, but unknown")
519                                    }
520                                }
521                                ScalarAffinity::String(_sa) => {
522                                    if shape.is_type::<String>() {
523                                        let value = number.to_string();
524                                        bail!(JsonErrorKind::StringAsNumber(value));
525                                    } else {
526                                        todo!()
527                                    }
528                                }
529                                _ => {
530                                    todo!("saw number in JSON but expected.. shape {}?", shape)
531                                }
532                            },
533                            _ => {
534                                todo!("saw number in JSON but expected.. shape {}?", shape)
535                            }
536                        }
537
538                        match why {
539                            WhyValue::TopLevel => {}
540                            WhyValue::ObjectKey => todo!(),
541                            WhyValue::ObjectValue => {
542                                wip = wip.pop().unwrap();
543                            }
544                            WhyValue::ArrayElement => {
545                                wip = wip.pop().unwrap();
546                            }
547                        }
548                    }
549                    b'n' => {
550                        // wow it's a null — probably
551                        let slice_rest = &input[pos..];
552                        if slice_rest.starts_with(b"null") {
553                            pos += 4;
554
555                            // ok but we already pushed some! luckily wip has the method for us
556                            wip = wip.pop_some_push_none().unwrap();
557
558                            match why {
559                                WhyValue::TopLevel => {}
560                                WhyValue::ObjectKey => todo!(),
561                                WhyValue::ObjectValue => {
562                                    // these are all super messy, they should be expect on the stack
563                                    wip = wip.pop().unwrap();
564                                }
565                                WhyValue::ArrayElement => {
566                                    wip = wip.pop().unwrap();
567                                }
568                            }
569                        } else {
570                            bail!(JsonErrorKind::UnexpectedCharacter('n'));
571                        }
572                    }
573                    c => {
574                        bail!(JsonErrorKind::UnexpectedCharacter(c as char));
575                    }
576                }
577            }
578            Expect::Separator(separator) => match separator {
579                Separator::Colon => match c {
580                    b':' => {
581                        pos += 1;
582                    }
583                    _ => {
584                        bail!(JsonErrorKind::UnexpectedCharacter(c as char));
585                    }
586                },
587                Separator::Comma(why) => match c {
588                    b',' => {
589                        pos += 1;
590                        match why {
591                            WhyComma::Array => {
592                                stack.push(Expect::Separator(Separator::Comma(WhyComma::Array)));
593                                stack.push(Expect::Value(WhyValue::ArrayElement));
594                                wip = wip.push().unwrap();
595                            }
596                            WhyComma::Object => {
597                                // looks like we're in for another round of object parsing
598                                stack.push(Expect::Separator(Separator::Comma(WhyComma::Object)));
599                                stack.push(Expect::Value(WhyValue::ObjectValue));
600                                stack.push(Expect::Separator(Separator::Colon));
601                                stack.push(Expect::Value(WhyValue::ObjectKey));
602                            }
603                        }
604                    }
605                    b'}' => {
606                        match why {
607                            WhyComma::Object => {
608                                pos += 1;
609
610                                // we finished the object, neat
611                                if frame_count > 1 {
612                                    wip = wip.pop().unwrap();
613                                }
614                            }
615                            _ => {
616                                bail!(JsonErrorKind::UnexpectedCharacter(c as char));
617                            }
618                        }
619                    }
620                    b']' => {
621                        pos += 1;
622                        match why {
623                            WhyComma::Array => {
624                                // we finished the array, neat
625                                if frame_count > 1 {
626                                    wip = wip.pop().unwrap();
627                                }
628                            }
629                            _ => {
630                                bail!(JsonErrorKind::UnexpectedCharacter(c as char));
631                            }
632                        }
633                    }
634                    _ => {
635                        bail!(JsonErrorKind::UnexpectedCharacter(c as char));
636                    }
637                },
638            },
639        }
640    }
641}