1use core::{ops::Deref, range::Range};
2
3use displaydoc::Display;
4
5use crate::tokens::{
6 CharWithContext, ErrorToken, JsonCharOption, Token, TokenOption, TokenWithContext,
7 lexical::{JsonChar, trim_end_whitespace},
8};
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Debug, PartialEq, Eq, Display, Clone)]
13pub enum ErrorKind {
14 ExpectedKey(TokenWithContext, TokenOption),
17 ExpectedColon(TokenWithContext, TokenOption),
19 ExpectedValue(Option<TokenWithContext>, TokenOption),
21 ExpectedEntryOrClosedDelimiter {
23 open_ctx: TokenWithContext,
24 expected: JsonChar,
25 found: TokenOption,
26 },
27 ExpectedCommaOrClosedCurlyBrace {
29 range: Range<usize>,
30 open_ctx: TokenWithContext,
31 found: TokenOption,
32 },
33 ExpectedOpenBrace {
35 expected: JsonChar,
36 context: Option<TokenWithContext>,
37 found: TokenOption,
38 },
39
40 ExpectedDigitFollowingMinus(Range<usize>, JsonCharOption),
43 ExpectedMinusOrDigit(JsonCharOption),
45 UnexpectedLeadingZero {
47 initial: Range<usize>,
48 extra: Range<usize>,
49 },
50 ExpectedDigitAfterDot {
52 number_range: Range<usize>,
53 dot_range: Range<usize>,
54 maybe_c: JsonCharOption,
55 },
56
57 ExpectedPlusOrMinusOrDigitAfterE {
60 e_range: Range<usize>,
61 maybe_c: JsonCharOption,
62 },
63 ExpectedDigitAfterE {
65 exponent_range: Range<usize>,
66 maybe_c: JsonCharOption,
67 },
68
69 UnexpectedControlCharacterInString(JsonChar),
72 ExpectedQuote {
74 open_range: Range<usize>,
75 string_range: Range<usize>,
76 },
77 ExpectedHexDigit {
79 quote_range: Range<usize>,
80 slash_range: Range<usize>,
81 u_range: Range<usize>,
82 maybe_c: JsonCharOption,
83 digit_idx: usize,
84 },
85 ExpectedEscape {
88 maybe_c: JsonCharOption,
89 slash_range: Range<usize>,
90 string_range: Range<usize>,
91 quote_range: Range<usize>,
92 },
93
94 InvalidEncoding(bytes2chars::ErrorKind),
97 UnexpectedCharacter(JsonChar),
99 TokenAfterEnd(ErrorToken),
101 NestingTooDeep(usize),
103}
104
105impl ErrorKind {
106 pub(crate) fn expected_entry_or_closed_delimiter(
107 open_ctx: TokenWithContext,
108 found: TokenOption,
109 ) -> Option<Self> {
110 closing_delimiter_for_open(open_ctx.token).map(|expected| {
111 Self::ExpectedEntryOrClosedDelimiter {
112 open_ctx,
113 expected,
114 found,
115 }
116 })
117 }
118}
119
120fn closing_delimiter_for_open(token: Token) -> Option<JsonChar> {
121 match token {
122 Token::OpenCurlyBrace => Some('}'.into()),
123 Token::OpenSquareBracket => Some(']'.into()),
124 _ => None,
125 }
126}
127
128#[derive(Debug, PartialEq, Eq, Display, Clone)]
129pub struct Error(pub(crate) Box<ErrorInner>);
133
134impl std::error::Error for Error {}
135
136#[derive(Debug, PartialEq, Eq, Display, Clone)]
137pub struct ErrorInner {
139 pub(crate) kind: ErrorKind,
140 pub(crate) range: Range<usize>,
141 pub(crate) source_text: String,
142 pub(crate) source_name: String,
143}
144
145impl From<ErrorInner> for Error {
146 fn from(value: ErrorInner) -> Self {
147 Error(Box::new(value))
148 }
149}
150
151impl Deref for Error {
152 type Target = ErrorInner;
153
154 fn deref(&self) -> &Self::Target {
155 &self.0
156 }
157}
158
159impl Error {
160 pub fn message(&self) -> String {
161 self.to_string()
162 }
163
164 pub fn source_text(&self) -> &str {
165 &self.source_text
166 }
167
168 pub fn range(&self) -> &Range<usize> {
170 &self.0.range
171 }
172
173 pub(crate) fn new(kind: ErrorKind, range: Range<usize>, text: &str) -> Self {
174 ErrorInner {
175 kind,
176 range,
177 source_text: text.into(),
178 source_name: "stdin".into(),
179 }
180 .into()
181 }
182
183 pub(crate) fn from_unterminated(kind: ErrorKind, text: &str) -> Self {
184 let trimmed = trim_end_whitespace(text);
185 let last_char_start = trimmed.char_indices().next_back().map_or(0, |(i, _)| i);
186 Self::new(kind, last_char_start..trimmed.len(), text)
187 }
188
189 pub fn from_utf8_error_slice(e: std::str::Utf8Error, bytes: &[u8]) -> Error {
192 use bytes2chars::Utf8CharIndices;
193 const LOSSY_BYTE_LENGTH: usize = '\u{FFFD}'.len_utf8();
194 let b2c_err =
195 Utf8CharIndices::new(bytes[e.valid_up_to()..].iter().copied(), e.valid_up_to())
196 .next()
197 .and_then(|r| r.err())
198 .expect("a Utf8Error was returned so this must be an error");
199
200 ErrorInner {
201 kind: ErrorKind::InvalidEncoding(b2c_err.kind),
202 range: e.valid_up_to()..e.valid_up_to() + LOSSY_BYTE_LENGTH,
203 source_text: String::from_utf8_lossy(bytes).into_owned(),
204 source_name: "stdin".into(),
205 }
206 .into()
207 }
208
209 pub(crate) fn from_maybe_token_with_context<F>(
210 f: F,
211 maybe_token: Option<TokenWithContext>,
212 text: &str,
213 ) -> Self
214 where
215 F: Fn(TokenOption) -> ErrorKind,
216 {
217 if let Some(twc) = maybe_token {
218 Error::new(
219 f(TokenOption(Some(ErrorToken::new(
220 twc.token, twc.range, text,
221 )))),
222 twc.range,
223 text,
224 )
225 } else {
226 Error::from_unterminated(f(None.into()), text)
227 }
228 }
229
230 pub(crate) fn from_maybe_json_char_with_context<F>(
231 f: F,
232 maybe_c: Option<CharWithContext>,
233 text: &str,
234 ) -> Self
235 where
236 F: Fn(JsonCharOption) -> ErrorKind,
237 {
238 if let Some(CharWithContext(r, c)) = maybe_c {
239 Error::new(f(Some(c).into()), r, text)
240 } else {
241 Error::from_unterminated(f(None.into()), text)
242 }
243 }
244}