expect-exit 0.5.2

Result.expected(): display an error message and exit without a panic.
Documentation
#![warn(missing_docs)]
//! Display an error message and exit without a panic.
//!
//! The `expect-exit` library defines the [`Expected`], [`ExpectedWithError`],
//! and [`ExpectedResult`] traits and implements them for the standard
//! [`Result`][`std::result::Result`], [`Option`][`std::option::Option`], and
//! [`bool`][`std::primitive::bool`] types as appropriate.
//! This allows a program to display an error message
//! and exit with a non-zero exit code without invoking a Rust panic, yet
//! optionally unwinding the stack so that various objects may perform some
//! clean-up actions.
//!
//! The methods with an `_e` suffix append an appropriate error message to
//! the supplied one. The methods with a `_` suffix allow the caller to
//! specify an already-constructed message instead of a function that
//! returns it.
//!
//! ```rust
//! use std::collections::HashMap;
//! use std::env;
//!
//! use expect_exit::{Expected, ExpectedWithError};
//!
//! let mut vars: HashMap<String, String> = ["HOME", "PATH", "LOGNAME"]
//!     .iter()
//!     .map(|name| {
//!         (
//!             name.to_string(),
//!             env::var(name).or_exit_e(|| format!("No '{} in the environment", name)),
//!         )
//!     })
//!     .collect();
//! vars.insert(
//!     "PWD".to_string(),
//!     env::current_dir()
//!         .or_exit_e_("Could not determine the current directory")
//!         .to_str()
//!         .or_exit_("Could not represent the path to the current directory")
//!         .to_string(),
//! );
//! println!("{:?}", vars);
//! if !vars["PWD"].starts_with("/") {
//!     expect_exit::exit("Expected an absolute path to the current directory");
//! }
//! ```
//!
//! The traits are currently implemented for the standard
//! [`Result`][`std::result::Result`],
//! [`Option`][`std::option::Option`], and
//! [`bool`][`std::primitive::bool`] types as appropriate.
//!
//! For the crate's change history, see
//! the [NEWS.md](https://gitlab.com/ppentchev/expect-exit/-/blob/master/NEWS.md)
//! file in the source distribution.

#![doc(html_root_url = "https://docs.rs/expect-exit/0.5.2")]
/*
 * Copyright (c) 2020 - 2022  Peter Pentchev <roam@ringlet.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

use std::error::Error;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::panic;
use std::process;

/// Something to enqueue a [`Drop::drop()`] call to ensure that we unwind
/// the stack before exiting the process.
struct ExitHelper {
    /// The code to exit the program with after the stack has been unwound.
    code: i32,
}

impl Drop for ExitHelper {
    fn drop(&mut self) {
        process::exit(self.code);
    }
}

/// Unwind the stack and end the process with the specified exit code.
#[inline]
pub fn exit_unwind(code: i32) -> ! {
    panic::resume_unwind(Box::new(ExitHelper { code }));
}

/// Output a message, then invoke the specified exit routine.
#[allow(clippy::print_stderr)]
fn _die(msg: &str, exit: fn(i32) -> !) -> ! {
    eprintln!("{}", msg);
    exit(1);
}

/// Output a message containing an error description, then invoke the specified exit routine.
#[allow(clippy::print_stderr)]
fn _die_perror(msg: &str, err: impl Display, exit: fn(i32) -> !) -> ! {
    eprintln!("{}: {}", msg, err);
    exit(1);
}

/// Display the specified message, then unwind the stack and exit.
#[inline]
pub fn exit(msg: &str) -> ! {
    _die(msg, exit_unwind);
}

/// Display the specified message and append an appropriate
/// description of the error, then unwind the stack and exit.
#[inline]
pub fn exit_perror(msg: &str, err: impl Display) -> ! {
    _die_perror(msg, err, exit_unwind);
}

/// Display the specified message, then exit without unwinding
/// the stack.
#[inline]
pub fn die(msg: &str) -> ! {
    _die(msg, process::exit);
}

/// Display the specified message and append an appropriate
/// description of the error, then exit without unwinding
/// the stack.
#[inline]
pub fn die_perror(msg: &str, err: impl Display) -> ! {
    _die_perror(msg, err, process::exit);
}

/// The error object returned by the [`ExpectedResult`] methods.
#[derive(Debug)]
pub struct ExpectationFailed {
    /// The error message describing the failure that occurred.
    message: String,
}

impl Display for ExpectationFailed {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        write!(f, "{}", self.message)
    }
}

impl Error for ExpectationFailed {}

/// Unwrap or exit with the specified message.
pub trait Expected<T>
where
    Self: Sized,
{
    /// Test the value. On success, return the unwrapped value.
    /// On error, unwind the stack, display the specified message,
    /// and exit.
    #[inline]
    fn or_exit_(self, msg: &str) -> T {
        self.or_exit(|| msg.to_owned())
    }

    /// Test the value. On success, return the unwrapped value.
    /// On error, unwind the stack, call the supplied function to
    /// obtain an error message, display it,
    /// and exit.
    fn or_exit<F>(self, f: F) -> T
    where
        F: FnOnce() -> String;

    /// Test the value. On success, return the unwrapped value.
    /// On error, do not unwind the stack, display the specified message,
    /// and exit.
    #[inline]
    fn or_die_(self, msg: &str) -> T {
        self.or_die(|| msg.to_owned())
    }

    /// Test the value. On success, return the unwrapped value.
    /// On error, do not unwind the stack, call the supplied function to
    /// obtain an error message, display it,
    /// and exit.
    fn or_die<F>(self, f: F) -> T
    where
        F: FnOnce() -> String;

    /// Alias for [`Expected::or_exit_`].
    #[inline]
    fn expect_or_exit_(self, msg: &str) -> T {
        self.or_exit_(msg)
    }

    /// Alias for [`Expected::or_exit`].
    #[inline]
    fn expect_or_exit<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        self.or_exit(f)
    }

    /// Alias for [`Expected::or_die_`].
    #[inline]
    fn expect_or_die_(self, msg: &str) -> T {
        self.or_die_(msg)
    }

    /// Alias for [`Expected::or_die`].
    #[inline]
    fn expect_or_die<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        self.or_die(f)
    }
}

/// Unwrap or exit with an appropriate error message.
pub trait ExpectedWithError<T>: Expected<T> {
    /// Test the value. On success, return the unwrapped value.
    /// On error, unwind the stack, display the specified message,
    /// append an appropriate description of the error, and exit.
    #[inline]
    fn or_exit_e_(self, msg: &str) -> T {
        self.or_exit_e(|| msg.to_owned())
    }

    /// Test the value. On success, return the unwrapped value.
    /// On error, unwind the stack, call the supplied function to
    /// obtain an error message, display it,
    /// append an appropriate description of the error, and exit.
    fn or_exit_e<F>(self, f: F) -> T
    where
        F: FnOnce() -> String;

    /// Test the value. On success, return the unwrapped value.
    /// On error, do not unwind the stack, display the specified message,
    /// append an appropriate description of the error, and exit.
    #[inline]
    fn or_die_e_(self, msg: &str) -> T {
        self.or_die_e(|| msg.to_owned())
    }

    /// Test the value. On success, return the unwrapped value.
    /// On error, do not unwind the stack, call the supplied function to
    /// obtain an error message, display it,
    /// append an appropriate description of the error, and exit.
    fn or_die_e<F>(self, f: F) -> T
    where
        F: FnOnce() -> String;

    /// Alias for [`ExpectedWithError::or_exit_e_`].
    #[inline]
    fn expect_or_exit_perror_(self, msg: &str) -> T {
        self.or_exit_e_(msg)
    }

    /// Alias for [`ExpectedWithError::or_exit_e`].
    #[inline]
    fn expect_or_exit_perror<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        self.or_exit_e(f)
    }

    /// Alias for [`ExpectedWithError::or_die_e_`].
    #[inline]
    fn expect_or_die_perror_(self, msg: &str) -> T {
        self.or_die_e_(msg)
    }

    /// Alias for [`ExpectedWithError::or_die_e`].
    #[inline]
    fn expect_or_die_perror<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        self.or_die_e(f)
    }
}

/// Test the value and return a result object containing either
/// the inner value or an error object that, when displayed, will provide
/// the specified error message.
pub trait ExpectedResult<T>
where
    Self: Sized,
{
    /// Return a result object with a non-boxed `Error` object.
    ///
    /// # Errors
    /// [`ExpectationFailed`] on error.
    #[inline]
    fn expect_result_nb_(self, msg: &str) -> Result<T, ExpectationFailed> {
        self.expect_result_nb(|| msg.to_owned())
    }

    /// Return a result object that may be tested using the `?` operator.
    ///
    /// # Errors
    /// [`ExpectationFailed`] on error.
    #[inline]
    fn expect_result_(self, msg: &str) -> Result<T, Box<dyn Error>> {
        self.expect_result(|| msg.to_owned())
    }

    /// Return a result object with a non-boxed `Error` object.
    /// Invoke the specified function to obtain the error message.
    ///
    /// # Errors
    /// [`ExpectationFailed`] on error.
    fn expect_result_nb<F>(self, f: F) -> Result<T, ExpectationFailed>
    where
        F: FnOnce() -> String;

    /// Return a result object that may be tested using the `?` operator.
    /// Invoke the specified function to obtain the error message.
    ///
    /// # Errors
    /// [`ExpectationFailed`] on error.
    #[inline]
    fn expect_result<F>(self, f: F) -> Result<T, Box<dyn Error>>
    where
        F: FnOnce() -> String,
    {
        self.expect_result_nb(f)
            .map_err(|err| -> Box<dyn Error> { Box::new(err) })
    }
}

impl<T> Expected<T> for Option<T> {
    #[inline]
    fn or_exit<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Some(value) => value,
            None => exit(&f()),
        }
    }

    #[inline]
    fn or_die<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Some(value) => value,
            None => die(&f()),
        }
    }
}

impl<T, E> Expected<T> for Result<T, E>
where
    E: Display,
{
    #[inline]
    fn or_exit<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Ok(value) => value,
            Err(_) => exit(&f()),
        }
    }

    #[inline]
    fn or_die<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Ok(value) => value,
            Err(_) => die(&f()),
        }
    }
}

impl<T, E> ExpectedWithError<T> for Result<T, E>
where
    E: Display,
{
    #[inline]
    fn or_exit_e<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Err(err) => exit_perror(&f(), err),
            Ok(value) => value,
        }
    }

    #[inline]
    fn or_die_e<F>(self, f: F) -> T
    where
        F: FnOnce() -> String,
    {
        match self {
            Err(err) => die_perror(&f(), err),
            Ok(value) => value,
        }
    }
}

impl<T> ExpectedResult<T> for Option<T> {
    #[inline]
    fn expect_result_nb<F>(self, f: F) -> Result<T, ExpectationFailed>
    where
        F: FnOnce() -> String,
    {
        self.ok_or_else(|| ExpectationFailed { message: f() })
    }
}

impl<T, E> ExpectedResult<T> for Result<T, E>
where
    E: Display,
{
    #[inline]
    fn expect_result_nb<F>(self, f: F) -> Result<T, ExpectationFailed>
    where
        F: FnOnce() -> String,
    {
        self.map_err(|err| ExpectationFailed {
            message: format!("{}: {}", f(), err),
        })
    }
}

impl Expected<Self> for bool {
    #[inline]
    fn or_exit<F>(self, f: F) -> Self
    where
        F: FnOnce() -> String,
    {
        if !self {
            exit(&f());
        }
        true
    }

    #[inline]
    fn or_die<F>(self, f: F) -> Self
    where
        F: FnOnce() -> String,
    {
        if !self {
            die(&f());
        }
        true
    }
}

impl ExpectedResult<()> for bool {
    #[inline]
    fn expect_result_nb<F>(self, f: F) -> Result<(), ExpectationFailed>
    where
        F: FnOnce() -> String,
    {
        if self {
            Ok(())
        } else {
            Err(ExpectationFailed { message: f() })
        }
    }
}