partial_result/
lib.rs

1//! A crate that provides a type that represents a partial success, i.e. a result that can contain a failure.
2//!
3//! Use this crate if you need to return a result and a failure. The failure can be of any type and represents a
4//! non-fatal error.
5//!
6//! # Examples
7//!
8//! ```
9//! use partial_result::{
10//!     PartialResult,
11//!     PartialResultExt,
12//!     PartialSuccess,
13//! };
14//!
15//! #[derive(Debug)]
16//! enum CriticalError {
17//!     WeAreDoomed(String),
18//!     EverythingIsLost(String),
19//! }
20//!
21//! #[derive(Debug)]
22//! enum NonCriticalError {
23//!     SomethingWentWrong(String),
24//!     SomethingElseWentWrong(String),
25//! }
26//!
27//! fn do_something() -> PartialResult<u32, NonCriticalError, CriticalError> {
28//!     let value = 42;
29//!     let failure = NonCriticalError::SomethingWentWrong("Something went wrong".to_string());
30//!
31//!     PartialResult::partial_success(value, failure)
32//! }
33//!
34//! fn main() -> Result<(), CriticalError> {
35//!     let result = do_something()?;
36//!     println!("Result: {}", result.value);
37//!     result.failure.map(|e| println!("WARN: there was an issue during the computation: {:?}", e));
38//!
39//!     Ok(())
40//! }
41
42/// A type that represents a result, where successful results can contain a failure.
43pub type PartialResult<T, F, E> = Result<PartialSuccess<T, F>, E>;
44
45/// A trait that extends [`PartialResult<T, F, E>`] with additional methods.
46pub trait PartialResultExt<T, F, E> {
47    /// Creates a new [`PartialResult<T, F, E>`] with the given value and failure.
48    fn partial_success(value: T, failure: F) -> Self;
49}
50
51impl<T, F, E> PartialResultExt<T, F, E> for PartialResult<T, F, E> {
52    fn partial_success(value: T, failure: F) -> Self {
53        Ok(PartialSuccess::partial(value, failure))
54    }
55}
56
57/// A type that represents a partial success.
58#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
59pub struct PartialSuccess<T, F> {
60    /// The value of the partial success.
61    pub value:   T,
62    /// The failure of the partial success.
63    pub failure: Option<F>,
64}
65
66impl<T, F> PartialSuccess<T, F> {
67    /// Creates a new [`PartialSuccess<T, F>`] with the given value and failure.
68    pub fn new(value: T, failure: Option<F>) -> Self {
69        Self {
70            value,
71            failure,
72        }
73    }
74
75    /// Creates a new [`PartialSuccess<T, F>`] with the given value.
76    pub fn success(value: T) -> Self {
77        Self::new(value, None)
78    }
79
80    /// Creates a new [`PartialSuccess<T, F>`] with the given value and failure.
81    pub fn partial(value: T, failure: F) -> Self {
82        Self::new(value, Some(failure))
83    }
84
85    /// Returns `true` if the partial success has a failure.
86    pub fn has_failure(&self) -> bool {
87        self.failure.is_some()
88    }
89
90    /// Converts the partial success into a [`Result<T, F>`].
91    pub fn to_result(self) -> Result<T, F> {
92        match self.failure {
93            Some(f) => Err(f),
94            None => Ok(self.value),
95        }
96    }
97
98    /// Converts the partial success into a [`Result<T, E>`], discarding the failure if any.
99    pub fn to_ok<E>(self) -> Result<T, E> {
100        Ok(self.value)
101    }
102}
103
104impl<T, F> From<PartialSuccess<T, F>> for Result<T, F> {
105    fn from(partial: PartialSuccess<T, F>) -> Self {
106        partial.to_result()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn partial_success_with_failure_has_failure() {
116        let partial = PartialSuccess::partial(42, "Something went wrong");
117
118        assert!(partial.has_failure());
119    }
120
121    #[test]
122    fn partial_success_without_failure_has_no_failure() {
123        let partial = PartialSuccess::<_, ()>::success(42);
124
125        assert!(!partial.has_failure());
126    }
127
128    #[test]
129    fn partial_success_with_failure_to_result_is_err() {
130        let partial = PartialSuccess::partial(42, "Something went wrong");
131
132        assert!(partial.to_result().is_err());
133    }
134
135    #[test]
136    fn partial_success_without_failure_to_result_is_ok() {
137        let partial = PartialSuccess::<_, ()>::success(42);
138
139        assert!(partial.to_result().is_ok());
140    }
141
142    #[test]
143    fn partial_success_with_failure_to_ok_is_ok() {
144        let partial = PartialSuccess::partial(42, "Something went wrong");
145
146        assert!(partial.to_ok::<&str>().is_ok());
147    }
148
149    #[test]
150    fn partial_result_ext_partial_success_creates_partial_success_with_failure() {
151        let partial = PartialResult::<_, _, ()>::partial_success(42, "Something went wrong");
152
153        assert!(partial.is_ok());
154        assert!(partial.unwrap().has_failure());
155    }
156}