qubit_json/json_decode_error.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! Defines the [`JsonDecodeError`] type used by the public decoder API.
10//!
11//! Author: Haixing Hu
12
13use std::fmt;
14
15use crate::{JsonDecodeErrorKind, JsonTopLevelKind};
16
17/// Error returned when lenient JSON decoding fails.
18///
19/// This value captures both a stable category in [`JsonDecodeErrorKind`] and
20/// human-readable context that can be logged or surfaced to the caller.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct JsonDecodeError {
23 /// Identifies the stable category of this decoding failure.
24 ///
25 /// Callers should match on this field when they need programmatic handling
26 /// that is independent from localized or parser-specific text.
27 pub kind: JsonDecodeErrorKind,
28 /// Stores a human-readable summary of the decoding failure.
29 ///
30 /// The message is intended for diagnostics and normally includes the
31 /// relevant parsing or deserialization context.
32 pub message: String,
33 /// Stores the top-level JSON kind required by the caller, when applicable.
34 ///
35 /// This field is only populated for errors raised by constrained decoding
36 /// methods such as `decode_object()` and `decode_array()`.
37 pub expected_top_level: Option<JsonTopLevelKind>,
38 /// Stores the top-level JSON kind that was actually parsed, when known.
39 ///
40 /// This field is only populated together with `expected_top_level` for
41 /// top-level contract mismatches.
42 pub actual_top_level: Option<JsonTopLevelKind>,
43 /// Stores the one-based line reported by `serde_json`, when available.
44 ///
45 /// This field is primarily useful for invalid JSON syntax and
46 /// deserialization failures that can be mapped back to a parser location.
47 pub line: Option<usize>,
48 /// Stores the one-based column reported by `serde_json`, when available.
49 ///
50 /// Like `line`, this field is only populated when the lower-level parser
51 /// or deserializer reports a concrete source position.
52 pub column: Option<usize>,
53}
54
55impl JsonDecodeError {
56 /// Creates an error indicating that the input became empty after
57 /// normalization.
58 #[inline]
59 pub(crate) fn empty_input() -> Self {
60 Self {
61 kind: JsonDecodeErrorKind::EmptyInput,
62 message: "JSON input is empty after normalization".to_string(),
63 expected_top_level: None,
64 actual_top_level: None,
65 line: None,
66 column: None,
67 }
68 }
69
70 /// Creates an error describing invalid JSON syntax reported by
71 /// `serde_json`.
72 #[inline]
73 pub(crate) fn invalid_json(error: serde_json::Error) -> Self {
74 let line = error.line();
75 let column = error.column();
76 Self {
77 kind: JsonDecodeErrorKind::InvalidJson,
78 message: format!("Failed to parse JSON: {error}"),
79 expected_top_level: None,
80 actual_top_level: None,
81 line: (line > 0).then_some(line),
82 column: (column > 0).then_some(column),
83 }
84 }
85
86 /// Creates an error describing a mismatch between expected and actual
87 /// top-level JSON kinds.
88 #[inline]
89 pub(crate) fn unexpected_top_level(
90 expected: JsonTopLevelKind,
91 actual: JsonTopLevelKind,
92 ) -> Self {
93 Self {
94 kind: JsonDecodeErrorKind::UnexpectedTopLevel,
95 message: format!("Unexpected JSON top-level type: expected {expected}, got {actual}"),
96 expected_top_level: Some(expected),
97 actual_top_level: Some(actual),
98 line: None,
99 column: None,
100 }
101 }
102
103 /// Creates an error describing a type deserialization failure reported by
104 /// `serde_json`.
105 #[inline]
106 pub(crate) fn deserialize(error: serde_json::Error) -> Self {
107 let line = error.line();
108 let column = error.column();
109 Self {
110 kind: JsonDecodeErrorKind::Deserialize,
111 message: format!("Failed to deserialize JSON value: {error}"),
112 expected_top_level: None,
113 actual_top_level: None,
114 line: (line > 0).then_some(line),
115 column: (column > 0).then_some(column),
116 }
117 }
118}
119
120impl fmt::Display for JsonDecodeError {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 match self.kind {
123 JsonDecodeErrorKind::EmptyInput => f.write_str(&self.message),
124 JsonDecodeErrorKind::UnexpectedTopLevel => f.write_str(&self.message),
125 JsonDecodeErrorKind::InvalidJson | JsonDecodeErrorKind::Deserialize => {
126 match (self.line, self.column) {
127 (Some(line), Some(column)) => {
128 write!(f, "{} (line {}, column {})", self.message, line, column)
129 }
130 _ => f.write_str(&self.message),
131 }
132 }
133 }
134 }
135}
136
137impl std::error::Error for JsonDecodeError {}