// 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::buf::BufferFormat; use crate::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 provided resource key. #[displaydoc("Missing data for key")] MissingDataKey, /// There is data for the key, but not for this particular locale. #[displaydoc("Missing data for locale")] MissingLocale, /// The request should include a locale. #[displaydoc("Request needs a locale")] NeedsLocale, /// The request should not contain a locale. #[displaydoc("Request has an extraneous locale")] ExtraneousLocale, /// The resource was blocked by a filter. The resource may or may not be available. #[displaydoc("Resource blocked by filter")] FilteredResource, /// The generic type parameter does not match the TypeId. The expected type name is stored /// as context when this error is returned. #[displaydoc("Mismatched types: tried to downcast with {0}, but actual type is different")] MismatchedType(&'static str), /// The payload is missing. This is usually caused by a previous error. #[displaydoc("Missing payload")] MissingPayload, /// A data provider object was given to an operation in an invalid state. #[displaydoc("Invalid state")] InvalidState, /// An unspecified error occurred, such as a Serde error. /// /// Check debug logs for potentially more information. #[displaydoc("Custom")] Custom, /// An error occurred while accessing a system resource. #[displaydoc("I/O error: {0:?}")] #[cfg(feature = "std")] Io(std::io::ErrorKind), /// An unspecified data source containing the required data is unavailable. #[displaydoc("Missing source data")] #[cfg(feature = "datagen")] MissingSourceData, /// An error indicating that the desired buffer format is not available. This usually /// means that a required Cargo feature was not enabled #[displaydoc("Unavailable buffer format: {0:?} (does icu_provider need to be compiled with an additional Cargo feature?)")] UnavailableBufferFormat(BufferFormat), } /// 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 NeedsLocale error and attach a data request for context: /// /// ```no_run /// # use icu_provider::prelude::*; /// let key: DataKey = unimplemented!(); /// let req: DataRequest = unimplemented!(); /// DataErrorKind::NeedsLocale.with_req(key, 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 key of the request, if available. pub key: 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(key) = self.key { write!(f, " (key: {key})")?; } 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, key: None, str_context: None, silent: false, } } /// Creates a DataError with a resource key context. #[inline] pub const fn with_key(self, key: DataKey) -> DataError { self.into_error().with_key(key) } /// 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, key: DataKey, req: DataRequest) -> DataError { self.into_error().with_req(key, 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, key: None, str_context: Some(str_context), silent: false, } } /// Sets the resource key of a DataError, returning a modified error. #[inline] pub const fn with_key(self, key: DataKey) -> Self { Self { kind: self.kind, key: Some(key), 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, key: self.key, 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 { self.with_str_context(core::any::type_name::()) } /// Logs the data error with the given request, returning an error containing the resource key. /// /// If the "log_error_context" Cargo feature is enabled, this logs the whole request. Either way, /// it returns an error with the resource key portion of the request as context. #[cfg_attr(not(feature = "log_error_context"), allow(unused_variables))] pub fn with_req(mut self, key: DataKey, req: DataRequest) -> Self { if req.metadata.silent { self.silent = true; } // Don't write out a log for MissingDataKey since there is no context to add #[cfg(feature = "log_error_context")] if !self.silent && self.kind != DataErrorKind::MissingDataKey { log::warn!("{} (key: {}, request: {})", self, key, req); } self.with_key(key) } /// Logs the data error with the given context, then return self. /// /// This does not modify the error, but if the "log_error_context" Cargo feature is enabled, /// it will print out the context. #[cfg(feature = "std")] #[cfg_attr(not(feature = "log_error_context"), allow(unused_variables))] pub fn with_path_context + ?Sized>(self, path: &P) -> Self { #[cfg(feature = "log_error_context")] if !self.silent { log::warn!("{} (path: {:?})", self, path.as_ref()); } self } /// Logs the data error with the given context, then return self. /// /// This does not modify the error, but if the "log_error_context" Cargo feature is enabled, /// it will print out the context. #[cfg_attr(not(feature = "log_error_context"), allow(unused_variables))] #[inline] pub fn with_display_context(self, context: &D) -> Self { #[cfg(feature = "log_error_context")] 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 "log_error_context" Cargo feature is enabled, /// it will print out the context. #[cfg_attr(not(feature = "log_error_context"), allow(unused_variables))] #[inline] pub fn with_debug_context(self, context: &D) -> Self { #[cfg(feature = "log_error_context")] if !self.silent { log::warn!("{}: {:?}", self, context); } self } #[inline] pub(crate) fn for_type() -> DataError { DataError { kind: DataErrorKind::MismatchedType(core::any::type_name::()), key: None, str_context: None, silent: false, } } } #[cfg(feature = "std")] impl std::error::Error for DataError {} #[cfg(feature = "std")] impl From for DataError { fn from(e: std::io::Error) -> Self { #[cfg(feature = "log_error_context")] log::warn!("I/O error: {}", e); DataErrorKind::Io(e.kind()).into_error() } }