error_accumulator/builder/
strukt.rs1use 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#[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 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 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 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 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 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 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}