use crate::property::{
HasCharCount, HasCheckedValue, HasDecimalDigits, HasEmptyValue, HasLength, HasMember,
HasZeroValue,
};
use crate::{
invalid_optional_value, invalid_relation, invalid_value, FieldName, RelatedFields, Validate,
Validation, Value,
};
use std::convert::TryFrom;
pub const INVALID_ASSERT_TRUE: &str = "invalid-assert-true";
pub const INVALID_ASSERT_FALSE: &str = "invalid-assert-false";
pub const INVALID_NOT_EMPTY: &str = "invalid-not-empty";
pub const INVALID_LENGTH_EXACT: &str = "invalid-length-exact";
pub const INVALID_LENGTH_MAX: &str = "invalid-length-max";
pub const INVALID_LENGTH_MIN: &str = "invalid-length-min";
pub const INVALID_CHAR_COUNT_EXACT: &str = "invalid-char-count-exact";
pub const INVALID_CHAR_COUNT_MAX: &str = "invalid-char-count-max";
pub const INVALID_CHAR_COUNT_MIN: &str = "invalid-char-count-min";
pub const INVALID_BOUND_EXACT: &str = "invalid-bound-exact";
pub const INVALID_BOUND_CLOSED_MAX: &str = "invalid-bound-closed-max";
pub const INVALID_BOUND_CLOSED_MIN: &str = "invalid-bound-closed-min";
pub const INVALID_BOUND_OPEN_MAX: &str = "invalid-bound-open-max";
pub const INVALID_BOUND_OPEN_MIN: &str = "invalid-bound-open-min";
pub const INVALID_NON_ZERO: &str = "invalid-non-zero";
pub const INVALID_DIGITS_INTEGER: &str = "invalid-digits-integer";
pub const INVALID_DIGITS_FRACTION: &str = "invalid-digits-fraction";
pub const INVALID_CONTAINS_ELEMENT: &str = "invalid-contains-element";
pub const INVALID_MUST_MATCH: &str = "invalid-must-match";
pub const INVALID_MUST_DEFINE_RANGE_INCLUSIVE: &str = "invalid-must-define-range-inclusive";
pub const INVALID_MUST_DEFINE_RANGE_EXCLUSIVE: &str = "invalid-must-define-range-exclusive";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssertTrue;
impl<T> Validate<AssertTrue, FieldName> for T
where
T: HasCheckedValue,
{
fn validate(
self,
name: impl Into<FieldName>,
_constraint: &AssertTrue,
) -> Validation<AssertTrue, Self> {
if self.is_checked_value() {
Validation::success(self)
} else {
Validation::failure(vec![invalid_value(INVALID_ASSERT_TRUE, name, false, true)])
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssertFalse;
impl<T> Validate<AssertFalse, FieldName> for T
where
T: HasCheckedValue,
{
fn validate(
self,
name: impl Into<FieldName>,
_constraint: &AssertFalse,
) -> Validation<AssertFalse, Self> {
if self.is_checked_value() {
Validation::failure(vec![invalid_value(INVALID_ASSERT_FALSE, name, true, false)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NotEmpty;
impl<T> Validate<NotEmpty, FieldName> for T
where
T: HasEmptyValue,
{
fn validate(
self,
name: impl Into<FieldName>,
_constraint: &NotEmpty,
) -> Validation<NotEmpty, Self> {
if self.is_empty_value() {
Validation::failure(vec![invalid_optional_value(
INVALID_NOT_EMPTY,
name,
None,
None,
)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Length {
Max(usize),
Min(usize),
MinMax(usize, usize),
Exact(usize),
}
impl<T> Validate<Length, FieldName> for T
where
T: HasLength,
{
fn validate(self, name: impl Into<FieldName>, constraint: &Length) -> Validation<Length, Self> {
let length = self.length();
if let Some((code, expected)) = match *constraint {
Length::Max(max) => {
if length > max {
Some((INVALID_LENGTH_MAX, max))
} else {
None
}
}
Length::Min(min) => {
if length < min {
Some((INVALID_LENGTH_MIN, min))
} else {
None
}
}
Length::MinMax(min, max) => {
if length < min {
Some((INVALID_LENGTH_MIN, min))
} else if length > max {
Some((INVALID_LENGTH_MAX, max))
} else {
None
}
}
Length::Exact(exact_len) => {
if length != exact_len {
Some((INVALID_LENGTH_EXACT, exact_len))
} else {
None
}
}
} {
let actual = Value::try_from(length).ok();
let expected = Value::try_from(expected).ok();
Validation::failure(vec![invalid_optional_value(code, name, actual, expected)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CharCount {
Max(usize),
Min(usize),
MinMax(usize, usize),
Exact(usize),
}
impl<T> Validate<CharCount, FieldName> for T
where
T: HasCharCount,
{
fn validate(
self,
name: impl Into<FieldName>,
constraint: &CharCount,
) -> Validation<CharCount, Self> {
let char_count = self.char_count();
if let Some((code, expected)) = match *constraint {
CharCount::Max(max) => {
if char_count > max {
Some((INVALID_CHAR_COUNT_MAX, max))
} else {
None
}
}
CharCount::Min(min) => {
if char_count < min {
Some((INVALID_CHAR_COUNT_MIN, min))
} else {
None
}
}
CharCount::MinMax(min, max) => {
if char_count < min {
Some((INVALID_CHAR_COUNT_MIN, min))
} else if char_count > max {
Some((INVALID_CHAR_COUNT_MAX, max))
} else {
None
}
}
CharCount::Exact(exact_val) => {
if char_count != exact_val {
Some((INVALID_CHAR_COUNT_EXACT, exact_val))
} else {
None
}
}
} {
let actual = Value::try_from(char_count).ok();
let expected = Value::try_from(expected).ok();
Validation::failure(vec![invalid_optional_value(code, name, actual, expected)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bound<T> {
ClosedRange(T, T),
ClosedOpenRange(T, T),
OpenClosedRange(T, T),
OpenRange(T, T),
Exact(T),
}
impl<T> Validate<Bound<T>, FieldName> for T
where
T: PartialOrd + Clone + Into<Value>,
{
fn validate(
self,
name: impl Into<FieldName>,
constraint: &Bound<T>,
) -> Validation<Bound<T>, Self> {
if let Some((code, expected)) = match constraint {
Bound::ClosedRange(min, max) => {
if self < *min {
Some((INVALID_BOUND_CLOSED_MIN, min.clone()))
} else if self > *max {
Some((INVALID_BOUND_CLOSED_MAX, max.clone()))
} else {
None
}
}
Bound::ClosedOpenRange(min, max) => {
if self < *min {
Some((INVALID_BOUND_CLOSED_MIN, min.clone()))
} else if self >= *max {
Some((INVALID_BOUND_OPEN_MAX, max.clone()))
} else {
None
}
}
Bound::OpenClosedRange(min, max) => {
if self <= *min {
Some((INVALID_BOUND_OPEN_MIN, min.clone()))
} else if self > *max {
Some((INVALID_BOUND_CLOSED_MAX, max.clone()))
} else {
None
}
}
Bound::OpenRange(min, max) => {
if self <= *min {
Some((INVALID_BOUND_OPEN_MIN, min.clone()))
} else if self >= *max {
Some((INVALID_BOUND_OPEN_MAX, max.clone()))
} else {
None
}
}
Bound::Exact(bound) => {
if *bound != self {
Some((INVALID_BOUND_EXACT, bound.clone()))
} else {
None
}
}
} {
Validation::failure(vec![invalid_value(code, name, self, expected)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonZero;
impl<T> Validate<NonZero, FieldName> for T
where
T: HasZeroValue + Into<Value>,
{
fn validate(
self,
name: impl Into<FieldName>,
_constraint: &NonZero,
) -> Validation<NonZero, Self> {
if self.is_zero_value() {
Validation::failure(vec![invalid_optional_value(
INVALID_NON_ZERO,
name,
Some(self.into()),
None,
)])
} else {
Validation::success(self)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Digits {
pub integer: u64,
pub fraction: u64,
}
impl<T> Validate<Digits, FieldName> for T
where
T: HasDecimalDigits,
{
fn validate(self, name: impl Into<FieldName>, constraint: &Digits) -> Validation<Digits, Self> {
let integer = self.integer_digits();
let fraction = self.fraction_digits();
if integer <= constraint.integer {
if fraction <= constraint.fraction {
Validation::success(self)
} else {
Validation::failure(vec![invalid_value(
INVALID_DIGITS_FRACTION,
name,
fraction,
constraint.fraction,
)])
}
} else if fraction <= constraint.fraction {
Validation::failure(vec![invalid_value(
INVALID_DIGITS_INTEGER,
name,
integer,
constraint.integer,
)])
} else {
let name = name.into();
Validation::failure(vec![
invalid_value(
INVALID_DIGITS_INTEGER,
name.clone(),
integer,
constraint.integer,
),
invalid_value(INVALID_DIGITS_FRACTION, name, fraction, constraint.fraction),
])
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Contains<'a, A>(pub &'a A);
impl<'a, T, A> Validate<Contains<'a, A>, FieldName> for T
where
T: HasMember<A> + Into<Value>,
A: Clone + Into<Value>,
{
fn validate(
self,
name: impl Into<FieldName>,
constraint: &Contains<'a, A>,
) -> Validation<Contains<'a, A>, Self> {
if self.has_member(&constraint.0) {
Validation::success(self)
} else {
Validation::failure(vec![invalid_value(
INVALID_CONTAINS_ELEMENT,
name,
self,
constraint.0.clone(),
)])
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MustMatch;
impl<T> Validate<MustMatch, RelatedFields> for (T, T)
where
T: PartialEq + Into<Value>,
{
fn validate(
self,
fields: impl Into<RelatedFields>,
_constraint: &MustMatch,
) -> Validation<MustMatch, Self> {
let RelatedFields(name1, name2) = fields.into();
if self.0 == self.1 {
Validation::success(self)
} else {
Validation::failure(vec![invalid_relation(
INVALID_MUST_MATCH,
name1,
self.0,
name2,
self.1,
)])
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MustDefineRange {
Inclusive,
Exclusive,
}
impl<T> Validate<MustDefineRange, RelatedFields> for (T, T)
where
T: PartialOrd + Into<Value>,
{
fn validate(
self,
fields: impl Into<RelatedFields>,
constraint: &MustDefineRange,
) -> Validation<MustDefineRange, Self> {
let RelatedFields(name1, name2) = fields.into();
match *constraint {
MustDefineRange::Inclusive => {
if self.0 <= self.1 {
Validation::success(self)
} else {
Validation::failure(vec![invalid_relation(
INVALID_MUST_DEFINE_RANGE_INCLUSIVE,
name1,
self.0,
name2,
self.1,
)])
}
}
MustDefineRange::Exclusive => {
if self.0 < self.1 {
Validation::success(self)
} else {
Validation::failure(vec![invalid_relation(
INVALID_MUST_DEFINE_RANGE_EXCLUSIVE,
name1,
self.0,
name2,
self.1,
)])
}
}
}
}
}
#[cfg(feature = "regex")]
pub use with_regex::*;
#[cfg(feature = "regex")]
mod with_regex {
use crate::{invalid_value, FieldName, Validate, Validation};
use regex::Regex;
pub const INVALID_PATTERN: &str = "invalid-pattern";
#[derive(Debug, Clone)]
pub struct Pattern(pub Regex);
impl Validate<Pattern, FieldName> for String {
fn validate(
self,
name: impl Into<FieldName>,
constraint: &Pattern,
) -> Validation<Pattern, Self> {
if constraint.0.is_match(&self) {
Validation::success(self)
} else {
Validation::failure(vec![invalid_value(
INVALID_PATTERN,
name,
self,
constraint.0.to_string(),
)])
}
}
}
}
#[cfg(test)]
mod tests;