fused_error/
lib.rs

1//! A simple library for working with composable errors.
2//!
3//! # Examples
4//!
5//! Basic usage:
6//!
7//! ```
8//! use std::{
9//!     num::{IntErrorKind, ParseIntError},
10//!     iter::Sum,
11//!     str::FromStr
12//! };
13//!
14//! use fused_error::{Accumulator, FusedResult, IteratorExt};
15//!
16//! /// Take an iterator of textual data, adding up all of the parsed numbers.
17//! ///
18//! /// Unlike the standard way of returning a `Result<N, N::Err>`, this doesn't
19//! /// short-circuit, it keeps track of the current sum, and reports any
20//! /// further diagnostics past the first failure.
21//! fn calculate_sum<N, E, I>(iter: I) -> FusedResult<N, N::Err>
22//! where
23//!     N: FromStr + Sum,
24//!     E: AsRef<str>,
25//!     I: IntoIterator<Item = E>,
26//! {
27//!     // Error accumulators collect errors to defer handling them, providing
28//!     // more holistic diagnostics:
29//!     let mut acc = Accumulator::new();
30//!     let sum = iter
31//!         .into_iter()
32//!         .map(|item| item.as_ref().parse::<N>())
33//!         // `fused_error` adds certain methods to iterators; no more
34//!         // disrupting iterator chains and `collect` hells for results!
35//!         .accumulate(&mut acc)
36//!         .sum();
37//!     // fused results let you easily pass around error accumulators and
38//!     // are perfect for cases where a yielded "ok" value and an error case
39//!     // aren't mutually exclusive.
40//!     FusedResult::new(sum, acc)
41//! }
42//!
43//! let result: FusedResult<i32, _> = calculate_sum(["1", "2", "3", "4"]);
44//! assert_eq!(result.value(), &10);
45//! assert_eq!(result.errors(), []);
46//!
47//! let result: FusedResult<i8, _> = calculate_sum(["", "-129", "foo", "128"]);
48//! assert_eq!(result.value(), &0);
49//! assert_eq!(
50//!     result
51//!         .errors()
52//!         .into_iter()
53//!         .map(|err| err.kind().clone())
54//!         .collect::<Vec<_>>(),
55//!     [
56//!         IntErrorKind::Empty,
57//!         IntErrorKind::NegOverflow,
58//!         IntErrorKind::InvalidDigit,
59//!         IntErrorKind::PosOverflow,
60//!     ],
61//! );
62//!
63//! let result: FusedResult<u8, _> = calculate_sum(["-1", "", "0", "1"]);
64//! assert_eq!(result.value(), &1);
65//! assert_eq!(
66//!     result
67//!         .errors()
68//!         .into_iter()
69//!         .map(|err| err.kind().clone())
70//!         .collect::<Vec<_>>(),
71//!     [IntErrorKind::InvalidDigit, IntErrorKind::Empty],
72//! );
73//! ```
74//!
75//! # Features
76//!
77//! So far, there is only one opt-in feature: `syn`. Enabling this feature
78//! implements [`FusedError`] on [`syn::Error`], as that was one of the main
79//! motivations for creating this library.
80//!
81//! # Motivation
82//!
83//! [`syn`] already has great composable errors that you combine with
84//! [`syn::Error::combine`]. Also, [`darling`] has a great system that was the
85//! primary inspiration for error accumulators and their drop mechanic. However,
86//! of course, [`darling`]'s accumulators are only to be used with [`darling`]
87//! errors and their accumulator API is far more limited to reflect this.
88//!
89//! The original use case for this crate, deferring and collecting multiple
90//! errors, is primarily helpful in parsing: the more diagnostics you can
91//! provide in one pass limits the need of frequently changing something,
92//! building, fixing the one error, and trying again.
93//!
94//! [`darling`]: https://docs.rs/darling/latest/darling/
95
96// fused_error types in rustdoc of other crates get linked to here
97#![doc(html_root_url = "https://docs.rs/fused_error/0.1.2")]
98#![cfg_attr(doc_cfg, feature(doc_cfg))]
99#![warn(missing_docs)]
100#![warn(clippy::pedantic)]
101#![allow(
102    clippy::mismatching_type_param_order,
103    clippy::missing_errors_doc,
104    clippy::module_name_repetitions
105)]
106
107pub mod accumulated;
108pub mod accumulator;
109pub mod iter;
110pub mod result;
111
112#[doc(inline)]
113pub use accumulated::Accumulated;
114#[doc(inline)]
115pub use accumulator::Accumulator;
116#[doc(inline)]
117pub use iter::IteratorExt;
118#[doc(inline)]
119pub use result::{FusedResult, PackedResult};
120
121/// Interface for error types that can store multiple error messages within one
122/// instance.
123///
124/// Instead of making your own newtype to implement `FusedError` on a remote
125/// error type, consider using [`Accumulated`] instead.
126///
127/// # Examples
128///
129/// ```
130/// use fused_error::{Accumulated, FusedError};
131///
132/// struct Error<'a> {
133///     messages: Vec<&'a str>,
134/// }
135///
136/// impl FusedError for Error<'_> {
137///     fn combine(&mut self, mut other: Self) {
138///         self.messages.append(&mut other.messages);
139///     }
140/// }
141///
142/// let mut err1 = Error { messages: vec!["foo"] };
143/// let err2 = Error { messages: vec!["bar", "baz"] };
144///
145/// err1.combine(err2);
146/// assert_eq!(err1.messages, ["foo", "bar", "baz"]);
147/// ```
148pub trait FusedError: Sized {
149    /// Drains `other`'s error messages into `self`'s error messages.
150    ///
151    /// If you find yourself frequently calling `err.combine(other)` only to
152    /// return `err`, consider using [`merge`] instead.
153    ///
154    /// [`merge`]: FusedError::merge
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use fused_error::{Accumulated, FusedError};
160    ///
161    /// let mut err1 = Accumulated::from("foo");
162    /// let err2 = Accumulated::from(vec!["bar", "baz"]);
163    /// let err3 = Accumulated::from("qux");
164    ///
165    /// err1.combine(err2);
166    /// err1.combine(err3);
167    ///
168    /// assert_eq!(err1, ["foo", "bar", "baz", "qux"]);
169    /// ```
170    fn combine(&mut self, other: Self);
171
172    /// Calls [`combine`] and returns `self` as a convenience for closures that
173    /// need to return `Self` like [`Iterator::fold`] or [`Iterator::reduce`].
174    ///
175    /// [`combine`]: FusedError::combine
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use fused_error::{Accumulated, FusedError};
181    ///
182    /// let errors = [
183    ///     Accumulated::from("foo"),
184    ///     Accumulated::from(vec!["bar", "baz"]),
185    ///     Accumulated::from("qux"),
186    /// ];
187    ///
188    /// let error = errors.into_iter().reduce(|a, b| a.merge(b)).unwrap();
189    /// assert_eq!(error, ["foo", "bar", "baz", "qux"]);
190    /// ```
191    #[must_use]
192    #[inline]
193    fn merge(mut self, other: Self) -> Self {
194        self.combine(other);
195        self
196    }
197}
198
199#[cfg(feature = "syn")]
200#[cfg_attr(doc_cfg, doc(cfg(feature = "syn")))]
201impl FusedError for syn::Error {
202    #[inline]
203    fn combine(&mut self, other: Self) {
204        self.combine(other);
205    }
206}
207
208/// A trait for splitting a result value into its "ok" and "error" parts.
209///
210/// # Safety
211///
212/// When implementing [`into_result_parts`], it is guaranteed that it **can
213/// not** return [`None`] for both the "ok" and "error" parts. In other words,
214/// it's only valid to return one of the following combinations:
215///
216/// * `(Some(T), Some(E))`
217/// * `(Some(T), None)`
218/// * `(None, Some(E))`
219///
220/// [`into_result_parts`]: IntoResultParts::into_result_parts
221pub unsafe trait IntoResultParts {
222    /// The "ok" component of the result value.
223    type Ok;
224
225    /// The "error" component of the result value.
226    type Err;
227
228    /// Splits a result-like value into its "ok" and "error" parts.
229    ///
230    /// It is **guaranteed** that `into_result_parts` **never** returns
231    /// `(None, None)`. However, "ok" and "error" parts **are not** mutually
232    /// exclusive. It's entirely possible `into_result_parts` returns
233    /// `(Some(T), Some(E))`.
234    ///
235    /// # Examples
236    ///
237    /// [`Result<T, E>`](Result) implements `IntoResultParts<T, E>`:
238    ///
239    /// ```
240    /// use fused_error::IntoResultParts;
241    ///
242    /// let result = "1".parse::<i32>();
243    /// let (ok, error) = result.into_result_parts();
244    /// assert_eq!(ok, Some(1));
245    /// assert!(error.is_none());
246    /// ```
247    ///
248    /// [`FusedResult<T, E>`](FusedResult) also implements
249    /// `IntoResultParts<T, E>` when `E` implements [`FusedError`].n
250    ///
251    /// It is also **guaranteed** that the "ok" part for a fused result is
252    /// **always** [`Some(T)`](Some).
253    ///
254    /// ```
255    /// use fused_error::{Accumulated, Accumulator, FusedResult, IntoResultParts};
256    ///
257    /// let mut acc = Accumulator::<Accumulated<&str>>::new();
258    /// acc.push("foo");
259    /// acc.push("bar");
260    ///
261    /// let result = FusedResult::new(1i32, acc);
262    /// let (ok, error) = result.into_result_parts();
263    /// assert_eq!(ok.unwrap(), 1);
264    /// assert_eq!(error.unwrap(), ["foo", "bar"]);
265    /// ```
266    #[must_use]
267    fn into_result_parts(self) -> (Option<Self::Ok>, Option<Self::Err>);
268}
269
270unsafe impl<T, E> IntoResultParts for Result<T, E> {
271    type Ok = T;
272    type Err = E;
273
274    #[inline]
275    fn into_result_parts(self) -> (Option<T>, Option<E>) {
276        match self {
277            Ok(ok) => (Some(ok), None),
278            Err(err) => (None, Some(err)),
279        }
280    }
281}
282
283/// A prelude to import the main items exported by this library:
284///
285/// * [`Accumulator<E>`]
286/// * [`FusedError`]
287/// * [`FusedResult<T, E>`](FusedResult)
288/// * [`IteratorExt`]
289/// * [`PackedResult<T, E>`](PackedResult)
290///
291/// *Note:* [`Accumulated<E>`] isn't in the prelude, as its use isn't advised
292/// unless necessary. It's expected a minority of projects will actually use
293/// [`Accumulated<E>`].
294pub mod prelude {
295    #[doc(inline)]
296    pub use crate::{
297        accumulator::Accumulator,
298        iter::IteratorExt,
299        result::{FusedResult, PackedResult},
300        FusedError,
301    };
302}