Skip to main content

qubit_mime/
mime_error.rs

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