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
72impl ParseError {
73 pub fn position(&self) -> Option<Position> {
75 match self {
76 ParseError::UnclosedList { at, .. }
77 | ParseError::UnexpectedChar { at, .. }
78 | ParseError::UnmatchedClose { at }
79 | ParseError::TrailingData { at }
80 | ParseError::InvalidUtf8 { at, .. } => Some(*at),
81 ParseError::UnclosedString { start } => Some(*start),
82 _ => None,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct AccessError(Box<AccessErrorInner>);
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96struct AccessErrorInner {
97 kind: AccessErrorKind,
98 position: Option<Position>,
99 path: Vec<usize>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum AccessErrorKind {
106 UnexpectedType {
108 expected: Cow<'static, str>,
109 found: String,
110 },
111 IndexOutOfBounds { index: usize, len: usize },
113 InvalidValue {
115 expected: Cow<'static, str>,
116 found: String,
117 reason: String,
118 },
119 CountMismatch { declared: usize, actual: usize },
121 Message(String),
123}
124
125impl AccessError {
126 pub(crate) fn new(kind: AccessErrorKind, position: Option<Position>) -> Self {
127 Self(Box::new(AccessErrorInner {
128 kind,
129 position,
130 path: Vec::new(),
131 }))
132 }
133
134 pub(crate) fn with_path(mut self, path: &[usize]) -> Self {
135 self.0.path = path.to_vec();
136 self
137 }
138
139 #[cfg_attr(not(feature = "serde"), allow(dead_code))]
141 pub(crate) fn or_position(mut self, position: Option<Position>) -> Self {
142 if self.0.position.is_none() {
143 self.0.position = position;
144 }
145 self
146 }
147
148 #[cfg_attr(not(feature = "serde"), allow(dead_code))]
150 pub(crate) fn prepend_path(mut self, index: usize) -> Self {
151 self.0.path.insert(0, index);
152 self
153 }
154
155 pub fn kind(&self) -> &AccessErrorKind {
156 &self.0.kind
157 }
158
159 pub fn position(&self) -> Option<Position> {
161 self.0.position
162 }
163
164 pub fn path(&self) -> &[usize] {
166 &self.0.path
167 }
168}
169
170impl fmt::Display for AccessError {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 let inner = &self.0;
173 match &inner.kind {
174 AccessErrorKind::UnexpectedType { expected, found }
175 | AccessErrorKind::InvalidValue {
176 expected, found, ..
177 } => write!(f, "expected {expected}, found {found}")?,
178 AccessErrorKind::IndexOutOfBounds { index, len } => {
179 write!(f, "index {index} is out of bounds for list of length {len}")?
180 }
181 AccessErrorKind::CountMismatch { declared, actual } => {
182 f.write_str(&count_mismatch(*declared, *actual))?
183 }
184 AccessErrorKind::Message(message) => f.write_str(message)?,
185 }
186 if let Some(position) = inner.position {
187 write!(f, " at {position}")?;
188 }
189 if !inner.path.is_empty() {
190 write!(f, " (path {:?})", inner.path)?;
191 }
192 if let AccessErrorKind::InvalidValue { reason, .. } = &inner.kind {
193 write!(f, ": {reason}")?;
194 }
195 Ok(())
196 }
197}
198
199impl std::error::Error for AccessError {}
200
201pub(crate) fn count_mismatch(declared: usize, actual: usize) -> String {
203 let items = if declared == 1 { "item" } else { "items" };
204 format!("list declares {declared} {items}, but has {actual}")
205}
206
207#[cfg(feature = "serde")]
208impl serde::de::Error for AccessError {
209 fn custom<T: fmt::Display>(msg: T) -> Self {
210 AccessError::new(AccessErrorKind::Message(msg.to_string()), None)
211 }
212}
213
214#[cfg(feature = "serde")]
216#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
217#[error("{0}")]
218pub struct SerializeError(String);
219
220#[cfg(feature = "serde")]
221impl SerializeError {
222 pub(crate) fn new(message: impl Into<String>) -> Self {
223 Self(message.into())
224 }
225}
226
227#[cfg(feature = "serde")]
228impl serde::ser::Error for SerializeError {
229 fn custom<T: fmt::Display>(msg: T) -> Self {
230 Self(msg.to_string())
231 }
232}
233
234#[derive(Debug, thiserror::Error)]
236pub enum Error {
237 #[error(transparent)]
238 Parse(#[from] ParseError),
239 #[error(transparent)]
240 Access(#[from] AccessError),
241}
242
243pub type Result<T, E = Error> = std::result::Result<T, E>;