partial-result 0.1.0

A library for results that return success for non-critical errors.
Documentation
//! A crate that provides a type that represents a partial success, i.e. a result that can contain a failure.
//!
//! Use this crate if you need to return a result and a failure. The failure can be of any type and represents a
//! non-fatal error.
//!
//! # Examples
//!
//! ```
//! use partial_result::{
//!     PartialResult,
//!     PartialResultExt,
//!     PartialSuccess,
//! };
//!
//! #[derive(Debug)]
//! enum CriticalError {
//!     WeAreDoomed(String),
//!     EverythingIsLost(String),
//! }
//!
//! #[derive(Debug)]
//! enum NonCriticalError {
//!     SomethingWentWrong(String),
//!     SomethingElseWentWrong(String),
//! }
//!
//! fn do_something() -> PartialResult<u32, NonCriticalError, CriticalError> {
//!     let value = 42;
//!     let failure = NonCriticalError::SomethingWentWrong("Something went wrong".to_string());
//!
//!     PartialResult::partial_success(value, failure)
//! }
//!
//! fn main() -> Result<(), CriticalError> {
//!     let result = do_something()?;
//!     println!("Result: {}", result.value);
//!     result.failure.map(|e| println!("WARN: there was an issue during the computation: {:?}", e));
//!
//!     Ok(())
//! }

/// A type that represents a result, where successful results can contain a failure.
pub type PartialResult<T, F, E> = Result<PartialSuccess<T, F>, E>;

/// A trait that extends [`PartialResult<T, F, E>`] with additional methods.
pub trait PartialResultExt<T, F, E> {
    /// Creates a new [`PartialResult<T, F, E>`] with the given value and failure.
    fn partial_success(value: T, failure: F) -> Self;
}

impl<T, F, E> PartialResultExt<T, F, E> for PartialResult<T, F, E> {
    fn partial_success(value: T, failure: F) -> Self {
        Ok(PartialSuccess::partial(value, failure))
    }
}

/// A type that represents a partial success.
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub struct PartialSuccess<T, F> {
    /// The value of the partial success.
    pub value:   T,
    /// The failure of the partial success.
    pub failure: Option<F>,
}

impl<T, F> PartialSuccess<T, F> {
    /// Creates a new [`PartialSuccess<T, F>`] with the given value and failure.
    pub fn new(value: T, failure: Option<F>) -> Self {
        Self {
            value,
            failure,
        }
    }

    /// Creates a new [`PartialSuccess<T, F>`] with the given value.
    pub fn success(value: T) -> Self {
        Self::new(value, None)
    }

    /// Creates a new [`PartialSuccess<T, F>`] with the given value and failure.
    pub fn partial(value: T, failure: F) -> Self {
        Self::new(value, Some(failure))
    }

    /// Returns `true` if the partial success has a failure.
    pub fn has_failure(&self) -> bool {
        self.failure.is_some()
    }

    /// Converts the partial success into a [`Result<T, F>`].
    pub fn to_result(self) -> Result<T, F> {
        match self.failure {
            Some(f) => Err(f),
            None => Ok(self.value),
        }
    }

    /// Converts the partial success into a [`Result<T, E>`], discarding the failure if any.
    pub fn to_ok<E>(self) -> Result<T, E> {
        Ok(self.value)
    }
}

impl<T, F> From<PartialSuccess<T, F>> for Result<T, F> {
    fn from(partial: PartialSuccess<T, F>) -> Self {
        partial.to_result()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn partial_success_with_failure_has_failure() {
        let partial = PartialSuccess::partial(42, "Something went wrong");

        assert!(partial.has_failure());
    }

    #[test]
    fn partial_success_without_failure_has_no_failure() {
        let partial = PartialSuccess::<_, ()>::success(42);

        assert!(!partial.has_failure());
    }

    #[test]
    fn partial_success_with_failure_to_result_is_err() {
        let partial = PartialSuccess::partial(42, "Something went wrong");

        assert!(partial.to_result().is_err());
    }

    #[test]
    fn partial_success_without_failure_to_result_is_ok() {
        let partial = PartialSuccess::<_, ()>::success(42);

        assert!(partial.to_result().is_ok());
    }

    #[test]
    fn partial_success_with_failure_to_ok_is_ok() {
        let partial = PartialSuccess::partial(42, "Something went wrong");

        assert!(partial.to_ok::<&str>().is_ok());
    }

    #[test]
    fn partial_result_ext_partial_success_creates_partial_success_with_failure() {
        let partial = PartialResult::<_, _, ()>::partial_success(42, "Something went wrong");

        assert!(partial.is_ok());
        assert!(partial.unwrap().has_failure());
    }
}