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 tag_content: String,
107 position: usize,
108 },
109 /// A color value function (e.g. `rgb(` or `ansi(`) was opened but never closed.
110 UnclosedValue(usize),
111 /// A color function received the wrong number of arguments.
112 InvalidArgumentCount {
113 expected: usize,
114 got: usize,
115 position: usize,
116 },
117 /// An argument could not be parsed into the expected numeric type.
118 InvalidValue { value: String, position: usize },
119 /// A reset tag was given a target that cannot be reset (e.g. `[/reset]` or `[/prefix]`).
120 InvalidResetTarget(usize),
121}
122
123impl std::fmt::Display for LexError {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self {
126 LexError::UnclosedTag(pos) => write!(f, "unclosed tag at position {pos}"),
127 LexError::InvalidTag {
128 tag_content,
129 position,
130 } => write!(f, "invalid tag '{tag_content}' at position {position}"),
131 LexError::UnclosedValue(pos) => {
132 write!(f, "unclosed parentheses for color value at position {pos}")
133 }
134 LexError::InvalidArgumentCount {
135 expected,
136 got,
137 position,
138 } => {
139 write!(
140 f,
141 "expected {expected} arguments, got {got} at position {position}"
142 )
143 }
144 LexError::InvalidValue { value, position } => {
145 write!(f, "invalid value '{value}' at position {position}")
146 }
147 LexError::InvalidResetTarget(pos) => write!(
148 f,
149 "reset target must be a color or emphasis tag at position {pos}"
150 ),
151 }
152 }
153}
154
155impl std::error::Error for LexError {}