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::_internal::log;
6use crate::buf::BufferFormat;
7use crate::prelude::*;
8use core::fmt;
9use displaydoc::Display;
10
11/// A list specifying general categories of data provider error.
12///
13/// Errors may be caused either by a malformed request or by the data provider
14/// not being able to fulfill a well-formed request.
15#[derive(Clone, Copy, Eq, PartialEq, Display, Debug)]
16#[non_exhaustive]
17pub enum DataErrorKind {
18    /// No data for the provided resource key.
19    #[displaydoc("Missing data for key")]
20    MissingDataKey,
21
22    /// There is data for the key, but not for this particular locale.
23    #[displaydoc("Missing data for locale")]
24    MissingLocale,
25
26    /// The request should include a locale.
27    #[displaydoc("Request needs a locale")]
28    NeedsLocale,
29
30    /// The request should not contain a locale.
31    #[displaydoc("Request has an extraneous locale")]
32    ExtraneousLocale,
33
34    /// The resource was blocked by a filter. The resource may or may not be available.
35    #[displaydoc("Resource blocked by filter")]
36    FilteredResource,
37
38    /// The generic type parameter does not match the TypeId. The expected type name is stored
39    /// as context when this error is returned.
40    #[displaydoc("Mismatched types: tried to downcast with {0}, but actual type is different")]
41    MismatchedType(&'static str),
42
43    /// The payload is missing. This is usually caused by a previous error.
44    #[displaydoc("Missing payload")]
45    MissingPayload,
46
47    /// A data provider object was given to an operation in an invalid state.
48    #[displaydoc("Invalid state")]
49    InvalidState,
50
51    /// The syntax of the [`DataKey`] or [`DataLocale`] was invalid.
52    #[displaydoc("Parse error for data key or data locale")]
53    KeyLocaleSyntax,
54
55    /// An unspecified error occurred, such as a Serde error.
56    ///
57    /// Check debug logs for potentially more information.
58    #[displaydoc("Custom")]
59    Custom,
60
61    /// An error occurred while accessing a system resource.
62    #[displaydoc("I/O error: {0:?}")]
63    #[cfg(feature = "std")]
64    Io(std::io::ErrorKind),
65
66    /// An unspecified data source containing the required data is unavailable.
67    #[displaydoc("Missing source data")]
68    #[cfg(feature = "datagen")]
69    MissingSourceData,
70
71    /// An error indicating that the desired buffer format is not available. This usually
72    /// means that a required Cargo feature was not enabled
73    #[displaydoc("Unavailable buffer format: {0:?} (does icu_provider need to be compiled with an additional Cargo feature?)")]
74    UnavailableBufferFormat(BufferFormat),
75}
76
77/// The error type for ICU4X data provider operations.
78///
79/// To create one of these, either start with a [`DataErrorKind`] or use [`DataError::custom()`].
80///
81/// # Example
82///
83/// Create a NeedsLocale error and attach a data request for context:
84///
85/// ```no_run
86/// # use icu_provider::prelude::*;
87/// let key: DataKey = unimplemented!();
88/// let req: DataRequest = unimplemented!();
89/// DataErrorKind::NeedsLocale.with_req(key, req);
90/// ```
91///
92/// Create a named custom error:
93///
94/// ```
95/// # use icu_provider::prelude::*;
96/// DataError::custom("This is an example error");
97/// ```
98#[derive(Clone, Copy, Eq, PartialEq, Debug)]
99#[non_exhaustive]
100pub struct DataError {
101    /// Broad category of the error.
102    pub kind: DataErrorKind,
103
104    /// The data key of the request, if available.
105    pub key: Option<DataKey>,
106
107    /// Additional context, if available.
108    pub str_context: Option<&'static str>,
109
110    /// Whether this error was created in silent mode to not log.
111    pub silent: bool,
112}
113
114impl fmt::Display for DataError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "ICU4X data error")?;
117        if self.kind != DataErrorKind::Custom {
118            write!(f, ": {}", self.kind)?;
119        }
120        if let Some(key) = self.key {
121            write!(f, " (key: {key})")?;
122        }
123        if let Some(str_context) = self.str_context {
124            write!(f, ": {str_context}")?;
125        }
126        Ok(())
127    }
128}
129
130impl DataErrorKind {
131    /// Converts this DataErrorKind into a DataError.
132    ///
133    /// If possible, you should attach context using a `with_` function.
134    #[inline]
135    pub const fn into_error(self) -> DataError {
136        DataError {
137            kind: self,
138            key: None,
139            str_context: None,
140            silent: false,
141        }
142    }
143
144    /// Creates a DataError with a resource key context.
145    #[inline]
146    pub const fn with_key(self, key: DataKey) -> DataError {
147        self.into_error().with_key(key)
148    }
149
150    /// Creates a DataError with a string context.
151    #[inline]
152    pub const fn with_str_context(self, context: &'static str) -> DataError {
153        self.into_error().with_str_context(context)
154    }
155
156    /// Creates a DataError with a type name context.
157    #[inline]
158    pub fn with_type_context<T>(self) -> DataError {
159        self.into_error().with_type_context::<T>()
160    }
161
162    /// Creates a DataError with a request context.
163    #[inline]
164    pub fn with_req(self, key: DataKey, req: DataRequest) -> DataError {
165        self.into_error().with_req(key, req)
166    }
167}
168
169impl DataError {
170    /// Returns a new, empty DataError with kind Custom and a string error message.
171    #[inline]
172    pub const fn custom(str_context: &'static str) -> Self {
173        Self {
174            kind: DataErrorKind::Custom,
175            key: None,
176            str_context: Some(str_context),
177            silent: false,
178        }
179    }
180
181    /// Sets the resource key of a DataError, returning a modified error.
182    #[inline]
183    pub const fn with_key(self, key: DataKey) -> Self {
184        Self {
185            kind: self.kind,
186            key: Some(key),
187            str_context: self.str_context,
188            silent: self.silent,
189        }
190    }
191
192    /// Sets the string context of a DataError, returning a modified error.
193    #[inline]
194    pub const fn with_str_context(self, context: &'static str) -> Self {
195        Self {
196            kind: self.kind,
197            key: self.key,
198            str_context: Some(context),
199            silent: self.silent,
200        }
201    }
202
203    /// Sets the string context of a DataError to the given type name, returning a modified error.
204    #[inline]
205    pub fn with_type_context<T>(self) -> Self {
206        if !self.silent {
207            log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
208        }
209        self.with_str_context(core::any::type_name::<T>())
210    }
211
212    /// Logs the data error with the given request, returning an error containing the resource key.
213    ///
214    /// If the "logging" Cargo feature is enabled, this logs the whole request. Either way,
215    /// it returns an error with the resource key portion of the request as context.
216    pub fn with_req(mut self, key: DataKey, req: DataRequest) -> Self {
217        if req.metadata.silent {
218            self.silent = true;
219        }
220        // Don't write out a log for MissingDataKey since there is no context to add
221        if !self.silent && self.kind != DataErrorKind::MissingDataKey {
222            log::warn!("{} (key: {}, request: {})", self, key, req);
223        }
224        self.with_key(key)
225    }
226
227    /// Logs the data error with the given context, then return self.
228    ///
229    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
230    /// it will print out the context.
231    #[cfg(feature = "std")]
232    pub fn with_path_context<P: AsRef<std::path::Path> + ?Sized>(self, _path: &P) -> Self {
233        if !self.silent {
234            log::warn!("{} (path: {:?})", self, _path.as_ref());
235        }
236        self
237    }
238
239    /// Logs the data error with the given context, then return self.
240    ///
241    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
242    /// it will print out the context.
243    #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
244    #[inline]
245    pub fn with_display_context<D: fmt::Display + ?Sized>(self, context: &D) -> Self {
246        if !self.silent {
247            log::warn!("{}: {}", self, context);
248        }
249        self
250    }
251
252    /// Logs the data error with the given context, then return self.
253    ///
254    /// This does not modify the error, but if the "logging" Cargo feature is enabled,
255    /// it will print out the context.
256    #[cfg_attr(not(feature = "logging"), allow(unused_variables))]
257    #[inline]
258    pub fn with_debug_context<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
259        if !self.silent {
260            log::warn!("{}: {:?}", self, context);
261        }
262        self
263    }
264
265    #[inline]
266    pub(crate) fn for_type<T>() -> DataError {
267        DataError {
268            kind: DataErrorKind::MismatchedType(core::any::type_name::<T>()),
269            key: None,
270            str_context: None,
271            silent: false,
272        }
273    }
274}
275
276#[cfg(feature = "std")]
277impl std::error::Error for DataError {}
278
279#[cfg(feature = "std")]
280impl From<std::io::Error> for DataError {
281    fn from(e: std::io::Error) -> Self {
282        log::warn!("I/O error: {}", e);
283        DataErrorKind::Io(e.kind()).into_error()
284    }
285}