1use std::fmt;
2
3use crate::unicode::{is_bidi_control, is_surrogate};
4use crate::{Dialect, Rule, Severity};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct Problem {
9 pub(crate) offset: usize,
10 pub(crate) severity: Severity,
11 pub(crate) detail: Detail,
12}
13
14impl Problem {
15 pub fn offset(&self) -> usize {
17 self.offset
18 }
19
20 pub fn severity(&self) -> Severity {
22 self.severity
23 }
24
25 pub fn rule(&self) -> Rule {
27 match self.detail {
28 Detail::Syntax(_) => Rule::Syntax,
29 Detail::DuplicateKey { .. } => Rule::DuplicateKey,
30 Detail::InvalidNumber { .. } => Rule::InvalidNumber,
31 Detail::InvalidCodePoint { .. } => Rule::InvalidCodePoint,
32 Detail::ByteOrderMark => Rule::ByteOrderMark,
33 Detail::MissingFinalNewline => Rule::MissingFinalNewline,
34 }
35 }
36
37 pub fn related(&self) -> Option<Related> {
40 let (offset, kind) = match &self.detail {
41 Detail::DuplicateKey { first, .. } => (*first, RelatedKind::FirstOccurrence),
42 Detail::Syntax(s) => (s.started?, RelatedKind::Start),
43 _ => return None,
44 };
45 Some(Related { offset, kind })
46 }
47
48 pub fn path(&self) -> Option<&str> {
60 match &self.detail {
61 Detail::DuplicateKey { path, .. } => Some(path),
62 _ => None,
63 }
64 }
65
66 #[deprecated(since = "3.0.0", note = "use related().map(|r| r.offset)")]
68 pub fn related_offset(&self) -> Option<usize> {
69 self.related().map(|r| r.offset)
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75#[non_exhaustive]
76pub struct Related {
77 pub offset: usize,
79 pub kind: RelatedKind,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
85#[non_exhaustive]
86pub enum RelatedKind {
87 FirstOccurrence,
89 Start,
91}
92
93impl fmt::Display for Problem {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 self.detail.fmt(f)
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub(crate) enum Detail {
103 Syntax(SyntaxError),
105 DuplicateKey {
107 first: usize,
109 path: String,
111 },
112 InvalidNumber {
114 overflows: bool,
116 },
117 InvalidCodePoint {
119 code_point: u32,
121 },
122 ByteOrderMark,
124 MissingFinalNewline,
126}
127
128impl fmt::Display for Detail {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Detail::Syntax(s) => s.fmt(f),
132 Detail::DuplicateKey { path, .. } => write!(f, "duplicate key {path}"),
133 Detail::InvalidNumber { overflows: true } => f.write_str("number overflows a double"),
134 Detail::InvalidNumber { overflows: false } => {
135 f.write_str("integer beyond ±(2^53 − 1) cannot be represented exactly")
136 }
137 Detail::InvalidCodePoint { code_point } if is_surrogate(*code_point) => {
138 write!(f, "lone surrogate U+{code_point:04X}")
139 }
140 Detail::InvalidCodePoint { code_point } => {
141 write!(f, "noncharacter U+{code_point:04X}")
142 }
143 Detail::ByteOrderMark => f.write_str("byte order mark at the start of the file"),
144 Detail::MissingFinalNewline => f.write_str("no newline at the end of the file"),
145 }
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
151pub(crate) struct SyntaxError {
152 pub(crate) kind: SyntaxKind,
154 pub(crate) started: Option<usize>,
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
160pub(crate) enum SyntaxKind {
161 UnexpectedEnd {
163 expected: &'static str,
165 },
166 UnexpectedLineEnd {
168 expected: &'static str,
170 },
171 Unexpected {
173 found: char,
175 expected: &'static str,
177 },
178 NotAllowed {
180 what: &'static str,
182 dialect: Dialect,
184 },
185 TrailingComma {
187 close: char,
189 dialect: Dialect,
191 },
192 UnterminatedString,
194 LineBreakInString,
196 ControlCharacter {
198 found: char,
200 },
201 InvalidEscape {
203 found: char,
205 },
206 InvalidUnicodeEscape,
208 InvalidHexEscape,
210 UnterminatedComment,
212 BlankLine,
214 TooDeep {
216 limit: usize,
218 },
219}
220
221impl fmt::Display for SyntaxError {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match &self.kind {
224 SyntaxKind::UnexpectedEnd { expected } => {
225 write!(f, "expected {expected}, found the end of the file")
226 }
227 SyntaxKind::UnexpectedLineEnd { expected } => {
228 write!(f, "expected {expected}, found the end of the line")
229 }
230 SyntaxKind::Unexpected { found, expected } => {
231 write!(f, "expected {expected}, found {}", Shown(*found))
232 }
233 SyntaxKind::NotAllowed { what, dialect } => {
234 write!(f, "{what} not allowed in {dialect}")
235 }
236 SyntaxKind::TrailingComma { close, dialect } => {
237 write!(
238 f,
239 "trailing comma before `{close}` is not allowed in {dialect}"
240 )
241 }
242 SyntaxKind::UnterminatedString => f.write_str("unterminated string"),
243 SyntaxKind::LineBreakInString => f.write_str("line break inside a string"),
244 SyntaxKind::ControlCharacter { found } => {
245 write!(
246 f,
247 "control character {} inside a string must be escaped",
248 Shown(*found)
249 )
250 }
251 SyntaxKind::InvalidEscape { found } => {
252 write!(f, "invalid escape `\\{}`", found.escape_debug())
253 }
254 SyntaxKind::InvalidUnicodeEscape => f.write_str("`\\u` needs four hex digits"),
255 SyntaxKind::InvalidHexEscape => f.write_str("`\\x` needs two hex digits"),
256 SyntaxKind::UnterminatedComment => f.write_str("unterminated block comment"),
257 SyntaxKind::BlankLine => {
258 f.write_str("blank line; JSON Lines needs a value on every line")
259 }
260 SyntaxKind::TooDeep { limit } => write!(f, "nested deeper than {limit} levels"),
261 }
262 }
263}
264
265struct Shown(char);
268
269impl fmt::Display for Shown {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 let c = self.0;
272 if c.is_control() || c.is_whitespace() || c == '\u{FEFF}' || is_bidi_control(c) {
273 write!(f, "U+{:04X}", u32::from(c))
274 } else {
275 write!(f, "`{c}`")
276 }
277 }
278}