use std::error::Error as StdError;
use std::fmt::{self, Debug, Display};
use std::ops::Not;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Simple(String),
Detailed(DetailedError),
Custom(Box<dyn StdError + Send + Sync>),
}
impl Error {
pub fn simple<T: Display>(message: T) -> Self {
Self::Simple(message.to_string())
}
pub fn detailed(details: DetailedError) -> Self {
Self::Detailed(details)
}
pub fn custom<T: StdError + Send + Sync + 'static>(error: T) -> Self {
Self::Custom(Box::new(error))
}
pub fn print(&self) {
match self {
Self::Simple(message) => println!("Error: {}", message),
Self::Detailed(details) => details.print(),
Self::Custom(error) => println!("Error: {}", error),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Simple(message) => write!(f, "{}", message),
Self::Detailed(details) => Display::fmt(details, f),
Self::Custom(error) => Display::fmt(error, f),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::Custom(err) => err.source(),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct DetailedError {
message: String,
help: Option<String>,
context: Vec<(String, String)>,
}
impl DetailedError {
pub fn new<T: Display>(message: T) -> Self {
Self {
message: message.to_string(),
help: None,
context: Vec::new(),
}
}
pub fn help<T: Display>(mut self, help: T) -> Self {
self.help = Some(help.to_string());
self
}
pub fn with<T: Display, U: Display>(mut self, key: T, value: U) -> Self {
self.context.push((key.to_string(), value.to_string()));
self
}
pub fn print(&self) {
println!("Error: {}", self.message);
for (key, value) in &self.context {
println!("{}: {}", key, value);
}
if let Some(help) = &self.help {
println!("Help: {}", help);
}
}
}
impl Display for DetailedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
if !self.context.is_empty() {
write!(f, " (")?;
for (i, (key, value)) in self.context.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", key, value)?;
}
write!(f, ")")?;
}
if let Some(help) = &self.help {
write!(f, " - {}", help)?;
}
Ok(())
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::Simple(s)
}
}
impl From<&str> for Error {
fn from(s: &str) -> Self {
Error::Simple(s.to_string())
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Custom(Box::new(error))
}
}
pub trait CustomError: StdError + Send + Sync + 'static {}
impl<T> CustomError for T where T: StdError + Send + Sync + 'static {}
pub trait IntoResult<T> {
fn into_result(self) -> Result<T>;
}
impl<T, E> IntoResult<T> for std::result::Result<T, E>
where
E: Into<Error>,
{
fn into_result(self) -> Result<T> {
self.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
#[test]
fn test_simple_error() {
let err = Error::simple("A simple error occurred");
err.print();
assert_eq!(format!("{}", err), "A simple error occurred");
}
#[test]
fn test_detailed_error() {
let err = DetailedError::new("A detailed error occurred")
.with("File", "config.toml")
.with("Line", "42")
.help("Please check the configuration file for errors");
let error = Error::detailed(err.clone());
error.print();
assert_eq!(
format!("{}", error),
"A detailed error occurred (File: config.toml, Line: 42) - Please check the configuration file for errors"
);
}
#[test]
fn test_custom_error() {
let file_error = File::open("non_existent_file.txt").unwrap_err();
let error = Error::custom(file_error);
error.print();
assert!(format!("{}", error).contains("No such file or directory"));
}
#[test]
fn test_into_result() -> Result<()> {
fn might_fail(flag: bool) -> std::result::Result<(), &'static str> {
if flag {
Ok(())
} else {
Err("Something went wrong")
}
}
let result = might_fail(false).into_result();
match result {
Ok(_) => {
println!("Operation succeeded.");
}
Err(e) => {
e.print();
}
}
Ok(())
}
}