use crate::error::{Error, ErrorKind};
use crate::{Meta, Validator};
use tanzim_value::Value;
pub struct Either {
meta: Meta,
first: Box<dyn Validator>,
second: Box<dyn Validator>,
}
impl Either {
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = meta;
self
}
pub fn new(
first: impl Into<Box<dyn Validator>>,
second: impl Into<Box<dyn Validator>>,
) -> Self {
Self {
meta: Meta::default(),
first: first.into(),
second: second.into(),
}
}
}
crate::impl_meta_methods!(Either);
impl Validator for Either {
fn meta(&self) -> &Meta {
&self.meta
}
fn meta_mut(&mut self) -> &mut Meta {
&mut self.meta
}
fn check(&self, value: &mut Value) -> Result<(), Error> {
let mut candidate = value.clone();
let first_error = match self.first.validate(&mut candidate) {
Ok(()) => {
*value = candidate;
return Ok(());
}
Err(error) => error,
};
let mut candidate = value.clone();
let second_error = match self.second.validate(&mut candidate) {
Ok(()) => {
*value = candidate;
return Ok(());
}
Err(error) => error,
};
Err(Error::new(ErrorKind::Either {
first: Box::new(first_error),
second: Box::new(second_error),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Bool, Integer};
#[test]
fn accepts_when_either_matches() {
let validator = Either::new(Integer::new(), Bool::new());
assert!(validator.validate(&mut Value::Int(3)).is_ok());
assert!(validator.validate(&mut Value::Bool(true)).is_ok());
}
#[test]
fn commits_coercion_of_winning_branch() {
let validator = Either::new(Integer::new(), Bool::new());
let mut value = Value::String("5".into());
validator.validate(&mut value).unwrap();
assert_eq!(value, Value::Int(5));
}
#[test]
fn original_value_preserved_for_second_attempt() {
let validator = Either::new(Integer::new(), Bool::new());
let mut value = Value::Bool(true);
validator.validate(&mut value).unwrap();
assert_eq!(value, Value::Bool(true));
}
#[test]
fn combines_errors_when_both_fail() {
let validator = Either::new(Bool::new(), Integer::new());
let mut value = Value::String("nope".into());
let error = validator.validate(&mut value).unwrap_err();
assert!(matches!(error.kind, ErrorKind::Either { .. }));
assert_eq!(value, Value::String("nope".into()));
}
}