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
72impl ParseError {
73    /// Position in the input where the problem was found, if known.
74    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/// The text is valid, but a node does not have the expected shape.
88///
89/// Carries the position of the offending node (for parsed nodes) and,
90/// for [`Node::try_get_path`](crate::Node::try_get_path), the path to it.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct AccessError(Box<AccessErrorInner>);
93
94/// Boxed to keep `Result<&Node, AccessError>` small on the happy path.
95#[derive(Debug, Clone, PartialEq, Eq)]
96struct AccessErrorInner {
97    kind: AccessErrorKind,
98    position: Option<Position>,
99    path: Vec<usize>,
100}
101
102/// What exactly went wrong in an [`AccessError`].
103#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum AccessErrorKind {
106    /// The node is of another kind (list instead of value, string instead of atom...).
107    UnexpectedType {
108        expected: Cow<'static, str>,
109        found: String,
110    },
111    /// The list is shorter than requested.
112    IndexOutOfBounds { index: usize, len: usize },
113    /// The value has the right kind but cannot be converted.
114    InvalidValue {
115        expected: Cow<'static, str>,
116        found: String,
117        reason: String,
118    },
119    /// A counted list `{count,items...}` whose count does not match its items.
120    CountMismatch { declared: usize, actual: usize },
121    /// A message from serde: missing items, unknown variants and the like.
122    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    /// Sets the position unless a more precise (inner) one is already known.
140    #[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    /// Adds a step to the path while the error bubbles up to the parent list.
149    #[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    /// Position of the offending node; `None` for nodes built in code.
160    pub fn position(&self) -> Option<Position> {
161        self.0.position
162    }
163
164    /// Path to the offending node relative to the node `try_get_path` was called on.
165    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
201/// Shared by `Node::try_counted` and `Counted`, which can only report through serde.
202pub(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/// A value cannot be represented in the bracket format.
215#[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/// Either kind of error, for code that parses and maps in one function.
235#[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>;