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::log;
6use crate::{marker::DataMarkerId, prelude::*};
7use core::fmt;
8use displaydoc::Display;
910/// 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")]
19MarkerNotFound,
2021/// There is data for the data marker, but not for this particular data identifier.
22#[displaydoc("Missing data for identifier")]
23IdentifierNotFound,
2425/// The request is invalid, such as a request for a singleton marker containing a data identifier.
26#[displaydoc("Invalid request")]
27InvalidRequest,
2829/// 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?)")]
31InconsistentData(DataMarkerInfo),
3233/// An error occured during [`Any`](core::any::Any) downcasting.
34#[displaydoc("Downcast: expected {0}, found")]
35Downcast(&'static str),
3637/// An error occured during [`serde`] deserialization.
38 ///
39 /// Check debug logs for potentially more information.
40#[displaydoc("Deserialize")]
41Deserialize,
4243/// An unspecified error occurred.
44 ///
45 /// Check debug logs for potentially more information.
46#[displaydoc("Custom")]
47Custom,
4849/// An error occurred while accessing a system resource.
50#[displaydoc("I/O: {0:?}")]
51 #[cfg(feature = "std")]
52Io(std::io::ErrorKind),
53}
5455/// 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.
80pub kind: DataErrorKind,
8182/// The data marker of the request, if available.
83pub marker: Option<DataMarkerId>,
8485/// Additional context, if available.
86pub str_context: Option<&'static str>,
8788/// Whether this error was created in silent mode to not log.
89pub silent: bool,
90}
9192impl fmt::Display for DataError {
93fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94write!(f, "ICU4X data error")?;
95if self.kind != DataErrorKind::Custom {
96write!(f, ": {}", self.kind)?;
97 }
98if let Some(marker) = self.marker {
99write!(f, " (marker: {marker:?})")?;
100 }
101if let Some(str_context) = self.str_context {
102write!(f, ": {str_context}")?;
103 }
104Ok(())
105 }
106}
107108impl DataErrorKind {
109/// Converts this DataErrorKind into a DataError.
110 ///
111 /// If possible, you should attach context using a `with_` function.
112#[inline]
113pub const fn into_error(self) -> DataError {
114 DataError {
115 kind: self,
116 marker: None,
117 str_context: None,
118 silent: false,
119 }
120 }
121122/// Creates a DataError with a data marker context.
123#[inline]
124pub const fn with_marker(self, marker: DataMarkerInfo) -> DataError {
125self.into_error().with_marker(marker)
126 }
127128/// Creates a DataError with a string context.
129#[inline]
130pub const fn with_str_context(self, context: &'static str) -> DataError {
131self.into_error().with_str_context(context)
132 }
133134/// Creates a DataError with a type name context.
135#[inline]
136pub fn with_type_context<T>(self) -> DataError {
137self.into_error().with_type_context::<T>()
138 }
139140/// Creates a DataError with a request context.
141#[inline]
142pub fn with_req(self, marker: DataMarkerInfo, req: DataRequest) -> DataError {
143self.into_error().with_req(marker, req)
144 }
145}
146147impl DataError {
148/// Returns a new, empty DataError with kind Custom and a string error message.
149#[inline]
150pub const fn custom(str_context: &'static str) -> Self {
151Self {
152 kind: DataErrorKind::Custom,
153 marker: None,
154 str_context: Some(str_context),
155 silent: false,
156 }
157 }
158159/// Sets the data marker of a DataError, returning a modified error.
160#[inline]
161pub const fn with_marker(self, marker: DataMarkerInfo) -> Self {
162Self {
163 kind: self.kind,
164 marker: Some(marker.id),
165 str_context: self.str_context,
166 silent: self.silent,
167 }
168 }
169170/// Sets the string context of a DataError, returning a modified error.
171#[inline]
172pub const fn with_str_context(self, context: &'static str) -> Self {
173Self {
174 kind: self.kind,
175 marker: self.marker,
176 str_context: Some(context),
177 silent: self.silent,
178 }
179 }
180181/// Sets the string context of a DataError to the given type name, returning a modified error.
182#[inline]
183pub fn with_type_context<T>(self) -> Self {
184if !self.silent {
185log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
186 }
187self.with_str_context(core::any::type_name::<T>())
188 }
189190/// 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.
194pub fn with_req(mut self, marker: DataMarkerInfo, req: DataRequest) -> Self {
195if req.metadata.silent {
196self.silent = true;
197 }
198// Don't write out a log for MissingDataMarker since there is no context to add
199if !self.silent && self.kind != DataErrorKind::MarkerNotFound {
200log::warn!("{self} (marker: {marker:?}, request: {})", req.id);
201 }
202self.with_marker(marker)
203 }
204205/// 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")]
210pub fn with_path_context(self, _path: &std::path::Path) -> Self {
211if !self.silent {
212log::warn!("{self} (path: {_path:?})");
213 }
214self
215}
216217/// 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]
223pub fn with_display_context<D: fmt::Display + ?Sized>(self, context: &D) -> Self {
224if !self.silent {
225log::warn!("{}: {}", self, context);
226 }
227self
228}
229230/// 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]
236pub fn with_debug_context<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
237if !self.silent {
238log::warn!("{}: {:?}", self, context);
239 }
240self
241}
242243#[inline]
244pub(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}
253254impl core::error::Error for DataError {}
255256#[cfg(feature = "std")]
257impl From<std::io::Error> for DataError {
258fn from(e: std::io::Error) -> Self {
259log::warn!("I/O error: {}", e);
260 DataErrorKind::Io(e.kind()).into_error()
261 }
262}
263264/// 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.
267fn allow_identifier_not_found(self) -> Result<Option<T>, DataError>;
268}
269270impl<T> ResultDataError<T> for Result<T, DataError> {
271fn allow_identifier_not_found(self) -> Result<Option<T>, DataError> {
272match self {
273Ok(t) => Ok(Some(t)),
274Err(DataError {
275 kind: DataErrorKind::IdentifierNotFound,
276 ..
277 }) => Ok(None),
278Err(e) => Err(e),
279 }
280 }
281}