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