use std::borrow::Cow;
use std::collections::HashMap;
use thiserror::Error;
use crate::Result;
#[cfg(feature = "use-serde")]
use serde::Serialize;
#[derive(Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "use-serde", derive(Serialize), serde(untagged))]
pub enum Location {
Named(Cow<'static, str>),
Index(usize),
}
#[derive(Error, Debug, PartialEq)]
#[cfg_attr(feature = "use-serde", derive(Serialize), serde(untagged))]
pub enum Error {
#[error("{0:#?}")]
Unstructured(Vec<Cow<'static, str>>),
#[error("{0:#?}")]
Structured(HashMap<Location, Error>),
}
impl Error {
pub fn new<S>(message: S) -> Self
where
S: Into<Cow<'static, str>>,
{
Self::Unstructured(vec![message.into()])
}
pub fn merge(&mut self, other: Error) {
match self {
Error::Unstructured(x) => match other {
Error::Unstructured(y) => x.extend(y.into_iter()),
_ => panic!("can only merge duplicate variants"),
},
Error::Structured(x) => match other {
Error::Structured(y) => {
x.extend(y.into_iter());
}
_ => panic!("can only merge duplicate variants"),
},
};
}
pub fn build() -> ErrorBuilder {
ErrorBuilder { errors: None }
}
}
pub struct ErrorBuilder {
errors: Option<Error>,
}
fn build_structured(errs: &mut Option<Error>, loc: Location, error: Error) {
let mut structured_errs = errs
.take()
.map(|e| match e {
Error::Unstructured(_) => panic!("should never happen"),
Error::Structured(hm) => hm,
})
.unwrap_or_else(HashMap::new);
use std::collections::hash_map::Entry;
match structured_errs.entry(loc) {
Entry::Occupied(mut entry) => {
entry.get_mut().merge(error);
}
Entry::Vacant(entry) => {
entry.insert(error);
}
};
*errs = Some(Error::Structured(structured_errs));
}
impl ErrorBuilder {
pub fn contains_errors(&self) -> bool {
self.errors.is_some()
}
pub fn build(&mut self) -> Result<()> {
if let Some(e) = self.errors.take() {
Err(e)
} else {
Ok(())
}
}
pub fn at_location(
&mut self,
location: Location,
message: impl Into<Cow<'static, str>>,
) -> &mut Self {
let e = Error::new(message);
build_structured(&mut self.errors, location, e);
self
}
pub fn at_named(
&mut self,
name: impl Into<Cow<'static, str>>,
message: impl Into<Cow<'static, str>>,
) -> &mut Self {
self.at_location(Location::Named(name.into()), message)
}
pub fn at_index(&mut self, index: usize, message: impl Into<Cow<'static, str>>) -> &mut Self {
self.at_location(Location::Index(index), message)
}
pub fn try_at_location(&mut self, location: Location, result: Result<()>) -> &mut Self {
if let Err(e) = result {
build_structured(&mut self.errors, location, e);
}
self
}
pub fn try_at_named(
&mut self,
name: impl Into<Cow<'static, str>>,
result: Result<()>,
) -> &mut Self {
self.try_at_location(Location::Named(name.into()), result)
}
pub fn try_at_index(&mut self, index: usize, result: Result<()>) -> &mut Self {
self.try_at_location(Location::Index(index), result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Validate;
#[test]
fn builder_works() {
struct Bar(f64);
impl Validate for Bar {
fn validate(&self) -> Result<()> {
if (self.0 - 42.).abs() < 0.1 {
Err(Error::new(
"cannot comprehend the meaning of life the universe and everything.",
))
} else {
Ok(())
}
}
}
struct Foo {
a: u64,
b: Vec<i64>,
c: HashMap<String, Bar>,
}
fn is_positive(x: &i64) -> Result<()> {
if *x < 0 {
Err(Error::new("value must be positive"))
} else {
Ok(())
}
}
fn validate_a_vector(x: &[i64]) -> Result<()> {
let mut eb = Error::build();
for (i, v) in x.iter().enumerate() {
eb.try_at_index(i, is_positive(v));
if i % 2 == 1 {
eb.at_index(i, "must be multiple of 2");
}
}
eb.build()
}
fn validate_a_map(x: &HashMap<String, Bar>) -> Result<()> {
let mut eb = Error::build();
if x.contains_key("Foo") {
eb.at_named("Foo", "must not contain a key with this name");
}
for (k, v) in x {
eb.try_at_named(k.to_string(), v.validate());
}
eb.build()
}
fn validate_foo(x: &Foo) -> Result<()> {
Error::build()
.try_at_named("a", crate::validators::min(&x.a, 5))
.try_at_named("b", validate_a_vector(&x.b))
.try_at_named("c", validate_a_map(&x.c))
.build()
}
let value = Foo {
a: 10,
b: vec![-1, 0, 1, 2],
c: vec![
("Foo".to_string(), Bar(42.)),
("Not Foo".to_string(), Bar(666.)),
]
.into_iter()
.collect(),
};
assert!(validate_foo(&value).is_err());
}
#[test]
fn test_errors() {
let _e = Error::new("foo");
let _e = Error::new("foo".to_string());
}
#[test]
fn test_error_merge() {
let mut a = Error::new("a");
let b = Error::new("b");
a.merge(b);
match a {
Error::Unstructured(x) => {
assert_eq!(x, vec!["a", "b"]);
}
Error::Structured(_) => panic!("should not happen"),
}
let mut a = Error::new("a");
let b = Error::new("b");
a.merge(b);
match a {
Error::Unstructured(x) => {
assert_eq!(x, vec!["a", "b"]);
}
Error::Structured(_) => panic!("should not happen"),
}
}
}