error_accumulator/builder/
field.rs

1use std::{error::Error, marker::PhantomData};
2
3use crate::{
4    append_or_record,
5    builder::{BuilderFinisher, ErrorBuilderParent},
6    cons::{Append, AsRefTuple, Cons, Nil, ToTuple},
7    construct::{Constructor, ListValidator},
8    error::AccumulatedError,
9    path::SourcePath,
10};
11
12/// A builder to record parsing results for a field of the input.
13#[derive(Debug)]
14pub struct FieldBuilder<Parent, Value, List> {
15    parent: Parent,
16    errors: AccumulatedError,
17    field: SourcePath,
18    values: List,
19    _marker: PhantomData<Value>,
20}
21
22impl<Parent, Value> FieldBuilder<Parent, Value, Nil>
23where
24    Parent: ErrorBuilderParent<Value>,
25{
26    pub(crate) fn new(parent: Parent, path: SourcePath) -> Self {
27        Self {
28            field: path,
29            parent,
30            errors: Default::default(),
31            values: Nil,
32            _marker: PhantomData,
33        }
34    }
35}
36
37impl<Parent, Value, List> FieldBuilder<Parent, Value, List>
38where
39    Parent: ErrorBuilderParent<Value>,
40{
41    /// Record a parsing result for the field.
42    pub fn value<T, E>(self, result: Result<T, E>) -> FieldBuilder<Parent, Value, List::Output>
43    where
44        List: Append<T>,
45        E: Error + Send + Sync + 'static,
46    {
47        let Self {
48            parent,
49            mut errors,
50            field,
51            values,
52            _marker,
53        } = self;
54
55        let values = append_or_record(values, &field, result, &mut errors);
56
57        FieldBuilder {
58            parent,
59            errors,
60            field,
61            values,
62            _marker,
63        }
64    }
65
66    /// Record a value for the field.
67    ///
68    /// This is infallible so it's easy to insert values that do not need
69    /// parsing in the [`ErrorAccumulator`](crate::ErrorAccumulator)'s system.
70    pub fn value_ok<T>(self, value: T) -> FieldBuilder<Parent, Value, List::Output>
71    where
72        List: Append<T>,
73    {
74        let Self {
75            parent,
76            errors,
77            field,
78            values,
79            _marker,
80        } = self;
81
82        let values = values.append(value);
83
84        FieldBuilder {
85            parent,
86            errors,
87            field,
88            values,
89            _marker,
90        }
91    }
92
93    /// Run another validation step on the previously recorded `Ok` values if
94    /// there were no errors yet.
95    ///
96    /// In case an error was already recorded the `validator` is not executed.
97    ///
98    /// For an example, see the docs of
99    /// [`StructBuilder::with_previous()`](crate::StructBuilder::with_previous).
100    pub fn with_previous<Valid, T, E>(
101        self,
102        validator: Valid,
103    ) -> FieldBuilder<Parent, Value, List::Output>
104    where
105        Valid: ListValidator<List, T, E>,
106        List: AsRefTuple + Append<T>,
107        E: Error + Send + Sync + 'static,
108    {
109        let Self {
110            parent,
111            mut errors,
112            field,
113            values,
114            _marker,
115        } = self;
116
117        let values = if errors.is_empty() {
118            let result = validator.validate(&values);
119            append_or_record(values, &field, result, &mut errors)
120        } else {
121            values.append(None)
122        };
123
124        FieldBuilder {
125            parent,
126            errors,
127            field,
128            values,
129            _marker,
130        }
131    }
132
133    /// Provide a [`Constructor`] to convert the recorded `Ok` values into the
134    /// target type.
135    pub fn on_ok<C>(self, constructor: C) -> BuilderFinisher<Parent, Value, List, C>
136    where
137        List: ToTuple,
138        C: Constructor<List::List, Value>,
139    {
140        BuilderFinisher {
141            parent: self.parent,
142            accumulated_errors: self.errors,
143            values: self.values,
144            constructor,
145            _marker: PhantomData,
146        }
147    }
148}
149
150impl<Parent, Value> FieldBuilder<Parent, Value, Cons<Value, Nil>>
151where
152    Parent: ErrorBuilderParent<Value>,
153{
154    /// Finish the builder and pass the builder's final result to the parent
155    /// builder.
156    pub fn finish(self) -> Parent::AfterRecord {
157        let result = if self.errors.is_empty() {
158            let (value,) = self.values.unwrap_tuple();
159            Ok(value)
160        } else {
161            Err(self.errors)
162        };
163
164        self.parent.finish_child_builder(result)
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use std::num::NonZeroI16;
171
172    use crate::{ErrorAccumulator, test_util::n};
173
174    #[test]
175    fn should_allow_multivalue_field_record() {
176        let (num,) = ErrorAccumulator::new()
177            .field_builder(n("foo"))
178            .value("42".parse::<u32>())
179            .value(NonZeroI16::try_from(-5))
180            .on_ok(|v, _| v)
181            .finish()
182            .analyse()
183            .unwrap();
184
185        assert_eq!(num, 42);
186    }
187
188    #[test]
189    fn should_return_error_on_multivalue_field_record() {
190        let err = ErrorAccumulator::new()
191            .field_builder(n("bar"))
192            .value("42".parse::<u32>())
193            .value(NonZeroI16::try_from(0))
194            .on_ok(|v, _| v)
195            .finish()
196            .analyse()
197            .unwrap_err();
198
199        assert_eq!(err.len(), 1);
200    }
201}