// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::log; use crate::{marker::DataMarkerId, prelude::*}; use core::fmt; use displaydoc::Display; /// A list specifying general categories of data provider error. /// /// Errors may be caused either by a malformed request or by the data provider /// not being able to fulfill a well-formed request. #[derive(Clone, Copy, Eq, PartialEq, Display, Debug)] #[non_exhaustive] pub enum DataErrorKind { /// No data for the requested data marker. This is only returned by [`DynamicDataProvider`]. #[displaydoc("Missing data for marker")] MarkerNotFound, /// There is data for the data marker, but not for this particular data identifier. #[displaydoc("Missing data for identifier")] IdentifierNotFound, /// The request is invalid, such as a request for a singleton marker containing a data identifier. #[displaydoc("Invalid request")] InvalidRequest, /// The data for two [`DataMarker`]s is not consistent. #[displaydoc("The data for two markers is not consistent: {0:?} (were they generated in different datagen invocations?)")] InconsistentData(DataMarkerInfo), /// An error occured during [`Any`](core::any::Any) downcasting. #[displaydoc("Downcast: expected {0}, found")] Downcast(&'static str), /// An error occured during [`serde`] deserialization. /// /// Check debug logs for potentially more information. #[displaydoc("Deserialize")] Deserialize, /// An unspecified error occurred. /// /// Check debug logs for potentially more information. #[displaydoc("Custom")] Custom, /// An error occurred while accessing a system resource. #[displaydoc("I/O: {0:?}")] #[cfg(feature = "std")] Io(std::io::ErrorKind), } /// The error type for ICU4X data provider operations. /// /// To create one of these, either start with a [`DataErrorKind`] or use [`DataError::custom()`]. /// /// # Example /// /// Create a IdentifierNotFound error and attach a data request for context: /// /// ```no_run /// # use icu_provider::prelude::*; /// let marker: DataMarkerInfo = unimplemented!(); /// let req: DataRequest = unimplemented!(); /// DataErrorKind::IdentifierNotFound.with_req(marker, req); /// ``` /// /// Create a named custom error: /// /// ``` /// # use icu_provider::prelude::*; /// DataError::custom("This is an example error"); /// ``` #[derive(Clone, Copy, Eq, PartialEq, Debug)] #[non_exhaustive] pub struct DataError { /// Broad category of the error. pub kind: DataErrorKind, /// The data marker of the request, if available. pub marker: Option, /// Additional context, if available. pub str_context: Option<&'static str>, /// Whether this error was created in silent mode to not log. pub silent: bool, } impl fmt::Display for DataError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ICU4X data error")?; if self.kind != DataErrorKind::Custom { write!(f, ": {}", self.kind)?; } if let Some(marker) = self.marker { write!(f, " (marker: {marker:?})")?; } if let Some(str_context) = self.str_context { write!(f, ": {str_context}")?; } Ok(()) } } impl DataErrorKind { /// Converts this DataErrorKind into a DataError. /// /// If possible, you should attach context using a `with_` function. #[inline] pub const fn into_error(self) -> DataError { DataError { kind: self, marker: None, str_context: None, silent: false, } } /// Creates a DataError with a data marker context. #[inline] pub const fn with_marker(self, marker: DataMarkerInfo) -> DataError { self.into_error().with_marker(marker) } /// Creates a DataError with a string context. #[inline] pub const fn with_str_context(self, context: &'static str) -> DataError { self.into_error().with_str_context(context) } /// Creates a DataError with a type name context. #[inline] pub fn with_type_context(self) -> DataError { self.into_error().with_type_context::() } /// Creates a DataError with a request context. #[inline] pub fn with_req(self, marker: DataMarkerInfo, req: DataRequest) -> DataError { self.into_error().with_req(marker, req) } } impl DataError { /// Returns a new, empty DataError with kind Custom and a string error message. #[inline] pub const fn custom(str_context: &'static str) -> Self { Self { kind: DataErrorKind::Custom, marker: None, str_context: Some(str_context), silent: false, } } /// Sets the data marker of a DataError, returning a modified error. #[inline] pub const fn with_marker(self, marker: DataMarkerInfo) -> Self { Self { kind: self.kind, marker: Some(marker.id), str_context: self.str_context, silent: self.silent, } } /// Sets the string context of a DataError, returning a modified error. #[inline] pub const fn with_str_context(self, context: &'static str) -> Self { Self { kind: self.kind, marker: self.marker, str_context: Some(context), silent: self.silent, } } /// Sets the string context of a DataError to the given type name, returning a modified error. #[inline] pub fn with_type_context(self) -> Self { if !self.silent { log::warn!("{self}: Type context: {}", core::any::type_name::()); } self.with_str_context(core::any::type_name::()) } /// Logs the data error with the given request, returning an error containing the data marker. /// /// If the "logging" Cargo feature is enabled, this logs the whole request. Either way, /// it returns an error with the data marker portion of the request as context. pub fn with_req(mut self, marker: DataMarkerInfo, req: DataRequest) -> Self { if req.metadata.silent { self.silent = true; } // Don't write out a log for MissingDataMarker since there is no context to add if !self.silent && self.kind != DataErrorKind::MarkerNotFound { log::warn!("{self} (marker: {marker:?}, request: {})", req.id); } self.with_marker(marker) } /// Logs the data error with the given context, then return self. /// /// This does not modify the error, but if the "logging" Cargo feature is enabled, /// it will print out the context. #[cfg(feature = "std")] pub fn with_path_context(self, _path: &std::path::Path) -> Self { if !self.silent { log::warn!("{self} (path: {_path:?})"); } self } /// Logs the data error with the given context, then return self. /// /// This does not modify the error, but if the "logging" Cargo feature is enabled, /// it will print out the context. #[cfg_attr(not(feature = "logging"), allow(unused_variables))] #[inline] pub fn with_display_context(self, context: &D) -> Self { if !self.silent { log::warn!("{}: {}", self, context); } self } /// Logs the data error with the given context, then return self. /// /// This does not modify the error, but if the "logging" Cargo feature is enabled, /// it will print out the context. #[cfg_attr(not(feature = "logging"), allow(unused_variables))] #[inline] pub fn with_debug_context(self, context: &D) -> Self { if !self.silent { log::warn!("{}: {:?}", self, context); } self } #[inline] pub(crate) fn for_type() -> DataError { DataError { kind: DataErrorKind::Downcast(core::any::type_name::()), marker: None, str_context: None, silent: false, } } } impl core::error::Error for DataError {} #[cfg(feature = "std")] impl From for DataError { fn from(e: std::io::Error) -> Self { log::warn!("I/O error: {}", e); DataErrorKind::Io(e.kind()).into_error() } } /// Extension trait for `Result`. pub trait ResultDataError: Sized { /// Propagates all errors other than [`DataErrorKind::IdentifierNotFound`], and returns `None` in that case. fn allow_identifier_not_found(self) -> Result, DataError>; } impl ResultDataError for Result { fn allow_identifier_not_found(self) -> Result, DataError> { match self { Ok(t) => Ok(Some(t)), Err(DataError { kind: DataErrorKind::IdentifierNotFound, .. }) => Ok(None), Err(e) => Err(e), } } }