qubit_mime/mime_error.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Error type used by MIME database parsing and detection.
11//!
12
13use thiserror::Error;
14
15use crate::ProviderRegistryError;
16
17/// Error type for MIME repository parsing and I/O backed detection.
18#[derive(Debug, Error)]
19pub enum MimeError {
20 /// A glob weight was outside the freedesktop MIME range `0..=100`.
21 #[error("invalid MIME glob weight: {weight}")]
22 InvalidGlobWeight {
23 /// Invalid glob weight.
24 weight: u16,
25 },
26
27 /// A magic matcher definition is internally inconsistent.
28 #[error("invalid MIME magic matcher: {reason}")]
29 InvalidMagicMatcher {
30 /// Human-readable validation failure.
31 reason: String,
32 },
33
34 /// An XML attribute is missing or malformed.
35 #[error("invalid XML attribute '{attribute}' on <{element}>: '{value}' ({reason})")]
36 InvalidXmlAttribute {
37 /// Element carrying the invalid attribute.
38 element: String,
39 /// Invalid attribute name.
40 attribute: String,
41 /// Invalid attribute value.
42 value: String,
43 /// Human-readable validation failure.
44 reason: String,
45 },
46
47 /// An XML element is missing required content or has invalid children.
48 #[error("invalid XML element <{element}>: {reason}")]
49 InvalidXmlElement {
50 /// Invalid element name.
51 element: String,
52 /// Human-readable validation failure.
53 reason: String,
54 },
55
56 /// A detector or classifier input cannot be processed.
57 #[error("invalid MIME classifier input: {reason}")]
58 InvalidClassifierInput {
59 /// Human-readable validation failure.
60 reason: String,
61 },
62
63 /// A detector provider name or alias is already registered.
64 #[error("duplicate MIME detector name or alias: {name}")]
65 DuplicateDetectorName {
66 /// Duplicate provider name or alias.
67 name: String,
68 },
69
70 /// A detector provider name or alias is empty.
71 #[error("MIME detector name must not be empty")]
72 EmptyDetectorName,
73
74 /// A detector provider name or alias is malformed.
75 #[error("invalid MIME detector name '{name}': {reason}")]
76 InvalidDetectorName {
77 /// Invalid provider name.
78 name: String,
79 /// Human-readable validation failure.
80 reason: String,
81 },
82
83 /// A detector provider could not be found.
84 #[error("unknown MIME detector: {name}")]
85 UnknownDetector {
86 /// Requested provider name or alias.
87 name: String,
88 },
89
90 /// A detector provider exists but is not available in this environment.
91 #[error("MIME detector '{name}' is unavailable: {reason}")]
92 DetectorUnavailable {
93 /// Requested provider name or alias.
94 name: String,
95 /// Human-readable unavailability reason.
96 reason: String,
97 },
98
99 /// No configured detector provider could be created.
100 #[error("no available MIME detector: {reason}")]
101 NoAvailableDetector {
102 /// Human-readable failure summary.
103 reason: String,
104 },
105
106 /// A detector backend failed with an implementation-specific error.
107 #[error("MIME detector backend '{backend}' failed: {reason}")]
108 DetectorBackend {
109 /// Backend identifier.
110 backend: String,
111 /// Human-readable failure reason.
112 reason: String,
113 },
114
115 /// A media stream classifier provider name or alias is already registered.
116 #[error("duplicate media stream classifier name or alias: {name}")]
117 DuplicateClassifierName {
118 /// Duplicate provider name or alias.
119 name: String,
120 },
121
122 /// A media stream classifier provider name or alias is empty.
123 #[error("media stream classifier name must not be empty")]
124 EmptyClassifierName,
125
126 /// A media stream classifier provider name or alias is malformed.
127 #[error("invalid media stream classifier name '{name}': {reason}")]
128 InvalidClassifierName {
129 /// Invalid provider name.
130 name: String,
131 /// Human-readable validation failure.
132 reason: String,
133 },
134
135 /// A media stream classifier provider could not be found.
136 #[error("unknown media stream classifier: {name}")]
137 UnknownClassifier {
138 /// Requested provider name or alias.
139 name: String,
140 },
141
142 /// A media stream classifier provider exists but is not available in this environment.
143 #[error("media stream classifier '{name}' is unavailable: {reason}")]
144 ClassifierUnavailable {
145 /// Requested provider name or alias.
146 name: String,
147 /// Human-readable unavailability reason.
148 reason: String,
149 },
150
151 /// No configured media stream classifier provider could be created.
152 #[error("no available media stream classifier: {reason}")]
153 NoAvailableClassifier {
154 /// Human-readable failure summary.
155 reason: String,
156 },
157
158 /// A media stream classifier backend failed with an implementation-specific error.
159 #[error("media stream classifier backend '{backend}' failed: {reason}")]
160 ClassifierBackend {
161 /// Backend identifier.
162 backend: String,
163 /// Human-readable failure reason.
164 reason: String,
165 },
166
167 /// The XML document could not be parsed.
168 #[error("failed to parse MIME XML: {0}")]
169 Xml(#[from] roxmltree::Error),
170
171 /// Detection from a path or reader failed due to I/O.
172 #[error("I/O error while detecting MIME type: {0}")]
173 Io(#[from] std::io::Error),
174
175 /// Detection using an external command failed.
176 #[error("command error while detecting MIME type: {0}")]
177 Command(#[from] qubit_command::CommandError),
178
179 /// Loading MIME configuration failed.
180 #[error("configuration error while loading MIME settings: {0}")]
181 Config(#[from] qubit_config::ConfigError),
182}
183
184impl From<ProviderRegistryError> for MimeError {
185 /// Converts a generic SPI registry error into a MIME-domain error.
186 fn from(error: ProviderRegistryError) -> Self {
187 match error {
188 ProviderRegistryError::EmptyProviderName => Self::EmptyDetectorName,
189 ProviderRegistryError::InvalidProviderName { name, reason } => {
190 Self::InvalidDetectorName { name, reason }
191 }
192 ProviderRegistryError::DuplicateProviderName { name }
193 | ProviderRegistryError::DuplicateProviderCandidate { name } => {
194 Self::DuplicateDetectorName {
195 name: name.as_str().to_owned(),
196 }
197 }
198 ProviderRegistryError::UnknownProvider { name } => Self::UnknownDetector {
199 name: name.as_str().to_owned(),
200 },
201 ProviderRegistryError::ProviderUnavailable { name, source } => {
202 Self::DetectorUnavailable {
203 name: name.as_str().to_owned(),
204 reason: source.reason().to_owned(),
205 }
206 }
207 ProviderRegistryError::ProviderCreate { name, source } => Self::DetectorBackend {
208 backend: name.as_str().to_owned(),
209 reason: source.reason().to_owned(),
210 },
211 ProviderRegistryError::NoAvailableProvider { failures } => Self::NoAvailableDetector {
212 reason: failures
213 .iter()
214 .map(ToString::to_string)
215 .collect::<Vec<_>>()
216 .join("; "),
217 },
218 ProviderRegistryError::EmptyRegistry => Self::NoAvailableDetector {
219 reason: "detector registry is empty".to_owned(),
220 },
221 }
222 }
223}
224
225impl MimeError {
226 /// Builds an invalid XML attribute error.
227 ///
228 /// # Parameters
229 /// - `element`: Element carrying the attribute.
230 /// - `attribute`: Attribute name.
231 /// - `value`: Attribute value.
232 /// - `reason`: Why the value is invalid.
233 ///
234 /// # Returns
235 /// A [`MimeError::InvalidXmlAttribute`](crate::MimeError::InvalidXmlAttribute) value.
236 pub(crate) fn invalid_attr(
237 element: &str,
238 attribute: &str,
239 value: &str,
240 reason: impl Into<String>,
241 ) -> Self {
242 Self::InvalidXmlAttribute {
243 element: element.to_owned(),
244 attribute: attribute.to_owned(),
245 value: value.to_owned(),
246 reason: reason.into(),
247 }
248 }
249
250 /// Builds an invalid XML element error.
251 ///
252 /// # Parameters
253 /// - `element`: Invalid element name.
254 /// - `reason`: Why the element is invalid.
255 ///
256 /// # Returns
257 /// A [`MimeError::InvalidXmlElement`](crate::MimeError::InvalidXmlElement) value.
258 pub(crate) fn invalid_element(element: &str, reason: impl Into<String>) -> Self {
259 Self::InvalidXmlElement {
260 element: element.to_owned(),
261 reason: reason.into(),
262 }
263 }
264
265 /// Builds an invalid magic matcher error.
266 ///
267 /// # Parameters
268 /// - `reason`: Why the matcher is invalid.
269 ///
270 /// # Returns
271 /// A [`MimeError::InvalidMagicMatcher`](crate::MimeError::InvalidMagicMatcher) value.
272 pub(crate) fn invalid_matcher(reason: impl Into<String>) -> Self {
273 Self::InvalidMagicMatcher {
274 reason: reason.into(),
275 }
276 }
277
278 /// Builds an invalid classifier input error.
279 ///
280 /// # Parameters
281 /// - `reason`: Why the input cannot be classified.
282 ///
283 /// # Returns
284 /// A [`MimeError::InvalidClassifierInput`](crate::MimeError::InvalidClassifierInput) value.
285 pub(crate) fn invalid_classifier_input(reason: impl Into<String>) -> Self {
286 Self::InvalidClassifierInput {
287 reason: reason.into(),
288 }
289 }
290
291 /// Builds a detector backend error.
292 ///
293 /// # Parameters
294 /// - `backend`: Detector backend identifier.
295 /// - `reason`: Why the backend failed.
296 ///
297 /// # Returns
298 /// A [`MimeError::DetectorBackend`](crate::MimeError::DetectorBackend) value.
299 pub fn detector_backend(backend: impl Into<String>, reason: impl Into<String>) -> Self {
300 Self::DetectorBackend {
301 backend: backend.into(),
302 reason: reason.into(),
303 }
304 }
305
306 /// Converts a generic SPI registry error into a classifier-domain error.
307 ///
308 /// # Parameters
309 /// - `error`: Provider registry error returned by `qubit-spi`.
310 ///
311 /// # Returns
312 /// Classifier-specific MIME error.
313 pub(crate) fn classifier_registry_error(error: ProviderRegistryError) -> Self {
314 match error {
315 ProviderRegistryError::EmptyProviderName => Self::EmptyClassifierName,
316 ProviderRegistryError::InvalidProviderName { name, reason } => {
317 Self::InvalidClassifierName { name, reason }
318 }
319 ProviderRegistryError::DuplicateProviderName { name }
320 | ProviderRegistryError::DuplicateProviderCandidate { name } => {
321 Self::DuplicateClassifierName {
322 name: name.as_str().to_owned(),
323 }
324 }
325 ProviderRegistryError::UnknownProvider { name } => Self::UnknownClassifier {
326 name: name.as_str().to_owned(),
327 },
328 ProviderRegistryError::ProviderUnavailable { name, source } => {
329 Self::ClassifierUnavailable {
330 name: name.as_str().to_owned(),
331 reason: source.reason().to_owned(),
332 }
333 }
334 ProviderRegistryError::ProviderCreate { name, source } => Self::ClassifierBackend {
335 backend: name.as_str().to_owned(),
336 reason: source.reason().to_owned(),
337 },
338 ProviderRegistryError::NoAvailableProvider { failures } => {
339 Self::NoAvailableClassifier {
340 reason: failures
341 .iter()
342 .map(ToString::to_string)
343 .collect::<Vec<_>>()
344 .join("; "),
345 }
346 }
347 ProviderRegistryError::EmptyRegistry => Self::NoAvailableClassifier {
348 reason: "classifier registry is empty".to_owned(),
349 },
350 }
351 }
352}