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