161 lines
3.7 KiB
Rust
161 lines
3.7 KiB
Rust
use std::fmt;
|
|
|
|
use crate::core::*;
|
|
|
|
/// Sanitized user input
|
|
#[repr(transparent)]
|
|
pub struct Sane<T>(T);
|
|
|
|
/// Unsanitized user input
|
|
#[repr(transparent)]
|
|
pub struct Insane<T>(T);
|
|
|
|
/// Anything that can be validated (used for transforming [`Insane`] to [`Sane`])
|
|
pub trait Validate: Sized {
|
|
fn validate(&self, builder: ResultBuilder) -> ResultBuilder;
|
|
}
|
|
|
|
/// A field in a struct that can be validated and that is reused in multiple places.
|
|
/// Primarily useful for newtype structs.
|
|
pub trait ValidateField<'a>: Sized {
|
|
fn validate_field(builder: FieldResultBuilder<'a, Self>) -> FieldResultBuilder<'a, Self>;
|
|
}
|
|
|
|
/// Validation error
|
|
#[derive(Debug)]
|
|
pub struct Error {
|
|
pub errors: Vec<(String, String)>,
|
|
}
|
|
|
|
/// Little helper to make tracking validation errors more convenient
|
|
pub struct ResultBuilder {
|
|
errors: Vec<(String, String)>,
|
|
}
|
|
|
|
pub struct FieldResultBuilder<'a, T> {
|
|
val: &'a T,
|
|
errors: Vec<String>,
|
|
}
|
|
|
|
impl<T> Sane<T> {
|
|
pub fn inner(self) -> T {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for Insane<T> {
|
|
fn from(t: T) -> Insane<T> {
|
|
Insane(t)
|
|
}
|
|
}
|
|
|
|
impl<T> Insane<T> {
|
|
pub fn inner(self) -> T {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl<T> TryFrom<Insane<T>> for Sane<T>
|
|
where
|
|
T: Validate,
|
|
{
|
|
type Error = crate::core::Error;
|
|
|
|
fn try_from(t: Insane<T>) -> Result<Sane<T>> {
|
|
Ok(Sane(
|
|
(&t.0).validate(ResultBuilder::new()).result(t.inner())?,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl ResultBuilder {
|
|
pub fn new() -> ResultBuilder {
|
|
ResultBuilder {
|
|
errors: Vec::with_capacity(0),
|
|
}
|
|
}
|
|
|
|
pub fn check<T, N, M, F>(mut self, val: &T, field_name: N, error_msg: M, check: F) -> Self
|
|
where
|
|
N: Into<String>,
|
|
M: Into<String>,
|
|
F: FnOnce(&T) -> bool,
|
|
{
|
|
if !check(val) {
|
|
self.errors.push((field_name.into(), error_msg.into()));
|
|
}
|
|
self
|
|
}
|
|
|
|
/// Begin sanitization on a single field
|
|
pub fn field<'a, T, N, F>(mut self, val: &'a T, name: N, check: F) -> Self
|
|
where
|
|
N: Into<&'a str>,
|
|
F: FnOnce(FieldResultBuilder<T>) -> FieldResultBuilder<T>,
|
|
{
|
|
let builder = check(FieldResultBuilder {
|
|
val,
|
|
errors: Vec::with_capacity(0),
|
|
});
|
|
builder.append_errors(name.into(), &mut self.errors);
|
|
self
|
|
}
|
|
|
|
/// Validate a field that implements [`ValidateField`]
|
|
pub fn field_auto<'a, T, N>(mut self, val: &'a T, name: N) -> Self
|
|
where
|
|
T: ValidateField<'a>,
|
|
N: Into<&'a str>,
|
|
{
|
|
let builder = T::validate_field(FieldResultBuilder {
|
|
val,
|
|
errors: Vec::with_capacity(0),
|
|
});
|
|
builder.append_errors(name.into(), &mut self.errors);
|
|
self
|
|
}
|
|
|
|
/// Obtain the final result of the validation
|
|
fn result<T>(self, success_val: T) -> Result<T> {
|
|
if self.errors.is_empty() {
|
|
Ok(success_val)
|
|
} else {
|
|
Err(Error {
|
|
errors: self.errors,
|
|
}
|
|
.into())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T> FieldResultBuilder<'a, T> {
|
|
pub fn check<M, F>(mut self, err_msg: M, check: F) -> Self
|
|
where
|
|
M: Into<String>,
|
|
F: FnOnce(&T) -> bool,
|
|
{
|
|
let err_msg = err_msg.into();
|
|
if !check(self.val) {
|
|
self.errors.push(err_msg);
|
|
}
|
|
self
|
|
}
|
|
|
|
fn append_errors(self, field_name: &str, errors: &mut Vec<(String, String)>) {
|
|
errors.extend(
|
|
self.errors
|
|
.into_iter()
|
|
.map(|msg| (String::from(field_name), msg)),
|
|
);
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Error {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
for (field, msg) in &self.errors {
|
|
writeln!(f, "in field \"{}\": {}", field, msg)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|