#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
use std::{error::Error, marker::PhantomData};
use crate::{
builder::{ArrayBuilder, ErrorBuilderParent, FieldBuilder, StructBuilder},
cons::{Append, AsRefTuple, Nil, ToTuple},
construct::{Constructor, ListValidator},
error::AccumulatedError,
path::{FieldName, PathSegment, SourcePath},
};
pub mod builder;
mod cons;
pub mod construct;
pub mod error;
pub mod path;
#[derive(Debug)]
pub struct ErrorAccumulator<List> {
errors: AccumulatedError,
values: List,
base: SourcePath,
}
#[derive(Debug)]
pub struct ErrorAccumulatorFinisher<List, Constructor, Out> {
accumulated_errors: AccumulatedError,
values: List,
constructor: Constructor,
_marker: PhantomData<Out>,
}
impl ErrorAccumulator<Nil> {
pub fn new() -> Self {
Self {
errors: Default::default(),
values: Nil,
base: Default::default(),
}
}
}
impl Default for ErrorAccumulator<Nil> {
fn default() -> Self {
Self::new()
}
}
impl<ChildValue, List> ErrorBuilderParent<ChildValue> for ErrorAccumulator<List>
where
List: Append<ChildValue>,
{
type AfterRecord = ErrorAccumulator<List::Output>;
fn finish_child_builder(
self,
child_result: Result<ChildValue, AccumulatedError>,
) -> Self::AfterRecord {
let Self {
errors: mut accumulated_errors,
values,
base,
} = self;
let values = match child_result {
Ok(value) => values.append(value),
Err(errors) => {
accumulated_errors.merge(errors);
values.append(None)
}
};
ErrorAccumulator {
errors: accumulated_errors,
values,
base,
}
}
}
impl<List> ErrorAccumulator<List> {
pub fn field<FieldValue, E>(
self,
field: FieldName,
result: Result<FieldValue, E>,
) -> ErrorAccumulator<List::Output>
where
List: Append<FieldValue>,
E: Error + Send + Sync + 'static,
{
let path = self.base.join(PathSegment::Field(field));
FieldBuilder::new(self, path).value(result).finish()
}
pub fn field_builder<FieldValue>(self, field: FieldName) -> FieldBuilder<Self, FieldValue, Nil>
where
List: Append<FieldValue>,
{
let path = self.base.join(PathSegment::Field(field));
FieldBuilder::new(self, path)
}
pub fn strukt<StructValue>(self, field: FieldName) -> StructBuilder<Self, StructValue, Nil>
where
List: Append<StructValue>,
{
let path = self.base.join(PathSegment::Field(field));
StructBuilder::new(self, path)
}
pub fn array<ElementValue>(self, field: FieldName) -> ArrayBuilder<Self, ElementValue>
where
List: Append<Vec<ElementValue>>,
{
let base = self.base.clone();
ArrayBuilder::new(self, base, field)
}
pub fn with_previous<Valid, T, E>(self, validator: Valid) -> ErrorAccumulator<List::Output>
where
Valid: ListValidator<List, T, E>,
List: AsRefTuple + Append<T>,
E: Error + Send + Sync + 'static,
{
let Self {
mut errors,
values,
base,
} = self;
let values = if errors.is_empty() {
let result = validator.validate(&values);
append_or_record(values, &base, result, &mut errors)
} else {
values.append(None)
};
ErrorAccumulator {
errors,
values,
base,
}
}
pub fn on_ok<C, Out>(self, constructor: C) -> ErrorAccumulatorFinisher<List, C, Out>
where
List: ToTuple,
C: Constructor<List::List, Out>,
{
ErrorAccumulatorFinisher {
accumulated_errors: self.errors,
values: self.values,
constructor,
_marker: PhantomData,
}
}
pub fn analyse(self) -> Result<List::List, AccumulatedError>
where
List: ToTuple,
{
if self.errors.is_empty() {
Ok(self.values.unwrap_tuple())
} else {
Err(self.errors)
}
}
}
impl<List, Constr, Out> ErrorAccumulatorFinisher<List, Constr, Out>
where
List: ToTuple,
Constr: Constructor<List::List, Out>,
{
pub fn analyse(self) -> Result<Out, AccumulatedError> {
if self.accumulated_errors.is_empty() {
Ok(self.constructor.construct(self.values.unwrap_tuple()))
} else {
Err(self.accumulated_errors)
}
}
}
fn append_or_record<L, T, E>(
list: L,
path: &SourcePath,
result: Result<T, E>,
errors: &mut AccumulatedError,
) -> L::Output
where
L: Append<T>,
E: Error + Send + Sync + 'static,
{
match result {
Ok(value) => list.append(value),
Err(error) => {
errors.append(path.clone(), error);
list.append(None)
}
}
}
#[cfg(test)]
pub(crate) mod test_util {
use super::*;
pub fn n(name: &str) -> FieldName {
name.parse().unwrap()
}
}
#[cfg(test)]
mod tests {
use std::num::{NonZeroI16, ParseIntError, TryFromIntError};
use super::*;
use crate::test_util::n;
#[derive(Debug, PartialEq, Eq)]
struct TestThing {
num: u32,
non_zero: NonZeroI16,
}
impl TestThing {
fn new(num: u32, non_zero: NonZeroI16) -> Self {
Self { num, non_zero }
}
}
#[test]
fn should_return_ok_values() {
let (num, non_zero) = ErrorAccumulator::new()
.field_builder(n("foo"))
.value("42".parse::<u32>())
.finish()
.field_builder(n("bar"))
.value(NonZeroI16::try_from(-5))
.finish()
.analyse()
.unwrap();
assert_eq!(num, 42);
assert_eq!(non_zero.get(), -5);
}
#[test]
fn should_return_on_one_error() {
let err = ErrorAccumulator::new()
.field("bar".parse().unwrap(), "42".parse::<u32>())
.field("foo".parse().unwrap(), NonZeroI16::try_from(0))
.analyse()
.unwrap_err();
assert_eq!(err.len(), 1);
}
#[test]
fn should_return_multiple_errors() {
let err = ErrorAccumulator::new()
.field(n("foo"), "foo".parse::<u32>())
.field(n("bar"), NonZeroI16::try_from(0))
.analyse()
.unwrap_err();
assert_eq!(err.get_by_type::<ParseIntError>().count(), 1);
assert_eq!(err.get_by_type::<TryFromIntError>().count(), 1);
}
#[test]
fn should_allow_construction_on_success() {
let thing = ErrorAccumulator::new()
.field(n("bar"), "42".parse::<u32>())
.field(n("foo"), NonZeroI16::try_from(-5))
.on_ok(TestThing::new)
.analyse()
.unwrap();
assert_eq!(thing, TestThing::new(42, NonZeroI16::new(-5).unwrap()))
}
}