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