Skip to main content

ferrocat_icu/
error.rs

1use core::fmt;
2
3/// High-level classification of ICU parse failures.
4///
5/// This enum is non-exhaustive so future parser error categories can be added
6/// without making existing downstream matches semver-breaking.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum IcuErrorKind {
10    /// The input violates the supported ICU syntax.
11    SyntaxError,
12}
13
14/// Byte offset plus line/column location inside the original input.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct IcuPosition {
17    /// Zero-based byte offset from the start of the input.
18    pub offset: usize,
19    /// One-based line number.
20    pub line: usize,
21    /// One-based column number.
22    pub column: usize,
23}
24
25/// Error returned when parsing ICU messages fails.
26#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct IcuParseError {
29    /// High-level failure kind.
30    pub kind: IcuErrorKind,
31    /// Human-readable parser error message.
32    pub message: String,
33    /// Source location for the parser failure.
34    pub position: IcuPosition,
35}
36
37impl IcuParseError {
38    /// Creates a syntax error at `offset` within `input`.
39    #[must_use]
40    pub fn syntax(message: impl Into<String>, input: &str, offset: usize) -> Self {
41        Self {
42            kind: IcuErrorKind::SyntaxError,
43            message: message.into(),
44            position: position_for_offset(input, offset),
45        }
46    }
47}
48
49impl fmt::Display for IcuParseError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(
52            f,
53            "{} at line {}, column {}",
54            self.message, self.position.line, self.position.column
55        )
56    }
57}
58
59impl std::error::Error for IcuParseError {}
60
61fn position_for_offset(input: &str, offset: usize) -> IcuPosition {
62    let clamped = offset.min(input.len());
63    let mut line = 1usize;
64    let mut column = 1usize;
65
66    for ch in input[..clamped].chars() {
67        if ch == '\n' {
68            line += 1;
69            column = 1;
70        } else {
71            column += 1;
72        }
73    }
74
75    IcuPosition {
76        offset: clamped,
77        line,
78        column,
79    }
80}