Skip to main content

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}