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 {}