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}