error_accumulator/builder/
array.rs

1use std::{error::Error, marker::PhantomData};
2
3use crate::{
4    builder::{ErrorBuilderParent, StructBuilder},
5    cons::Nil,
6    error::AccumulatedError,
7    path::{FieldName, PathSegment, SourcePath},
8};
9
10/// A builder to record the parsing results of elements of an array in the
11/// input.
12///
13/// Arrays can be composed of single values or nested structs.
14#[derive(Debug)]
15pub struct ArrayBuilder<Parent, Value> {
16    parent: Parent,
17    base: SourcePath,
18    errors: AccumulatedError,
19    array_name: FieldName,
20    values: Vec<Value>,
21    _marker: PhantomData<Value>,
22}
23
24impl<Parent, Value> ArrayBuilder<Parent, Value>
25where
26    Parent: ErrorBuilderParent<Vec<Value>>,
27{
28    pub(crate) fn new(parent: Parent, base: SourcePath, field: FieldName) -> Self {
29        Self {
30            base,
31            parent,
32            errors: Default::default(),
33            array_name: field,
34            values: Default::default(),
35            _marker: PhantomData,
36        }
37    }
38
39    /// Record an [`Iterator`] of parsing results for single values.
40    pub fn of_values<E>(self, values: impl IntoIterator<Item = Result<Value, E>>) -> Self
41    where
42        E: Error + Send + Sync + 'static,
43    {
44        values
45            .into_iter()
46            .enumerate()
47            .fold(self, |rec, (index, result)| rec.value(index, result))
48    }
49
50    /// Consume an [`Iterator`] of nested structs from the input recording
51    /// errors while parsing.
52    ///
53    /// The provided `Parser` is a closure that receives a [`StructBuilder`] for
54    /// the element that's passed into the parser as well. Use the
55    /// `StructBuilder` to record any parsing results while processing the
56    /// element.
57    pub fn of_structs<I, T, Parser>(self, elements: I, mut parse: Parser) -> Self
58    where
59        I: IntoIterator<Item = T>,
60        Parser: FnMut(StructBuilder<Self, Value, Nil>, T) -> Self,
61    {
62        elements
63            .into_iter()
64            .enumerate()
65            .fold(self, |rec, (index, element)| {
66                parse(rec.strukt(index), element)
67            })
68    }
69
70    /// Record a parsing results for a single value within the array at a
71    /// certain index.
72    ///
73    /// This is a low-level operation, consider using
74    /// [`of_values()`](Self::of_values) instead.
75    pub fn value<E>(mut self, index: usize, result: Result<Value, E>) -> Self
76    where
77        E: Error + Send + Sync + 'static,
78    {
79        match result {
80            Ok(value) => self.values.push(value),
81            Err(error) => {
82                self.errors.append(self.element_path(index), error);
83            }
84        }
85
86        self
87    }
88
89    /// Start a [`StructBuilder`] to record the parsing results for a nested
90    /// struct within the array at a certain index.
91    pub fn strukt(self, index: usize) -> StructBuilder<Self, Value, Nil> {
92        let path = self.element_path(index);
93        StructBuilder::new(self, path)
94    }
95
96    /// Finish the `ArrayBuilder` and pass the final result to the parent
97    /// builder.
98    pub fn finish(self) -> Parent::AfterRecord {
99        let result = if self.errors.is_empty() {
100            Ok(self.values)
101        } else {
102            Err(self.errors)
103        };
104
105        self.parent.finish_child_builder(result)
106    }
107
108    fn element_path(&self, index: usize) -> SourcePath {
109        self.base.join(PathSegment::Array {
110            name: self.array_name.clone(),
111            index,
112        })
113    }
114}
115
116impl<Parent, Value> ErrorBuilderParent<Value> for ArrayBuilder<Parent, Value> {
117    type AfterRecord = Self;
118
119    fn finish_child_builder(
120        mut self,
121        child_result: Result<Value, AccumulatedError>,
122    ) -> Self::AfterRecord {
123        match child_result {
124            Ok(value) => self.values.push(value),
125            Err(errors) => {
126                self.errors.merge(errors);
127            }
128        }
129
130        self
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use crate::{ErrorAccumulator, test_util::n};
138
139    #[derive(Debug, PartialEq, Eq)]
140    struct Test(u32);
141
142    #[test]
143    fn should_record_array_of_structs() {
144        let (res,) = ErrorAccumulator::new()
145            .array(n("foo"))
146            .of_structs(vec!["42", "21", "33"], |rec, value| {
147                rec.field(n("num"), value.parse()).on_ok(Test).finish()
148            })
149            .finish()
150            .analyse()
151            .unwrap();
152
153        assert_eq!(vec![Test(42), Test(21), Test(33)], res);
154    }
155
156    #[test]
157    fn should_record_array_of_values() {
158        let (res,) = ErrorAccumulator::new()
159            .array(n("foo"))
160            .of_values(vec!["42".parse(), "21".parse(), "33".parse()])
161            .finish()
162            .analyse()
163            .unwrap();
164
165        assert_eq!(vec![42, 21, 33], res);
166    }
167
168    #[test]
169    fn should_record_error_in_array() {
170        let res = ErrorAccumulator::new()
171            .array(n("foo"))
172            .of_values(vec!["42".parse::<u32>(), "aa".parse(), "bb".parse()])
173            .finish()
174            .analyse()
175            .unwrap_err();
176
177        assert_eq!(
178            res.get_by_path(&SourcePath::new().join(PathSegment::Array {
179                name: n("foo"),
180                index: 1
181            }))
182            .count(),
183            1
184        );
185        assert_eq!(
186            res.get_by_path(&SourcePath::new().join(PathSegment::Array {
187                name: n("foo"),
188                index: 2
189            }))
190            .count(),
191            1
192        );
193    }
194}