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