error_accumulator/builder/
strukt.rs

1use std::{error::Error, marker::PhantomData};
2
3use crate::{
4    append_or_record,
5    builder::{ArrayBuilder, BuilderFinisher, ErrorBuilderParent, FieldBuilder},
6    cons::{Append, AsRefTuple, Nil, ToTuple},
7    construct::{Constructor, ListValidator},
8    error::AccumulatedError,
9    path::{FieldName, PathSegment, SourcePath},
10};
11
12/// A builder to record parsing results for a nested struct in the input.
13///
14/// A `StructBuilder` can have other nested structs, arrays, and fields.
15#[derive(Debug)]
16pub struct StructBuilder<Parent, Value, List> {
17    parent: Parent,
18    errors: AccumulatedError,
19    struct_path: SourcePath,
20    values: List,
21    _marker: PhantomData<Value>,
22}
23
24impl<Parent, Value> StructBuilder<Parent, Value, Nil>
25where
26    Parent: ErrorBuilderParent<Value>,
27{
28    pub(crate) fn new(parent: Parent, base: SourcePath) -> Self {
29        Self {
30            struct_path: base,
31            parent,
32            errors: Default::default(),
33            values: Nil,
34            _marker: PhantomData,
35        }
36    }
37}
38
39impl<Parent, Value, List> StructBuilder<Parent, Value, List>
40where
41    Parent: ErrorBuilderParent<Value>,
42{
43    /// Record a parsing result for a field in this struct.
44    pub fn field<T, E>(
45        self,
46        field: FieldName,
47        result: Result<T, E>,
48    ) -> StructBuilder<Parent, Value, List::Output>
49    where
50        List: Append<T>,
51        E: Error + Send + Sync + 'static,
52        Self: ErrorBuilderParent<T, AfterRecord = StructBuilder<Parent, Value, List::Output>>,
53    {
54        let field_path = self.struct_path.join(PathSegment::Field(field));
55        FieldBuilder::new(self, field_path).value(result).finish()
56    }
57
58    /// Start a [`FieldBuilder`] to record the parsing results for a field in
59    /// this struct.
60    pub fn field_builder<FieldValue>(self, field: FieldName) -> FieldBuilder<Self, FieldValue, Nil>
61    where
62        List: Append<FieldValue>,
63    {
64        let field_path = self.struct_path.join(PathSegment::Field(field));
65        FieldBuilder::new(self, field_path)
66    }
67
68    /// Start a [`StructBuilder`] to record the parsing results of a nested
69    /// struct within the current one.
70    pub fn strukt<StructValue>(self, field: FieldName) -> StructBuilder<Self, StructValue, Nil>
71    where
72        List: Append<StructValue>,
73    {
74        let base = self.struct_path.join(PathSegment::Field(field));
75        StructBuilder::new(self, base)
76    }
77
78    /// Start an [`ArrayBuilder`] to record the parsing results for a nested
79    /// array within the current struct.
80    pub fn array<ElementValue>(self, field: FieldName) -> ArrayBuilder<Self, ElementValue>
81    where
82        List: Append<Vec<ElementValue>>,
83    {
84        let base = self.struct_path.clone();
85        ArrayBuilder::new(self, base, field)
86    }
87
88    /// Run another validation step on the previously recorded `Ok` values if
89    /// there were no errors yet.
90    ///
91    /// In case an error was already recorded the `validator` is not executed.
92    ///
93    /// There are blanked implementations for [`ListValidator`] for closures
94    /// that take the references to `Ok` values as arguments, e.g.
95    ///
96    /// ```
97    /// # use std::{convert::Infallible, num::NonZeroU16};
98    /// # use error_accumulator::{ErrorAccumulator, path::FieldName};
99    /// # const FOO: FieldName = FieldName::new_unchecked("foo");
100    /// # const BAR: FieldName = FieldName::new_unchecked("bar");
101    /// # const BAZ: FieldName = FieldName::new_unchecked("baz");
102    /// let res = ErrorAccumulator::new().strukt(FOO)
103    ///     .field(BAR, NonZeroU16::try_from(16))
104    ///     .field(BAZ, NonZeroU16::try_from(8))
105    ///     // for some reason the type annotation with `&_` is required to make this compile
106    ///     .with_previous(|bar: &_, baz: &_|
107    ///         Ok::<_, Infallible>(format!("{bar}{baz}"))
108    ///     )
109    ///     .on_ok(|_, _, res| res)
110    ///     .finish()
111    ///     .analyse()
112    ///     .unwrap()
113    ///     .0;
114    /// assert_eq!(res.as_str(), "168");
115    /// ```
116    pub fn with_previous<Valid, T, E>(
117        self,
118        validator: Valid,
119    ) -> StructBuilder<Parent, Value, List::Output>
120    where
121        Valid: ListValidator<List, T, E>,
122        List: AsRefTuple + Append<T>,
123        E: Error + Send + Sync + 'static,
124    {
125        let Self {
126            parent,
127            mut errors,
128            struct_path,
129            values,
130            _marker,
131        } = self;
132
133        let values = if errors.is_empty() {
134            let result = validator.validate(&values);
135            append_or_record(values, &struct_path, result, &mut errors)
136        } else {
137            values.append(None)
138        };
139
140        StructBuilder {
141            parent,
142            errors,
143            struct_path,
144            values,
145            _marker,
146        }
147    }
148
149    /// Provide a [`Constructor`] to build a struct values from all the recorded
150    /// `Ok` values of the builder.
151    pub fn on_ok<C>(self, constructor: C) -> BuilderFinisher<Parent, Value, List, C>
152    where
153        List: ToTuple,
154        C: Constructor<List::List, Value>,
155    {
156        BuilderFinisher {
157            parent: self.parent,
158            accumulated_errors: self.errors,
159            values: self.values,
160            constructor,
161            _marker: PhantomData,
162        }
163    }
164}
165
166impl<Parent, OwnValue, ChildValue, List> ErrorBuilderParent<ChildValue>
167    for StructBuilder<Parent, OwnValue, List>
168where
169    List: Append<ChildValue>,
170{
171    type AfterRecord = StructBuilder<Parent, OwnValue, List::Output>;
172
173    fn finish_child_builder(
174        self,
175        child_result: Result<ChildValue, AccumulatedError>,
176    ) -> Self::AfterRecord {
177        let Self {
178            parent,
179            mut errors,
180            struct_path,
181            values,
182            _marker,
183        } = self;
184
185        let values = match child_result {
186            Ok(value) => values.append(value),
187            Err(child_errors) => {
188                errors.merge(child_errors);
189                values.append(None)
190            }
191        };
192
193        StructBuilder {
194            parent,
195            errors,
196            struct_path,
197            values,
198            _marker,
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use std::{io, num::NonZeroI16};
206
207    use super::*;
208    use crate::{ErrorAccumulator, test_util::n};
209
210    #[test]
211    fn should_record_nested_structs() {
212        let foo_struct = ErrorAccumulator::new().strukt(n("foo"));
213        let foo_struct = foo_struct
214            .field_builder(n("bar"))
215            .value("42".parse::<u32>())
216            .value(NonZeroI16::try_from(-5))
217            .on_ok(|v, _| v)
218            .finish();
219        let foo_baz_struct = foo_struct
220            .strukt(n("baz"))
221            .field(n("quux"), Ok::<_, io::Error>("god"))
222            .on_ok(|s: &str| s.chars().rev().collect::<String>())
223            .finish();
224
225        let (res,) = foo_baz_struct
226            .on_ok(|num, s| format!("{num}|{s}"))
227            .finish()
228            .analyse()
229            .unwrap();
230
231        assert_eq!(res.as_str(), "42|dog")
232    }
233
234    #[test]
235    fn should_record_nested_error() {
236        let foo_struct = ErrorAccumulator::new().strukt(n("foo"));
237        let foo_struct = foo_struct
238            .field_builder(n("bar"))
239            .value("42".parse::<u32>())
240            .value(NonZeroI16::try_from(-5))
241            .on_ok(|v, _| v)
242            .finish();
243        let foo_baz_struct = foo_struct
244            .strukt(n("baz"))
245            .field(
246                n("quux"),
247                Err::<&str, _>(io::Error::new(io::ErrorKind::AddrInUse, "bad")),
248            )
249            .on_ok(|s: &str| s.chars().rev().collect::<String>())
250            .finish();
251
252        let res = foo_baz_struct
253            .on_ok(|num, s| format!("{num}|{s}"))
254            .finish()
255            .analyse()
256            .unwrap_err();
257
258        assert_eq!(
259            res.get_by_path(
260                &SourcePath::new()
261                    .join(PathSegment::Field(n("foo")))
262                    .join(PathSegment::Field(n("baz")))
263                    .join(PathSegment::Field(n("quux")))
264            )
265            .count(),
266            1
267        );
268    }
269}