error_rail/convert/
mod.rs

1//! Conversion helpers between [`Result`], [`Validation`], and [`ComposableError`].
2//!
3//! These adapters make it straightforward to incrementally adopt `error-rail`
4//! by wrapping legacy results or by flattening composable errors back into core
5//! types when interacting with external APIs.
6//!
7//! # Examples
8//!
9//! ```
10//! use error_rail::convert::*;
11//! use error_rail::validation::Validation;
12//!
13//! // Convert between Result and Validation
14//! let result: Result<i32, &str> = Ok(42);
15//! let validation = result_to_validation(result);
16//! assert!(validation.is_valid());
17//!
18//! // Wrap errors in ComposableError
19//! let result: Result<i32, &str> = Err("failed");
20//! let composable = wrap_in_composable_result(result);
21//! assert!(composable.is_err());
22//! ```
23
24use crate::types::alloc_type::Box;
25use crate::types::BoxedComposableResult;
26use crate::validation::core::Validation;
27use crate::{types::composable_error::ComposableError, ErrorVec};
28use core::iter::FusedIterator;
29
30/// Converts a `Validation` to a `Result`, taking the first error if invalid.
31///
32/// # Arguments
33///
34/// * `validation` - The validation to convert
35///
36/// # Returns
37///
38/// * `Ok(value)` if validation is valid
39/// * `Err(first_error)` if validation is invalid
40///
41/// # Panics
42///
43/// Panics if the `Validation::Invalid` variant contains no errors (should never happen).
44///
45/// # Examples
46///
47/// ```
48/// use error_rail::convert::validation_to_result;
49/// use error_rail::validation::Validation;
50///
51/// let valid = Validation::<&str, i32>::Valid(42);
52/// assert_eq!(validation_to_result(valid), Ok(42));
53///
54/// let invalid = Validation::<&str, i32>::invalid("error");
55/// assert_eq!(validation_to_result(invalid), Err("error"));
56/// ```
57#[inline]
58pub fn validation_to_result<T, E>(validation: Validation<E, T>) -> Result<T, E> {
59    match validation {
60        Validation::Valid(value) => Ok(value),
61        Validation::Invalid(errors) => {
62            let error = errors
63                .into_iter()
64                .next()
65                .expect("Validation::Invalid must contain at least one error");
66            Err(error)
67        },
68    }
69}
70
71/// Converts a `Result` to a `Validation`.
72///
73/// # Arguments
74///
75/// * `result` - The result to convert
76///
77/// # Returns
78///
79/// * `Validation::Valid(value)` if result is `Ok`
80/// * `Validation::Invalid([error])` if result is `Err`
81///
82/// # Examples
83///
84/// ```
85/// use error_rail::convert::result_to_validation;
86/// use error_rail::validation::Validation;
87///
88/// let ok_result: Result<i32, &str> = Ok(42);
89/// let validation = result_to_validation(ok_result);
90/// assert!(validation.is_valid());
91///
92/// let err_result: Result<i32, &str> = Err("failed");
93/// let validation = result_to_validation(err_result);
94/// assert!(validation.is_invalid());
95/// ```
96#[inline]
97pub fn result_to_validation<T, E>(result: Result<T, E>) -> Validation<E, T> {
98    match result {
99        Ok(value) => Validation::Valid(value),
100        Err(error) => Validation::invalid(error),
101    }
102}
103
104/// Extracts the core error from a `ComposableError`, discarding all context.
105///
106/// # Arguments
107///
108/// * `composable` - The composable error to unwrap
109///
110/// # Returns
111///
112/// The underlying core error value
113///
114/// # Examples
115///
116/// ```
117/// use error_rail::{ComposableError, convert::composable_to_core};
118///
119/// let composable = ComposableError::<&str>::new("error")
120///     .with_context("additional context");
121/// let core = composable_to_core(composable);
122/// assert_eq!(core, "error");
123/// ```
124#[inline]
125pub fn composable_to_core<E>(composable: ComposableError<E>) -> E {
126    composable.into_core()
127}
128
129/// Wraps a core error in a `ComposableError` with no context.
130///
131/// # Arguments
132///
133/// * `error` - The core error to wrap
134///
135/// # Returns
136///
137/// A new `ComposableError` containing the error
138///
139/// # Examples
140///
141/// ```
142/// use error_rail::{ComposableError, convert::core_to_composable};
143///
144/// let core_error = "something failed";
145/// let composable = core_to_composable(core_error);
146/// assert_eq!(composable.core_error(), &"something failed");
147/// ```
148#[inline]
149pub fn core_to_composable<E>(error: E) -> ComposableError<E> {
150    error.into()
151}
152
153/// Flattens a `Result<T, ComposableError<E>>` into `Result<T, E>`.
154///
155/// Strips all context and error codes, returning only the core error.
156///
157/// # Arguments
158///
159/// * `result` - The result with composable error to flatten
160///
161/// # Returns
162///
163/// A result containing only the core error type
164///
165/// # Examples
166///
167/// ```
168/// use error_rail::{ComposableError, convert::flatten_composable_result};
169///
170/// let composable_result: Result<i32, ComposableError<&str>> =
171///     Err(ComposableError::new("error").with_context("context"));
172/// let flattened = flatten_composable_result(composable_result);
173/// assert_eq!(flattened, Err("error"));
174/// ```
175#[inline]
176pub fn flatten_composable_result<T, E>(result: Result<T, ComposableError<E>>) -> Result<T, E> {
177    result.map_err(composable_to_core)
178}
179
180/// Wraps a plain `Result<T, E>` into `Result<T, ComposableError<E>>`.
181///
182/// Converts the error variant into a `ComposableError` with no context.
183///
184/// # Arguments
185///
186/// * `result` - The plain result to wrap
187///
188/// # Returns
189///
190/// A result with composable error type
191///
192/// # Examples
193///
194/// ```
195/// use error_rail::convert::wrap_in_composable_result;
196///
197/// let plain_result: Result<i32, &str> = Err("error");
198/// let wrapped = wrap_in_composable_result(plain_result);
199/// assert!(wrapped.is_err());
200/// ```
201#[inline]
202#[allow(clippy::result_large_err)]
203pub fn wrap_in_composable_result<T, E>(result: Result<T, E>) -> Result<T, ComposableError<E>> {
204    result.map_err(core_to_composable)
205}
206
207/// Wraps a plain `Result<T, E>` into a boxed `ComposableError`.
208///
209/// Similar to [`wrap_in_composable_result`] but boxes the error to reduce stack size.
210///
211/// # Arguments
212///
213/// * `result` - The plain result to wrap
214///
215/// # Returns
216///
217/// A result with boxed composable error
218///
219/// # Examples
220///
221/// ```
222/// use error_rail::convert::wrap_in_composable_result_boxed;
223///
224/// let plain_result: Result<i32, &str> = Err("error");
225/// let boxed = wrap_in_composable_result_boxed(plain_result);
226/// assert!(boxed.is_err());
227/// ```
228#[inline]
229pub fn wrap_in_composable_result_boxed<T, E>(result: Result<T, E>) -> BoxedComposableResult<T, E> {
230    result.map_err(|e| Box::new(core_to_composable(e)))
231}
232
233/// Collects multiple errors into a single `Validation`.
234///
235/// # Arguments
236///
237/// * `errors` - An iterator of errors to collect
238///
239/// # Returns
240///
241/// * `Validation::Valid(())` if no errors
242/// * `Validation::Invalid(errors)` if any errors present
243///
244/// # Examples
245///
246/// ```
247/// use error_rail::convert::collect_errors;
248/// use error_rail::validation::Validation;
249///
250/// let errors = vec!["error1", "error2"];
251/// let validation = collect_errors(errors);
252/// assert!(validation.is_invalid());
253///
254/// let no_errors: Vec<&str> = vec![];
255/// let validation = collect_errors(no_errors);
256/// assert!(validation.is_valid());
257/// ```
258#[inline]
259pub fn collect_errors<E, I>(errors: I) -> Validation<E, ()>
260where
261    I: IntoIterator<Item = E>,
262{
263    let error_vec: ErrorVec<E> = errors.into_iter().collect();
264    if error_vec.is_empty() {
265        Validation::Valid(())
266    } else {
267        Validation::invalid_many(error_vec)
268    }
269}
270
271/// Iterator returned by [`split_validation_errors`].
272pub enum SplitValidationIter<T, E> {
273    Valid(Option<T>),
274    Invalid(<ErrorVec<E> as IntoIterator>::IntoIter),
275}
276
277impl<T, E> Iterator for SplitValidationIter<T, E> {
278    type Item = Result<T, E>;
279
280    fn next(&mut self) -> Option<Self::Item> {
281        match self {
282            Self::Valid(opt) => opt.take().map(Ok),
283            Self::Invalid(iter) => iter.next().map(Err),
284        }
285    }
286
287    fn size_hint(&self) -> (usize, Option<usize>) {
288        match self {
289            Self::Valid(opt) => {
290                let len = if opt.is_some() { 1 } else { 0 };
291                (len, Some(len))
292            },
293            Self::Invalid(iter) => iter.size_hint(),
294        }
295    }
296}
297
298impl<T, E> ExactSizeIterator for SplitValidationIter<T, E> {}
299impl<T, E> FusedIterator for SplitValidationIter<T, E> {}
300
301/// Splits a `Validation` into individual `Result` values.
302///
303/// # Arguments
304///
305/// * `validation` - The validation to split
306///
307/// # Returns
308///
309/// An iterator that yields:
310/// * `Ok(value)` if validation is valid
311/// * `Err(e)` for each error if validation is invalid
312///
313/// # Examples
314///
315/// ```
316/// use error_rail::convert::split_validation_errors;
317/// use error_rail::validation::Validation;
318///
319/// let valid = Validation::<&str, i32>::Valid(42);
320/// let results: Vec<_> = split_validation_errors(valid).collect();
321/// assert_eq!(results, vec![Ok(42)]);
322///
323/// let invalid = Validation::<&str, i32>::invalid_many(vec!["err1", "err2"]);
324/// let results: Vec<_> = split_validation_errors(invalid).collect();
325/// assert_eq!(results, vec![Err("err1"), Err("err2")]);
326/// ```
327pub fn split_validation_errors<T, E>(validation: Validation<E, T>) -> SplitValidationIter<T, E> {
328    match validation {
329        Validation::Valid(value) => SplitValidationIter::Valid(Some(value)),
330        Validation::Invalid(errors) => SplitValidationIter::Invalid(errors.into_iter()),
331    }
332}