blob: 15ba3e16ceeeb879db5bd455aa32bbebfc131f3f [file] [log] [blame]
// 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<DataMarkerId>,
/// 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<T>(self) -> DataError {
self.into_error().with_type_context::<T>()
}
/// 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<T>(self) -> Self {
if !self.silent {
log::warn!("{self}: Type context: {}", core::any::type_name::<T>());
}
self.with_str_context(core::any::type_name::<T>())
}
/// 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<D: fmt::Display + ?Sized>(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<D: fmt::Debug + ?Sized>(self, context: &D) -> Self {
if !self.silent {
log::warn!("{}: {:?}", self, context);
}
self
}
#[inline]
pub(crate) fn for_type<T>() -> DataError {
DataError {
kind: DataErrorKind::Downcast(core::any::type_name::<T>()),
marker: None,
str_context: None,
silent: false,
}
}
}
impl core::error::Error for DataError {}
#[cfg(feature = "std")]
impl From<std::io::Error> 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<T, DataError>`.
pub trait ResultDataError<T>: Sized {
/// Propagates all errors other than [`DataErrorKind::IdentifierNotFound`], and returns `None` in that case.
fn allow_identifier_not_found(self) -> Result<Option<T>, DataError>;
}
impl<T> ResultDataError<T> for Result<T, DataError> {
fn allow_identifier_not_found(self) -> Result<Option<T>, DataError> {
match self {
Ok(t) => Ok(Some(t)),
Err(DataError {
kind: DataErrorKind::IdentifierNotFound,
..
}) => Ok(None),
Err(e) => Err(e),
}
}
}