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