ako 0.0.3

Ako is a Rust crate that offers a practical and human-friendly approach to creating, manipulating, formatting and converting dates, times and timestamps.
Documentation
use core::fmt::{self, Debug, Display, Formatter};

/// A specialized Result type for Ako.
pub type Result<T> = core::result::Result<T, Error>;

/// An error that can occur within the Ako crate.
///
/// Note that this type is intentionally opaque while the
/// API is unstable.
///
pub struct Error {
    pub(crate) kind: ErrorKind,
}

impl Error {
    /// Raises this error as a `panic`. Intended for use in a `const` and `static` context.
    #[doc(hidden)]
    #[track_caller]
    pub const fn panic(self) -> ! {
        panic!("{}", self.kind.description());
    }
}

#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum ErrorKind {
    /// Component was out of permitted range.
    OutOfRange,

    /// Parsing was successful; however, there are trailing characters remaining in the input.
    ParseTrailing,

    /// Unexpected character encountered while parsing.
    ParseInvalid,

    /// Input has prematurely ended while parsing.
    ParseNotEnough,

    /// Malformed time-zone interchange file (TZif).
    TimeZoneFile,
}

impl ErrorKind {
    pub const fn description(self) -> &'static str {
        match self {
            Self::OutOfRange => "component out of permitted range",
            Self::ParseTrailing => "unexpected trailing characters in input while parsing",
            Self::ParseInvalid => "input contains invalid characters while parsing",
            Self::ParseNotEnough => "input prematurely ended while parsing",
            Self::TimeZoneFile => "malformed time-zone interchange file (TZif)",
        }
    }
}

impl Debug for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.kind.fmt(f)
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.pad(self.kind.description())
    }
}

impl core::error::Error for Error {}

impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Self {
        Self { kind }
    }
}

// raises `OutOfRange` if the value is out of range
macro_rules! ensure_range {
    ($min:expr, $max:expr, $value:expr) => {
        #[allow(unused_comparisons)]
        if $value > $max || $value < $min {
            return Err($crate::Error {
                kind: $crate::error::ErrorKind::OutOfRange,
            });
        }
    };
}

#[allow(unused)]
pub trait ResultExt<T>: Sized {
    fn context(self, kind: ErrorKind) -> Result<T>;

    fn context_tzif(self) -> Result<T> {
        self.context(ErrorKind::TimeZoneFile)
    }
}

impl<T, E: core::error::Error> ResultExt<T> for core::result::Result<T, E> {
    fn context(self, kind: ErrorKind) -> Result<T> {
        self.map_err(|error| {
            log::error!("{}: {}", kind.description(), error);

            kind.into()
        })
    }
}