icu_provider/
error.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::log;
6use crate::{marker::DataMarkerId, prelude::*};
7use core::fmt;
8use displaydoc::Display;
9
10/// A list specifying general categories of data provider error.
11///
12/// Errors may be caused either by a malformed request or by the data provider
13/// not being able to fulfill a well-formed request.
14#[derive(Clone, Copy, Eq, PartialEq, Display, Debug)]
15#[non_exhaustive]
16pub enum DataErrorKind {
17    /// No data for the requested data marker. This is only returned by [`DynamicDataProvider`].
18    #[displaydoc("Missing data for marker")]
19    MarkerNotFound,
20
21    /// There is data for the data marker, but not for this particular data identifier.
22    #[displaydoc("Missing data for identifier")]
23    IdentifierNotFound,
24
25    /// The request is invalid, such as a request for a singleton marker containing a data identifier.
26    #[displaydoc("Invalid request")]
27    InvalidRequest,
28
29    /// The data for two [`DataMarker`]s is not consistent.
30    #[displaydoc("The data for two markers is not consistent: {0:?} (were they generated in different datagen invocations?)")]
31    InconsistentData(DataMarkerInfo),
32
33    /// An error occured during [`Any`](core::any::Any) downcasting.
34    #[displaydoc("Downcast: expected {0}, found")]
35    Downcast(&'static str),
36
37    /// An error occured during [`serde`] deserialization.
38    ///
39    /// Check debug logs for potentially more information.
40    #[displaydoc("Deserialize")]
41    Deserialize,
42
43    /// An unspecified error occurred.
44    ///
45    /// Check debug logs for potentially more information.
46    #[displaydoc("Custom")]
47    Custom,
48
49    /// An error occurred while accessing a system resource.
50    #[displaydoc("I/O: {0:?}")]
51    #[cfg(feature = "std")]
52    Io(std::io::ErrorKind),
53}
54
55/// The error type for ICU4X data provider operations.
56///
57/// To create one of these, either start with a [`DataErrorKind`] or use [`DataError::custom()`].
58///
59/// # Example
60///
61/// Create a IdentifierNotFound error and attach a data request for context:
62///
63/// ```no_run
64/// # use icu_provider::prelude::*;
65/// let marker: DataMarkerInfo = unimplemented!();
66/// let req: DataRequest = unimplemented!();
67/// DataErrorKind::IdentifierNotFound.with_req(marker, req);
68/// ```
69///
70/// Create a named custom error:
71///
72/// ```
73/// # use icu_provider::prelude::*;
74/// DataError::custom("This is an example error");
75/// ```
76#[derive(Clone, Copy, Eq, PartialEq, Debug)]
77#[non_exhaustive]
78pub struct DataError {
79    /// Broad category of the error.
80    pub kind: DataErrorKind,
81
82    /// The data marker of the request, if available.
83    pub marker: Option<DataMarkerId>,
84
85    /// Additional context, if available.
86    pub str_context: Option<&'static str>,
87
88    /// Whether this error was created in silent mode to not log.
89    pub silent: bool,
90}
91
92impl fmt::Display for DataError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "ICU4X data error")?;
95        if self.kind != DataErrorKind::Custom {
96            write!(f, ": {}", self.kind)?;
97        }
98        if let Some(marker) = self.marker {
99            write!(f, " (marker: {marker:?})")?;
100        }
101        if let Some(str_context) = self.str_context {
102            write!(f, ": {str_context}")?;
103        }
104        Ok(())
105    }
106}
107
108impl DataErrorKind {
109    /// Converts this DataErrorKind into a DataError.
110    ///
111    /// If possible, you should attach context using a `with_` function.
112    #[inline]
113    pub const fn into_error(self) -> DataError {
114        DataError {
115            kind: self,
116            marker: None,
117            str_context: None,
118            silent: false,
119        }
120    }
121
122    /// Creates a DataError with a data marker context.
123    #[inline]
124    pub const fn with_marker(self, marker: DataMarkerInfo) -> DataError {
125        self.into_error().with_marker(marker)
126    }
127
128    /// Creates a DataError with a string context.
129    #[inline]
130    pub const fn with_str_context(self, context: &'static str) -> DataError {
131        self.into_error().with_str_context(context)
132    }
133
134    /// Creates a DataError with a type name context.
135    #[inline]
136    pub fn with_type_context<T>(self) -> DataError {
137        self.into_error().with_type_context::<T>()
138    }
139
140    /// Creates a DataError with a request context.
141    #[inline]
142    pub fn with_req(self, marker: DataMarkerInfo, req: DataRequest) -> DataError {
143        self.into_error().with_req(marker, req)
144    }
145}
146
147impl DataError {
148    /// Returns a new, empty DataError with kind Custom and a string error message.
149    #[inline]
150    pub const fn custom(str_context: &'static str) -> Self {
151        Self {
152            kind: DataErrorKind::Custom,
153            marker: None,
154            str_context: Some(str_context),
155            silent: false,
156        }
157    }
158
159    /// Sets the data marker of a DataError, returning a modified error.
160    #[inline]
161    pub const fn with_marker(self, marker: DataMarkerInfo) -> Self {
162        Self {
163            kind: self.kind,
164            marker: Some(marker.id),
165            str_context: self.str_context,
166            silent: self.silent,
167        }
168    }
169
170    /// Sets the string context of a DataError, returning a modified error.
171    #[inline]
172    pub const fn with_str_context(self, context: &'static str) -> Self {
173        Self {
174            kind: self.kind,
175            marker: self.marker,
176            str_context: Some(context),
177            silent: self.silent,
178        }
179    }
180
181    /// Sets the string context of a DataError to the given type name, returning a modified error.
182    #[inline]
183    pub fn with_type_context<T>(self) -> Self {
184        if !self.silent {
185            log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
186        }
187        self.with_str_context(core::any::type_name::<T>())
188    }
189
190    /// Logs the data error with the given request, returning an error containing the data marker.
191    ///
192    /// If the "logging" Cargo feature is enabled, this logs the whole request. Either way,
193    /// it returns an error with the data marker portion of the request as context.
194    pub fn with_req(mut self, marker: DataMarkerInfo, req: DataRequest) -> Self {
195        if req.metadata.silent {
196            self.silent = true;
197        }
198        // Don't write out a log for MissingDataMarker since there is no context to add
199        if !self.silent && self.kind != DataErrorKind::MarkerNotFound {
200            log::warn!("{self} (marker: {marker:?}, request: {})", req.id);
201        }
202        self.with_marker(marker)
203    }
204
205    /// Logs the data error with the given context, then return self.
206    ///
207    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
208    /// it will print out the context.
209    #[cfg(feature = "std")]
210    pub fn with_path_context(self, _path: &std::path::Path) -> Self {
211        if !self.silent {
212            log::warn!("{self} (path: {_path:?})");
213        }
214        self
215    }
216
217    /// Logs the data error with the given context, then return self.
218    ///
219    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
220    /// it will print out the context.
221    #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
222    #[inline]
223    pub fn with_display_context<D: fmt::Display + ?Sized>(self, context: &D) -> Self {
224        if !self.silent {
225            log::warn!("{}: {}", self, context);
226        }
227        self
228    }
229
230    /// Logs the data error with the given context, then return self.
231    ///
232    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
233    /// it will print out the context.
234    #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
235    #[inline]
236    pub fn with_debug_context<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
237        if !self.silent {
238            log::warn!("{}: {:?}", self, context);
239        }
240        self
241    }
242
243    #[inline]
244    pub(crate) fn for_type<T>() -> DataError {
245        DataError {
246            kind: DataErrorKind::Downcast(core::any::type_name::<T>()),
247            marker: None,
248            str_context: None,
249            silent: false,
250        }
251    }
252}
253
254impl core::error::Error for DataError {}
255
256#[cfg(feature = "std")]
257impl From<std::io::Error> for DataError {
258    fn from(e: std::io::Error) -> Self {
259        log::warn!("I/O error: {}", e);
260        DataErrorKind::Io(e.kind()).into_error()
261    }
262}
263
264/// Extension trait for `Result<T, DataError>`.
265pub trait ResultDataError<T>: Sized {
266    /// Propagates all errors other than [`DataErrorKind::IdentifierNotFound`], and returns `None` in that case.
267    fn allow_identifier_not_found(self) -> Result<Option<T>, DataError>;
268}
269
270impl<T> ResultDataError<T> for Result<T, DataError> {
271    fn allow_identifier_not_found(self) -> Result<Option<T>, DataError> {
272        match self {
273            Ok(t) => Ok(Some(t)),
274            Err(DataError {
275                kind: DataErrorKind::IdentifierNotFound,
276                ..
277            }) => Ok(None),
278            Err(e) => Err(e),
279        }
280    }
281}