Skip to main content

one_brackets/
error.rs

1//! Errors: [`ParseError`] for broken input, [`AccessError`] for unexpected structure,
2//! and [`Error`] that wraps both for code doing parsing and mapping in one go.
3
4use std::borrow::Cow;
5use std::fmt;
6use std::io;
7use std::path::PathBuf;
8
9/// Position in the input.
10///
11/// `line` and `column` are 1-based; the column counts Unicode characters.
12/// `offset` is the byte offset from the start of the input (including the BOM).
13#[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/// The input could not be read or is not valid bracket text.
41#[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    /// Position in the input where the problem was found, if known.
77    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/// The text is valid, but a node does not have the expected shape.
92///
93/// Carries the position of the offending node (for parsed nodes) and,
94/// for [`Node::try_get_path`](crate::Node::try_get_path), the path to it.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct AccessError(Box<AccessErrorInner>);
97
98/// Boxed to keep `Result<&Node, AccessError>` small on the happy path.
99#[derive(Debug, Clone, PartialEq, Eq)]
100struct AccessErrorInner {
101    kind: AccessErrorKind,
102    position: Option<Position>,
103    path: Vec<usize>,
104}
105
106/// What exactly went wrong in an [`AccessError`].
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum AccessErrorKind {
110    /// The node is of another kind (list instead of value, string instead of atom...).
111    UnexpectedType {
112        expected: Cow<'static, str>,
113        found: String,
114    },
115    /// The list is shorter than requested.
116    IndexOutOfBounds { index: usize, len: usize },
117    /// The value has the right kind but cannot be converted.
118    InvalidValue {
119        expected: Cow<'static, str>,
120        found: String,
121        reason: String,
122    },
123    /// A counted list `{count,items...}` whose count does not match its items.
124    CountMismatch { declared: usize, actual: usize },
125    /// A message from serde: missing items, unknown variants and the like.
126    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    /// Sets the position unless a more precise (inner) one is already known.
144    #[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    /// Adds a step to the path while the error bubbles up to the parent list.
153    #[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    /// Position of the offending node; `None` for nodes built in code.
164    pub fn position(&self) -> Option<Position> {
165        self.0.position
166    }
167
168    /// Path to the offending node relative to the node `try_get_path` was called on.
169    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
205/// Shared by `Node::try_counted` and `Counted`, which can only report through serde.
206pub(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/// A value cannot be represented in the bracket format.
219#[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/// Either kind of error, for code that parses and maps in one function.
239#[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>;