1#![deny(missing_debug_implementations)]
14#![deny(missing_docs)]
15
16use std::{error::Error, marker::PhantomData};
17
18use crate::{
19 builder::{ArrayBuilder, ErrorBuilderParent, FieldBuilder, StructBuilder},
20 cons::{Append, AsRefTuple, Nil, ToTuple},
21 construct::{Constructor, ListValidator},
22 error::AccumulatedError,
23 path::{FieldName, PathSegment, SourcePath},
24};
25
26pub mod builder;
27mod cons;
28pub mod construct;
29pub mod error;
30pub mod path;
31
32#[derive(Debug)]
45pub struct ErrorAccumulator<List> {
46 errors: AccumulatedError,
47 values: List,
48 base: SourcePath,
49}
50
51#[derive(Debug)]
55pub struct ErrorAccumulatorFinisher<List, Constructor, Out> {
56 accumulated_errors: AccumulatedError,
57 values: List,
58 constructor: Constructor,
59 _marker: PhantomData<Out>,
60}
61
62impl ErrorAccumulator<Nil> {
63 pub fn new() -> Self {
65 Self {
66 errors: Default::default(),
67 values: Nil,
68 base: Default::default(),
69 }
70 }
71}
72
73impl Default for ErrorAccumulator<Nil> {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl<ChildValue, List> ErrorBuilderParent<ChildValue> for ErrorAccumulator<List>
80where
81 List: Append<ChildValue>,
82{
83 type AfterRecord = ErrorAccumulator<List::Output>;
84
85 fn finish_child_builder(
86 self,
87 child_result: Result<ChildValue, AccumulatedError>,
88 ) -> Self::AfterRecord {
89 let Self {
90 errors: mut accumulated_errors,
91 values,
92 base,
93 } = self;
94
95 let values = match child_result {
96 Ok(value) => values.append(value),
97 Err(errors) => {
98 accumulated_errors.merge(errors);
99 values.append(None)
100 }
101 };
102
103 ErrorAccumulator {
104 errors: accumulated_errors,
105 values,
106 base,
107 }
108 }
109}
110
111impl<List> ErrorAccumulator<List> {
112 pub fn field<FieldValue, E>(
114 self,
115 field: FieldName,
116 result: Result<FieldValue, E>,
117 ) -> ErrorAccumulator<List::Output>
118 where
119 List: Append<FieldValue>,
120 E: Error + Send + Sync + 'static,
121 {
122 let path = self.base.join(PathSegment::Field(field));
123 FieldBuilder::new(self, path).value(result).finish()
124 }
125
126 pub fn field_builder<FieldValue>(self, field: FieldName) -> FieldBuilder<Self, FieldValue, Nil>
134 where
135 List: Append<FieldValue>,
136 {
137 let path = self.base.join(PathSegment::Field(field));
138 FieldBuilder::new(self, path)
139 }
140
141 pub fn strukt<StructValue>(self, field: FieldName) -> StructBuilder<Self, StructValue, Nil>
149 where
150 List: Append<StructValue>,
151 {
152 let path = self.base.join(PathSegment::Field(field));
153 StructBuilder::new(self, path)
154 }
155
156 pub fn array<ElementValue>(self, field: FieldName) -> ArrayBuilder<Self, ElementValue>
161 where
162 List: Append<Vec<ElementValue>>,
163 {
164 let base = self.base.clone();
165 ArrayBuilder::new(self, base, field)
166 }
167
168 pub fn with_previous<Valid, T, E>(self, validator: Valid) -> ErrorAccumulator<List::Output>
175 where
176 Valid: ListValidator<List, T, E>,
177 List: AsRefTuple + Append<T>,
178 E: Error + Send + Sync + 'static,
179 {
180 let Self {
181 mut errors,
182 values,
183 base,
184 } = self;
185
186 let values = if errors.is_empty() {
187 let result = validator.validate(&values);
188 append_or_record(values, &base, result, &mut errors)
189 } else {
190 values.append(None)
191 };
192
193 ErrorAccumulator {
194 errors,
195 values,
196 base,
197 }
198 }
199
200 pub fn on_ok<C, Out>(self, constructor: C) -> ErrorAccumulatorFinisher<List, C, Out>
208 where
209 List: ToTuple,
210 C: Constructor<List::List, Out>,
211 {
212 ErrorAccumulatorFinisher {
213 accumulated_errors: self.errors,
214 values: self.values,
215 constructor,
216 _marker: PhantomData,
217 }
218 }
219
220 pub fn analyse(self) -> Result<List::List, AccumulatedError>
226 where
227 List: ToTuple,
228 {
229 if self.errors.is_empty() {
230 Ok(self.values.unwrap_tuple())
232 } else {
233 Err(self.errors)
234 }
235 }
236}
237
238impl<List, Constr, Out> ErrorAccumulatorFinisher<List, Constr, Out>
239where
240 List: ToTuple,
241 Constr: Constructor<List::List, Out>,
242{
243 pub fn analyse(self) -> Result<Out, AccumulatedError> {
246 if self.accumulated_errors.is_empty() {
247 Ok(self.constructor.construct(self.values.unwrap_tuple()))
249 } else {
250 Err(self.accumulated_errors)
251 }
252 }
253}
254
255fn append_or_record<L, T, E>(
256 list: L,
257 path: &SourcePath,
258 result: Result<T, E>,
259 errors: &mut AccumulatedError,
260) -> L::Output
261where
262 L: Append<T>,
263 E: Error + Send + Sync + 'static,
264{
265 match result {
266 Ok(value) => list.append(value),
267 Err(error) => {
268 errors.append(path.clone(), error);
269 list.append(None)
270 }
271 }
272}
273
274#[cfg(test)]
275pub(crate) mod test_util {
276 use super::*;
277
278 pub fn n(name: &str) -> FieldName {
279 name.parse().unwrap()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use std::num::{NonZeroI16, ParseIntError, TryFromIntError};
286
287 use super::*;
288 use crate::test_util::n;
289
290 #[derive(Debug, PartialEq, Eq)]
291 struct TestThing {
292 num: u32,
293 non_zero: NonZeroI16,
294 }
295
296 impl TestThing {
297 fn new(num: u32, non_zero: NonZeroI16) -> Self {
298 Self { num, non_zero }
299 }
300 }
301
302 #[test]
303 fn should_return_ok_values() {
304 let (num, non_zero) = ErrorAccumulator::new()
305 .field_builder(n("foo"))
306 .value("42".parse::<u32>())
307 .finish()
308 .field_builder(n("bar"))
309 .value(NonZeroI16::try_from(-5))
310 .finish()
311 .analyse()
312 .unwrap();
313
314 assert_eq!(num, 42);
315 assert_eq!(non_zero.get(), -5);
316 }
317
318 #[test]
319 fn should_return_on_one_error() {
320 let err = ErrorAccumulator::new()
321 .field("bar".parse().unwrap(), "42".parse::<u32>())
322 .field("foo".parse().unwrap(), NonZeroI16::try_from(0))
323 .analyse()
324 .unwrap_err();
325
326 assert_eq!(err.len(), 1);
327 }
328
329 #[test]
330 fn should_return_multiple_errors() {
331 let err = ErrorAccumulator::new()
332 .field(n("foo"), "foo".parse::<u32>())
333 .field(n("bar"), NonZeroI16::try_from(0))
334 .analyse()
335 .unwrap_err();
336
337 assert_eq!(err.get_by_type::<ParseIntError>().count(), 1);
338 assert_eq!(err.get_by_type::<TryFromIntError>().count(), 1);
339 }
340
341 #[test]
342 fn should_allow_construction_on_success() {
343 let thing = ErrorAccumulator::new()
344 .field(n("bar"), "42".parse::<u32>())
345 .field(n("foo"), NonZeroI16::try_from(-5))
346 .on_ok(TestThing::new)
347 .analyse()
348 .unwrap();
349
350 assert_eq!(thing, TestThing::new(42, NonZeroI16::new(-5).unwrap()))
351 }
352}