use std::{
error::Error,
fmt::Display,
ops::{Deref, DerefMut},
};
#[cfg(feature = "derive")]
pub use engcon_macros::*;
pub struct Validated<T: Validator + Sized> {
inner: T,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ValidationError {
msg: String,
src: String,
}
pub trait Validator: Sized {
fn validate(&self) -> Result<(), ValidationError>;
fn try_into_validated(self) -> Result<Validated<Self>, ValidationError> {
match self.validate() {
Ok(_) => {
let reval = unsafe { Validated::new_unchecked(self) };
Ok(reval)
}
Err(err) => Err(err),
}
}
}
impl Error for ValidationError {}
impl ValidationError {
pub fn new(msg: String, src: String) -> Self {
ValidationError { msg, src }
}
}
impl Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Error validating '{}': {}", self.src, self.msg)
}
}
impl<T: Validator + Sized> Validated<T> {
pub unsafe fn new_unchecked(inner: T) -> Self {
Validated::<T> { inner }
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: Validator + Sized> Deref for Validated<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: Validator + Sized> DerefMut for Validated<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[cfg(test)]
mod tests {
#[derive(Debug, Clone, PartialEq)]
struct PlainOldData {
only_lower: String,
}
impl Validator for PlainOldData {
fn validate(&self) -> Result<(), ValidationError> {
if self.only_lower.chars().any(|ch| !ch.is_lowercase()) {
Err(ValidationError::new(
"String is not entirely lower-case".to_owned(),
"Src".to_owned(),
))
} else {
Ok(())
}
}
}
use super::*;
#[test]
fn all_lowercase_works() {
let tmp = PlainOldData {
only_lower: "thisisonlylowercase".to_owned(),
};
let result = tmp.clone().validate();
assert!(result.is_ok());
}
#[test]
fn whitespaces_are_errors() {
let tmp = PlainOldData {
only_lower: "this is not only lowercase".to_owned(),
};
let result = tmp.clone().validate();
assert!(result.is_err());
}
}