explicit-error-exit 0.3.0

Explicit concrete Error type to manage errors that end a process/program
Documentation
//! Built on top of [`explicit-error`](https://crates.io/crates/explicit-error), it provides idiomatic tools to manage errors that ends a process/program.
//! Based on the [explicit-error](explicit_error) crate, its chore tenet is to favor explicitness by inlining the error output while remaining concise.
//!
//! The key features are:
//! - Provide [MainResult] as a returned type of crate's main function to have well formated error.
//! - Explicitly mark any error wrapped in a [Result] as a [Fault], a backtrace is captured.
//! - Inline transformation of any errors wrapped in a [Result] into an [Error].
//! - A derive macro [ExitError](derive::ExitError) to easily declare how enum or struct errors transform into an [Error].
//! - Add context to errors to help debug.
//!
//! # A tour of explicit-error-bin
//!
//! The cornerstone of the library is the [Error] type. Use `Result<T, explicit_error_http::Error>`, or equivalently `explicit_error_bin::Result<T>`, as the return type of any faillible function returning errors that can end the program.
//!
//! ## Inline
//!
//! In the body of the function you can explicitly turn errors as exit errors using [ExitError] or marking them as [Fault].
//! ```no_run
//! use explicit_error_exit::{prelude::*, ExitError, Result, Fault, MainResult};
//! use std::process::ExitCode;
//! // Import the prelude to enable functions on std::result::Result
//!
//! fn main() -> MainResult { // Error message returned: "Error: Something went wrong because .."
//!     business_logic()?;
//!     Ok(())
//! }
//!
//! fn business_logic() -> Result<()> {
//!     Err(42).map_err(|e|
//!         ExitError::new(
//!             "Something went wrong because ..",
//!             ExitCode::from(e)
//!         )
//!     )?;
//!
//!     Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
//!         .or_fault()?;
//!     
//!     // Same behavior as or_fault() but the error is not captured as a source because it does not implement `[std::error::Error]`
//!     Err("error message").or_fault_no_source()?;
//!
//!     if 1 > 2 {
//!         Err(Fault::new()
//!             .with_context("Usefull context to help debug."))?;
//!     }
//!
//!     Ok(())
//! }
//!```
//!
//! ## Enum and struct
//!
//! Domain errors are often represented as enum or struct as they are raised in different places.
//! To easily enable the conversion to [Error] use the [ExitError](derive::ExitError) derive and implement `From<&MyError> for ExitError`.
//!
//! ```rust
//! use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
//! use std::process::ExitCode;
//!
//! #[derive(ExitError, Debug)]
//! enum MyError {
//!     Foo,
//! }
//!
//! impl From<&MyError> for ExitError {
//!     fn from(value: &MyError) -> Self {
//!         match value {
//!             MyError::Foo => ExitError::new(
//!                     "Something went wrong because ..",
//!                     ExitCode::from(42)
//!                 ),
//!         }
//!     }
//! }
//!
//! fn business_logic() -> Result<()> {
//!     Err(MyError::Foo)?;
//!
//!     Ok(())
//! }
//! ```
//!
//! Note: The [ExitError](derive::ExitError) derive implements the conversion to [Error], the impl of [Display](std::fmt::Display) and [std::error::Error].
//!
//! # Pattern matching
//!
//! One of the drawbacks of using one and only one return type for different domain functions is that callers loose the ability to pattern match on the returned error.
//! A solution is provided using [try_map_on_source](explicit_error::ResultError::try_map_on_source) on any `Result<T, Error>`, or equivalently `explicit_error_exit::Result<T>`.
//!
//! ```rust
//! use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
//! use std::process::ExitCode;
//!
//! #[derive(ExitError, Debug)]
//! enum MyError {
//!     Foo,
//!     Bar
//! }
//!
//! # impl From<&MyError> for ExitError {
//! #     fn from(value: &MyError) -> Self {
//! #         ExitError::new(
//! #           "Something went wrong because ..",
//! #           ExitCode::from(42))
//! #     }
//! # }
//! fn business_logic() -> Result<()> {
//!     let err: Result<()> = Err(MyError::Foo)?;
//!
//!     // Do the map if the source's type of the Error is MyError
//!     err.try_map_on_source(|e| {
//!         match e {
//!             MyError::Foo => ExitError::new(
//!                 "Foo",
//!                 ExitCode::SUCCESS),
//!             MyError::Bar => ExitError::new(
//!                 "Bar",
//!                 ExitCode::FAILURE),
//!         }
//!     })?;
//!
//!     Ok(())
//! }
//! ```
//!
//! Note: under the hood [try_map_on_source](explicit_error::ResultError::try_map_on_source) perform some downcasting.
mod domain;
mod error;

use std::process::{ExitCode, Termination};

pub use domain::*;
pub use error::*;

pub type Error = explicit_error::Error<DomainError>;
pub type Result<T> = std::result::Result<T, Error>;
pub type MainResult = std::result::Result<(), MainError>;

/// Re-import from [explicit_error] crate.
pub use explicit_error::Fault;

pub mod prelude {
    pub use crate::ResultDomainWithContext;
    pub use explicit_error::prelude::*;
}

pub mod derive {
    pub use explicit_error_derive::ExitError;
}

/// Crate's main function returned type. It implements [Termination] to properly format console error.
///
/// To have your own termination custom logic, you can re-implement an equivalent of [MainError]. Have a look at source it is straightforward.
pub struct MainError(Error);

impl Termination for MainError {
    fn report(self) -> std::process::ExitCode {
        match self.0 {
            explicit_error::Error::Domain(domain) => domain.output.exit_code,
            explicit_error::Error::Fault(_) => ExitCode::FAILURE,
        }
    }
}

impl std::fmt::Debug for MainError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Error> for MainError {
    fn from(value: Error) -> Self {
        Self(value)
    }
}

impl From<DomainError> for MainError {
    fn from(value: DomainError) -> Self {
        Self(value.into())
    }
}

impl From<ExitError> for MainError {
    fn from(value: ExitError) -> Self {
        Self(value.into())
    }
}

impl From<Fault> for MainError {
    fn from(value: Fault) -> Self {
        Self(value.into())
    }
}