use crate::Context;
use crate::error::CodedError;
pub trait Validator<T> {
type Error: std::error::Error;
fn validate(&self, ctx: &Context, value: &T) -> Result<(), Self::Error>;
}
pub struct ValidatorFn<F, E>
where
E: std::error::Error,
{
f: F,
_marker: std::marker::PhantomData<E>,
}
impl<F, E> ValidatorFn<F, E>
where
E: std::error::Error,
{
pub fn new<T>(f: F) -> Self
where
F: Fn(&Context, &T) -> Result<(), E>,
{
Self {
f,
_marker: std::marker::PhantomData,
}
}
}
impl<F, T, E> Validator<T> for ValidatorFn<F, E>
where
F: Fn(&Context, &T) -> Result<(), E>,
E: std::error::Error,
{
type Error = E;
fn validate(&self, ctx: &Context, value: &T) -> Result<(), Self::Error> {
(self.f)(ctx, value)
}
}
pub struct PassValidator;
impl<T> Validator<T> for PassValidator {
type Error = std::convert::Infallible;
fn validate(&self, _ctx: &Context, _value: &T) -> Result<(), Self::Error> {
Ok(())
}
}
pub struct FailValidator {
error: CodedError,
}
impl FailValidator {
#[must_use]
pub fn new(error: CodedError) -> Self {
Self { error }
}
}
impl<T> Validator<T> for FailValidator {
type Error = CodedError;
fn validate(&self, _ctx: &Context, _value: &T) -> Result<(), Self::Error> {
Err(self.error.clone())
}
}
#[cfg(feature = "async")]
use async_trait::async_trait;
#[cfg(feature = "async")]
#[async_trait]
pub trait AsyncValidator<T>: Send + Sync
where
T: Send + Sync,
{
type Error: std::error::Error + Send;
async fn validate(&self, ctx: &Context, value: &T) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
#[async_trait]
impl<V, T> AsyncValidator<T> for V
where
V: Validator<T> + Send + Sync,
V::Error: Send,
T: Send + Sync,
{
type Error = V::Error;
async fn validate(&self, ctx: &Context, value: &T) -> Result<(), Self::Error> {
Validator::validate(self, ctx, value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validator_fn_pass() {
let validator = ValidatorFn::new(|_ctx: &Context, value: &i32| -> Result<(), CodedError> {
if *value > 0 {
Ok(())
} else {
Err(CodedError::new("INVALID"))
}
});
let result = Validator::validate(&validator, &Context::new(), &42);
assert!(result.is_ok());
}
#[test]
fn validator_fn_fail() {
let validator = ValidatorFn::new(|_ctx: &Context, value: &i32| -> Result<(), CodedError> {
if *value > 0 {
Ok(())
} else {
Err(CodedError::new("INVALID"))
}
});
let result = Validator::validate(&validator, &Context::new(), &-1);
assert!(result.is_err());
}
#[test]
fn pass_validator() {
let validator = PassValidator;
let result: Result<(), std::convert::Infallible> =
Validator::validate(&validator, &Context::new(), &"anything");
assert!(result.is_ok());
}
#[test]
fn fail_validator() {
let validator = FailValidator::new(CodedError::new("ALWAYS_FAIL"));
let result = Validator::validate(&validator, &Context::new(), &42);
assert!(result.is_err());
assert_eq!(
result.err().map(|e| e.code().to_string()),
Some("ALWAYS_FAIL".to_string())
);
}
}