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 } => Self::DuplicateDetectorName {
193 name: name.as_str().to_owned(),
194 },
195 ProviderRegistryError::UnknownProvider { name } => Self::UnknownDetector {
196 name: name.as_str().to_owned(),
197 },
198 ProviderRegistryError::ProviderUnavailable { name, source } => {
199 Self::DetectorUnavailable {
200 name: name.as_str().to_owned(),
201 reason: source.reason().to_owned(),
202 }
203 }
204 ProviderRegistryError::ProviderCreate { name, source } => Self::DetectorBackend {
205 backend: name.as_str().to_owned(),
206 reason: source.reason().to_owned(),
207 },
208 ProviderRegistryError::NoAvailableProvider { failures } => Self::NoAvailableDetector {
209 reason: failures
210 .iter()
211 .map(ToString::to_string)
212 .collect::<Vec<_>>()
213 .join("; "),
214 },
215 ProviderRegistryError::EmptyRegistry => Self::NoAvailableDetector {
216 reason: "detector registry is empty".to_owned(),
217 },
218 }
219 }
220}
221
222impl MimeError {
223 /// Builds an invalid XML attribute error.
224 ///
225 /// # Parameters
226 /// - `element`: Element carrying the attribute.
227 /// - `attribute`: Attribute name.
228 /// - `value`: Attribute value.
229 /// - `reason`: Why the value is invalid.
230 ///
231 /// # Returns
232 /// A [`MimeError::InvalidXmlAttribute`](crate::MimeError::InvalidXmlAttribute) value.
233 pub(crate) fn invalid_attr(
234 element: &str,
235 attribute: &str,
236 value: &str,
237 reason: impl Into<String>,
238 ) -> Self {
239 Self::InvalidXmlAttribute {
240 element: element.to_owned(),
241 attribute: attribute.to_owned(),
242 value: value.to_owned(),
243 reason: reason.into(),
244 }
245 }
246
247 /// Builds an invalid XML element error.
248 ///
249 /// # Parameters
250 /// - `element`: Invalid element name.
251 /// - `reason`: Why the element is invalid.
252 ///
253 /// # Returns
254 /// A [`MimeError::InvalidXmlElement`](crate::MimeError::InvalidXmlElement) value.
255 pub(crate) fn invalid_element(element: &str, reason: impl Into<String>) -> Self {
256 Self::InvalidXmlElement {
257 element: element.to_owned(),
258 reason: reason.into(),
259 }
260 }
261
262 /// Builds an invalid magic matcher error.
263 ///
264 /// # Parameters
265 /// - `reason`: Why the matcher is invalid.
266 ///
267 /// # Returns
268 /// A [`MimeError::InvalidMagicMatcher`](crate::MimeError::InvalidMagicMatcher) value.
269 pub(crate) fn invalid_matcher(reason: impl Into<String>) -> Self {
270 Self::InvalidMagicMatcher {
271 reason: reason.into(),
272 }
273 }
274
275 /// Builds an invalid classifier input error.
276 ///
277 /// # Parameters
278 /// - `reason`: Why the input cannot be classified.
279 ///
280 /// # Returns
281 /// A [`MimeError::InvalidClassifierInput`](crate::MimeError::InvalidClassifierInput) value.
282 pub(crate) fn invalid_classifier_input(reason: impl Into<String>) -> Self {
283 Self::InvalidClassifierInput {
284 reason: reason.into(),
285 }
286 }
287
288 /// Builds a detector backend error.
289 ///
290 /// # Parameters
291 /// - `backend`: Detector backend identifier.
292 /// - `reason`: Why the backend failed.
293 ///
294 /// # Returns
295 /// A [`MimeError::DetectorBackend`](crate::MimeError::DetectorBackend) value.
296 pub fn detector_backend(backend: impl Into<String>, reason: impl Into<String>) -> Self {
297 Self::DetectorBackend {
298 backend: backend.into(),
299 reason: reason.into(),
300 }
301 }
302
303 /// Converts a generic SPI registry error into a classifier-domain error.
304 ///
305 /// # Parameters
306 /// - `error`: Provider registry error returned by `qubit-spi`.
307 ///
308 /// # Returns
309 /// Classifier-specific MIME error.
310 pub(crate) fn classifier_registry_error(error: ProviderRegistryError) -> Self {
311 match error {
312 ProviderRegistryError::EmptyProviderName => Self::EmptyClassifierName,
313 ProviderRegistryError::InvalidProviderName { name, reason } => {
314 Self::InvalidClassifierName { name, reason }
315 }
316 ProviderRegistryError::DuplicateProviderName { name } => {
317 Self::DuplicateClassifierName {
318 name: name.as_str().to_owned(),
319 }
320 }
321 ProviderRegistryError::UnknownProvider { name } => Self::UnknownClassifier {
322 name: name.as_str().to_owned(),
323 },
324 ProviderRegistryError::ProviderUnavailable { name, source } => {
325 Self::ClassifierUnavailable {
326 name: name.as_str().to_owned(),
327 reason: source.reason().to_owned(),
328 }
329 }
330 ProviderRegistryError::ProviderCreate { name, source } => Self::ClassifierBackend {
331 backend: name.as_str().to_owned(),
332 reason: source.reason().to_owned(),
333 },
334 ProviderRegistryError::NoAvailableProvider { failures } => {
335 Self::NoAvailableClassifier {
336 reason: failures
337 .iter()
338 .map(ToString::to_string)
339 .collect::<Vec<_>>()
340 .join("; "),
341 }
342 }
343 ProviderRegistryError::EmptyRegistry => Self::NoAvailableClassifier {
344 reason: "classifier registry is empty".to_owned(),
345 },
346 }
347 }
348}