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