Skip to main content

farben_core/
errors.rs

1//! Error types for farben markup tokenization and registry operations.
2//!
3//! [`LexError`] covers failures during tokenization of markup strings like `[bold red]text[/]`.
4//! [`RegistryError`] covers failures in registry operations such as `set_prefix` and `insert_style`.
5//! [`LexErrorDisplay`] wraps a [`LexError`] with the original input to produce compiler-style
6//! diagnostic output with a caret pointing at the offending byte offset.
7
8/// Errors produced by registry operations (`set_prefix`, `insert_style`).
9///
10/// These errors carry no source position because registry calls happen outside of markup
11/// parsing, where no input string is available to point into.
12pub enum RegistryError {
13    /// The `prefix!` macro was called with a style name that has not been registered.
14    UnknownStyle(String),
15}
16
17impl std::fmt::Display for RegistryError {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            Self::UnknownStyle(s) => write!(f, "unknown style: '{s}' has not been registered"),
21        }
22    }
23}
24
25/// Diagnostic formatter that renders a [`LexError`] alongside the original markup input.
26///
27/// Formats output in the style of the Rust compiler: the input string on one line, followed by
28/// a caret (`^`) on the next line aligned to the byte offset stored in the error variant.
29///
30/// # Example
31///
32/// ```text
33///    | [bold unknown]text[/]
34///    |  ^^^^ invalid tag: 'bold unknown'
35/// ```
36pub struct LexErrorDisplay<'a> {
37    /// The error to render.
38    pub error: &'a LexError,
39    /// The full markup string that was being tokenized when the error occurred.
40    pub input: &'a str,
41}
42
43impl std::fmt::Display for LexErrorDisplay<'_> {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        writeln!(f, "   | {}", self.input)?;
46        match self.error {
47            LexError::UnclosedTag(position) => {
48                write!(f, "   | {}^ unclosed tag", " ".repeat(*position))
49            }
50            LexError::InvalidTag {
51                tag_content,
52                position,
53            } => {
54                write!(
55                    f,
56                    "   | {}{} invalid tag: '{tag_content}'",
57                    " ".repeat(*position + 1),
58                    "^".repeat(tag_content.len())
59                )
60            }
61            LexError::UnclosedValue(position) => {
62                write!(
63                    f,
64                    "   | {}^ unclosed parentheses for color value",
65                    " ".repeat(*position + 1)
66                )
67            }
68            LexError::InvalidArgumentCount {
69                expected,
70                got,
71                position,
72            } => {
73                write!(
74                    f,
75                    "   | {}^ expected {expected} arguments, got {got}",
76                    " ".repeat(*position + 1)
77                )
78            }
79            LexError::InvalidValue { value, position } => {
80                write!(
81                    f,
82                    "   | {}{} invalid value: '{value}'",
83                    " ".repeat(*position + 1),
84                    "^".repeat(value.len())
85                )
86            }
87            LexError::InvalidResetTarget(position) => {
88                write!(
89                    f,
90                    "   | {}^ reset target must be a color or emphasis tag",
91                    " ".repeat(*position + 1)
92                )
93            }
94        }
95    }
96}
97
98/// Errors produced during tokenization of a farben markup string.
99#[derive(Debug)]
100pub enum LexError {
101    /// A `[` was found with no matching `]`.
102    UnclosedTag(usize),
103    /// The tag name is not a recognized keyword or color form.
104    InvalidTag {
105        tag_content: String,
106        position: usize,
107    },
108    /// A color value function (e.g. `rgb(` or `ansi(`) was opened but never closed.
109    UnclosedValue(usize),
110    /// A color function received the wrong number of arguments.
111    InvalidArgumentCount {
112        expected: usize,
113        got: usize,
114        position: usize,
115    },
116    /// An argument could not be parsed into the expected numeric type.
117    InvalidValue { value: String, position: usize },
118    /// A reset tag was given a target that cannot be reset (e.g. `[/reset]` or `[/prefix]`).
119    InvalidResetTarget(usize),
120}
121
122impl std::fmt::Display for LexError {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            LexError::UnclosedTag(pos) => write!(f, "unclosed tag at position {pos}"),
126            LexError::InvalidTag {
127                tag_content,
128                position,
129            } => write!(f, "invalid tag '{tag_content}' at position {position}"),
130            LexError::UnclosedValue(pos) => {
131                write!(f, "unclosed parentheses for color value at position {pos}")
132            }
133            LexError::InvalidArgumentCount {
134                expected,
135                got,
136                position,
137            } => {
138                write!(
139                    f,
140                    "expected {expected} arguments, got {got} at position {position}"
141                )
142            }
143            LexError::InvalidValue { value, position } => {
144                write!(f, "invalid value '{value}' at position {position}")
145            }
146            LexError::InvalidResetTarget(pos) => write!(
147                f,
148                "reset target must be a color or emphasis tag at position {pos}"
149            ),
150        }
151    }
152}
153
154impl std::error::Error for LexError {}