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 ).
45use crate::_internal::log;
6use crate::buf::BufferFormat;
7use crate::prelude::*;
8use core::fmt;
9use displaydoc::Display;
1011/// 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")]
20MissingDataKey,
2122/// There is data for the key, but not for this particular locale.
23#[displaydoc("Missing data for locale")]
24MissingLocale,
2526/// The request should include a locale.
27#[displaydoc("Request needs a locale")]
28NeedsLocale,
2930/// The request should not contain a locale.
31#[displaydoc("Request has an extraneous locale")]
32ExtraneousLocale,
3334/// The resource was blocked by a filter. The resource may or may not be available.
35#[displaydoc("Resource blocked by filter")]
36FilteredResource,
3738/// 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")]
41MismatchedType(&'static str),
4243/// The payload is missing. This is usually caused by a previous error.
44#[displaydoc("Missing payload")]
45MissingPayload,
4647/// A data provider object was given to an operation in an invalid state.
48#[displaydoc("Invalid state")]
49InvalidState,
5051/// The syntax of the [`DataKey`] or [`DataLocale`] was invalid.
52#[displaydoc("Parse error for data key or data locale")]
53KeyLocaleSyntax,
5455/// An unspecified error occurred, such as a Serde error.
56 ///
57 /// Check debug logs for potentially more information.
58#[displaydoc("Custom")]
59Custom,
6061/// An error occurred while accessing a system resource.
62#[displaydoc("I/O error: {0:?}")]
63 #[cfg(feature = "std")]
64Io(std::io::ErrorKind),
6566/// An unspecified data source containing the required data is unavailable.
67#[displaydoc("Missing source data")]
68 #[cfg(feature = "datagen")]
69MissingSourceData,
7071/// 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?)")]
74UnavailableBufferFormat(BufferFormat),
75}
7677/// 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.
102pub kind: DataErrorKind,
103104/// The data key of the request, if available.
105pub key: Option<DataKey>,
106107/// Additional context, if available.
108pub str_context: Option<&'static str>,
109110/// Whether this error was created in silent mode to not log.
111pub silent: bool,
112}
113114impl fmt::Display for DataError {
115fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116write!(f, "ICU4X data error")?;
117if self.kind != DataErrorKind::Custom {
118write!(f, ": {}", self.kind)?;
119 }
120if let Some(key) = self.key {
121write!(f, " (key: {key})")?;
122 }
123if let Some(str_context) = self.str_context {
124write!(f, ": {str_context}")?;
125 }
126Ok(())
127 }
128}
129130impl DataErrorKind {
131/// Converts this DataErrorKind into a DataError.
132 ///
133 /// If possible, you should attach context using a `with_` function.
134#[inline]
135pub const fn into_error(self) -> DataError {
136 DataError {
137 kind: self,
138 key: None,
139 str_context: None,
140 silent: false,
141 }
142 }
143144/// Creates a DataError with a resource key context.
145#[inline]
146pub const fn with_key(self, key: DataKey) -> DataError {
147self.into_error().with_key(key)
148 }
149150/// Creates a DataError with a string context.
151#[inline]
152pub const fn with_str_context(self, context: &'static str) -> DataError {
153self.into_error().with_str_context(context)
154 }
155156/// Creates a DataError with a type name context.
157#[inline]
158pub fn with_type_context<T>(self) -> DataError {
159self.into_error().with_type_context::<T>()
160 }
161162/// Creates a DataError with a request context.
163#[inline]
164pub fn with_req(self, key: DataKey, req: DataRequest) -> DataError {
165self.into_error().with_req(key, req)
166 }
167}
168169impl DataError {
170/// Returns a new, empty DataError with kind Custom and a string error message.
171#[inline]
172pub const fn custom(str_context: &'static str) -> Self {
173Self {
174 kind: DataErrorKind::Custom,
175 key: None,
176 str_context: Some(str_context),
177 silent: false,
178 }
179 }
180181/// Sets the resource key of a DataError, returning a modified error.
182#[inline]
183pub const fn with_key(self, key: DataKey) -> Self {
184Self {
185 kind: self.kind,
186 key: Some(key),
187 str_context: self.str_context,
188 silent: self.silent,
189 }
190 }
191192/// Sets the string context of a DataError, returning a modified error.
193#[inline]
194pub const fn with_str_context(self, context: &'static str) -> Self {
195Self {
196 kind: self.kind,
197 key: self.key,
198 str_context: Some(context),
199 silent: self.silent,
200 }
201 }
202203/// Sets the string context of a DataError to the given type name, returning a modified error.
204#[inline]
205pub fn with_type_context<T>(self) -> Self {
206if !self.silent {
207log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
208 }
209self.with_str_context(core::any::type_name::<T>())
210 }
211212/// 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.
216pub fn with_req(mut self, key: DataKey, req: DataRequest) -> Self {
217if req.metadata.silent {
218self.silent = true;
219 }
220// Don't write out a log for MissingDataKey since there is no context to add
221if !self.silent && self.kind != DataErrorKind::MissingDataKey {
222log::warn!("{} (key: {}, request: {})", self, key, req);
223 }
224self.with_key(key)
225 }
226227/// 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")]
232pub fn with_path_context<P: AsRef<std::path::Path> + ?Sized>(self, _path: &P) -> Self {
233if !self.silent {
234log::warn!("{} (path: {:?})", self, _path.as_ref());
235 }
236self
237}
238239/// 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]
245pub fn with_display_context<D: fmt::Display + ?Sized>(self, context: &D) -> Self {
246if !self.silent {
247log::warn!("{}: {}", self, context);
248 }
249self
250}
251252/// 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]
258pub fn with_debug_context<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
259if !self.silent {
260log::warn!("{}: {:?}", self, context);
261 }
262self
263}
264265#[inline]
266pub(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}
275276#[cfg(feature = "std")]
277impl std::error::Error for DataError {}
278279#[cfg(feature = "std")]
280impl From<std::io::Error> for DataError {
281fn from(e: std::io::Error) -> Self {
282log::warn!("I/O error: {}", e);
283 DataErrorKind::Io(e.kind()).into_error()
284 }
285}