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}