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