282 lines
9.0 KiB
Rust
282 lines
9.0 KiB
Rust
// 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),
|
|
}
|
|
}
|
|
}
|