facet_json/
deserialize.rs

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