1use std::borrow::Cow;
5use std::fmt;
6use std::io;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct Position {
15 pub offset: u64,
16 pub line: u64,
17 pub column: u64,
18}
19
20impl Position {
21 pub(crate) const START: Position = Position {
22 offset: 0,
23 line: 1,
24 column: 1,
25 };
26}
27
28impl Default for Position {
29 fn default() -> Self {
30 Self::START
31 }
32}
33
34impl fmt::Display for Position {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "line {}, column {}", self.line, self.column)
37 }
38}
39
40#[derive(Debug, thiserror::Error)]
42#[non_exhaustive]
43pub enum ParseError {
44 #[error("I/O error while reading input: {0}")]
45 Io(#[from] io::Error),
46
47 #[error("failed to open file `{}`: {source}", path.display())]
48 Open { path: PathBuf, source: io::Error },
49
50 #[error("input is empty: expected a bracket value")]
51 EmptyInput,
52
53 #[error("unexpected end of input at {at}: list opened at {opened} is not closed")]
54 UnclosedList { opened: Position, at: Position },
55
56 #[error("unexpected end of input: string starting at {start} is not closed")]
57 UnclosedString { start: Position },
58
59 #[error("unexpected character {found:?} at {at}: expected ',' or '}}'")]
60 UnexpectedChar { found: char, at: Position },
61
62 #[error("unexpected '}}' at {at}: no list is open")]
63 UnmatchedClose { at: Position },
64
65 #[error("trailing data at {at}: only one top-level value is allowed")]
66 TrailingData { at: Position },
67
68 #[error("invalid UTF-8 in {context} starting at {at}")]
69 InvalidUtf8 { context: &'static str, at: Position },
70
71 #[error("invalid escape at {at}: expected four hex digits after `\"\\`")]
72 InvalidEscape { at: Position },
73}
74
75impl ParseError {
76 pub fn position(&self) -> Option<Position> {
78 match self {
79 ParseError::UnclosedList { at, .. }
80 | ParseError::UnexpectedChar { at, .. }
81 | ParseError::UnmatchedClose { at }
82 | ParseError::TrailingData { at }
83 | ParseError::InvalidUtf8 { at, .. }
84 | ParseError::InvalidEscape { at } => Some(*at),
85 ParseError::UnclosedString { start } => Some(*start),
86 _ => None,
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct AccessError(Box<AccessErrorInner>);
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100struct AccessErrorInner {
101 kind: AccessErrorKind,
102 position: Option<Position>,
103 path: Vec<usize>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum AccessErrorKind {
110 UnexpectedType {
112 expected: Cow<'static, str>,
113 found: String,
114 },
115 IndexOutOfBounds { index: usize, len: usize },
117 InvalidValue {
119 expected: Cow<'static, str>,
120 found: String,
121 reason: String,
122 },
123 CountMismatch { declared: usize, actual: usize },
125 Message(String),
127}
128
129impl AccessError {
130 pub(crate) fn new(kind: AccessErrorKind, position: Option<Position>) -> Self {
131 Self(Box::new(AccessErrorInner {
132 kind,
133 position,
134 path: Vec::new(),
135 }))
136 }
137
138 pub(crate) fn with_path(mut self, path: &[usize]) -> Self {
139 self.0.path = path.to_vec();
140 self
141 }
142
143 #[cfg_attr(not(feature = "serde"), allow(dead_code))]
145 pub(crate) fn or_position(mut self, position: Option<Position>) -> Self {
146 if self.0.position.is_none() {
147 self.0.position = position;
148 }
149 self
150 }
151
152 #[cfg_attr(not(feature = "serde"), allow(dead_code))]
154 pub(crate) fn prepend_path(mut self, index: usize) -> Self {
155 self.0.path.insert(0, index);
156 self
157 }
158
159 pub fn kind(&self) -> &AccessErrorKind {
160 &self.0.kind
161 }
162
163 pub fn position(&self) -> Option<Position> {
165 self.0.position
166 }
167
168 pub fn path(&self) -> &[usize] {
170 &self.0.path
171 }
172}
173
174impl fmt::Display for AccessError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 let inner = &self.0;
177 match &inner.kind {
178 AccessErrorKind::UnexpectedType { expected, found }
179 | AccessErrorKind::InvalidValue {
180 expected, found, ..
181 } => write!(f, "expected {expected}, found {found}")?,
182 AccessErrorKind::IndexOutOfBounds { index, len } => {
183 write!(f, "index {index} is out of bounds for list of length {len}")?
184 }
185 AccessErrorKind::CountMismatch { declared, actual } => {
186 f.write_str(&count_mismatch(*declared, *actual))?
187 }
188 AccessErrorKind::Message(message) => f.write_str(message)?,
189 }
190 if let Some(position) = inner.position {
191 write!(f, " at {position}")?;
192 }
193 if !inner.path.is_empty() {
194 write!(f, " (path {:?})", inner.path)?;
195 }
196 if let AccessErrorKind::InvalidValue { reason, .. } = &inner.kind {
197 write!(f, ": {reason}")?;
198 }
199 Ok(())
200 }
201}
202
203impl std::error::Error for AccessError {}
204
205pub(crate) fn count_mismatch(declared: usize, actual: usize) -> String {
207 let items = if declared == 1 { "item" } else { "items" };
208 format!("list declares {declared} {items}, but has {actual}")
209}
210
211#[cfg(feature = "serde")]
212impl serde::de::Error for AccessError {
213 fn custom<T: fmt::Display>(msg: T) -> Self {
214 AccessError::new(AccessErrorKind::Message(msg.to_string()), None)
215 }
216}
217
218#[cfg(feature = "serde")]
220#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
221#[error("{0}")]
222pub struct SerializeError(String);
223
224#[cfg(feature = "serde")]
225impl SerializeError {
226 pub(crate) fn new(message: impl Into<String>) -> Self {
227 Self(message.into())
228 }
229}
230
231#[cfg(feature = "serde")]
232impl serde::ser::Error for SerializeError {
233 fn custom<T: fmt::Display>(msg: T) -> Self {
234 Self(msg.to_string())
235 }
236}
237
238#[derive(Debug, thiserror::Error)]
240pub enum Error {
241 #[error(transparent)]
242 Parse(#[from] ParseError),
243 #[error(transparent)]
244 Access(#[from] AccessError),
245}
246
247pub type Result<T, E = Error> = std::result::Result<T, E>;