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}