use std::{cmp::max, fmt::Display};
mod macros;
#[allow(unused_imports)]
pub use macros::*;
pub const DEFAULT_LOG_LEVEL: Level = Level::Debug;
pub trait Log {
fn log(&self);
}
impl Log for Error {
fn log(&self) {
let mut error_level = self.level;
if error_level == Level::Unspecified {
error_level = DEFAULT_LOG_LEVEL;
}
match error_level {
Level::Trace => {
tracing::trace!("{}", self.message);
}
Level::Debug => {
tracing::debug!("{}", self.message);
}
Level::Info => {
tracing::info!("{}", self.message);
}
Level::Warn => {
tracing::warn!("{}", self.message);
}
Level::Error => {
tracing::error!("{}", self.message);
}
Level::Unspecified => {}
}
}
}
impl<T> Log for Result<T> {
fn log(&self) {
let error = match self {
Ok(_) => {
return;
}
Err(e) => e,
};
let mut error_level = error.level;
if error_level == Level::Unspecified {
error_level = DEFAULT_LOG_LEVEL;
}
match error_level {
Level::Trace => {
tracing::trace!("{}", error.message);
}
Level::Debug => {
tracing::debug!("{}", error.message);
}
Level::Info => {
tracing::info!("{}", error.message);
}
Level::Warn => {
tracing::warn!("{}", error.message);
}
Level::Error => {
tracing::error!("{}", error.message);
}
Level::Unspecified => {}
}
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct Error {
pub level: Level,
pub message: String,
}
impl std::error::Error for Error {}
pub trait Wrap<T> {
fn wrap(self) -> Result<T>;
}
impl<T, E> Wrap<T> for std::result::Result<T, E>
where
E: Display,
{
fn wrap(self) -> Result<T> {
match self {
Ok(t) => Ok(t),
Err(e) => Err(Error {
level: Level::Unspecified,
message: format!("{e}"),
}),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum Level {
Unspecified,
Trace,
Debug,
Info,
Warn,
Error,
}
fn concatenate(error: &String, cause: &String) -> String {
format!("{error}\ncaused by: {cause}")
}
pub trait Context<T, E> {
fn context(self, error: E) -> Result<T>;
}
impl<T> Context<T, Error> for Result<T> {
fn context(self, error: Error) -> Result<T> {
match self {
Ok(t) => Ok(t),
Err(cause) => Err(Error {
level: max(error.level, cause.level),
message: concatenate(&error.message, &format!("{cause}")),
}),
}
}
}
impl<T, F> Context<T, F> for Result<T>
where
F: Fn(Error) -> Error,
{
fn context(self, error: F) -> Result<T> {
match self {
Ok(t) => Ok(t),
Err(cause) => Err(Error {
level: max(error(cause.clone()).level, cause.level),
message: concatenate(&error(cause.clone()).message, &format!("{cause}")),
}),
}
}
}
impl<T> Context<T, Error> for Option<T> {
fn context(self, error: Error) -> Result<T> {
match self {
Some(t) => Ok(t),
None => Err(error),
}
}
}
impl<'a, T> Context<&'a mut T, Error> for &'a mut Option<T> {
fn context(self, error: Error) -> Result<&'a mut T> {
match self {
Some(t) => Ok(t),
None => Err(error),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ordering() {
assert!(Level::Trace < Level::Debug);
assert!(Level::Debug < Level::Info);
assert!(Level::Info < Level::Warn);
assert!(Level::Warn < Level::Error);
assert!(max(Level::Trace, Level::Error) == Level::Error);
}
}