Skip to main content

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