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