use std::fmt;
#[derive(Clone)]
pub struct ValidationError(Vec<String>);
impl ValidationError {
pub fn builder() -> ValidationErrorBuilder {
ValidationErrorBuilder::default()
}
pub fn extend(&mut self, other: Self) {
self.0.extend(other.0);
}
pub fn for_field(mut self, name: &str) -> Self {
for err in self.0.iter_mut() {
*err = format!("for `{name}`: {err}");
}
self
}
pub fn invalid(reason: impl Into<String>) -> Self {
Self(vec![reason.into()])
}
}
impl fmt::Debug for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(&self.0).finish()
}
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0.join(", "))
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, Clone, Default)]
pub struct ValidationErrorBuilder(Vec<String>);
impl ValidationErrorBuilder {
pub fn build(self) -> Result<(), ValidationError> {
if self.0.is_empty() {
Ok(())
} else {
Err(ValidationError(self.0))
}
}
pub fn invalid(mut self, reason: impl Into<String>) -> Self {
self.invalid_mut(reason);
self
}
pub fn invalid_mut(&mut self, reason: impl Into<String>) -> &mut Self {
self.0.push(reason.into());
self
}
pub fn extend(mut self, error: ValidationError) -> Self {
self.extend_mut(error);
self
}
pub fn extend_mut(&mut self, error: ValidationError) -> &mut Self {
self.0.extend(error.0);
self
}
}
#[cfg(test)]
mod tests {
use crate::validate::ValidationError;
#[test]
fn record_invalid() {
assert_contains(
&ValidationError::invalid("custom message"),
"custom message",
);
assert_contains(
&ValidationError::builder()
.invalid("custom message")
.build()
.unwrap_err(),
"custom message",
);
let multiple = ValidationError::builder()
.invalid("first")
.invalid("second")
.build()
.unwrap_err();
assert_contains(&multiple, "first");
assert_contains(&multiple, "second");
}
#[test]
fn extendable() {
let mut extended = ValidationError::invalid("first");
extended.extend(ValidationError::invalid("second"));
assert_contains(&extended, "first");
assert_contains(&extended, "second");
let extended = ValidationError::builder()
.invalid("first")
.extend(ValidationError::invalid("second"))
.build()
.unwrap_err();
assert_contains(&extended, "first");
assert_contains(&extended, "second");
}
#[test]
fn add_field_context() {
let contextualized = ValidationError::invalid("custom message").for_field("my_field");
assert_contains(&contextualized, "custom message");
assert_contains(&contextualized, "my_field");
}
#[test]
fn build_returns_ok_if_no_errors() {
assert!(ValidationError::builder().build().is_ok());
}
fn assert_contains(error: &ValidationError, s: &str) {
assert!(format!("{}", error).contains(s));
assert!(format!("{:?}", error).contains(s));
}
}