Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct SignupData {
first_name: String,
#[validate(range(min = 18, max = 20))]
age: u32,
#[validate(range(exclusive_min = 0.0, max = 100.0))]
height: f32,
}

fn validate_unique_username(username: &str) -> Result<(), ValidationError> {
Expand Down Expand Up @@ -197,7 +199,8 @@ const MAX_CONST: u64 = 10;
```

### range
Tests whether a number is in the given range. `range` takes 1 or 2 arguments `min` and `max` that can be a number or a value path.
Tests whether a number is in the given range. `range` takes 1 or 2 arguments, and they can be normal (`min` and `max`) or exclusive (`exclusive_min`, `exclusive_max`, unreachable limits).
These can be a number or a value path.

Examples:

Expand All @@ -212,6 +215,8 @@ const MIN_CONSTANT: i32 = 0;
#[validate(range(max = 10.8))]
#[validate(range(min = "MAX_CONSTANT"))]
#[validate(range(min = "crate::MAX_CONSTANT"))]
#[validate(range(exclusive_min = 0.0, max = 100.0))]
#[validate(range(exclusive_max = 10))]
```

### must_match
Expand Down
2 changes: 0 additions & 2 deletions validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ serde_derive = "1"
serde_json = "1"
validator_derive = { version = "0.16", path = "../validator_derive", optional = true }
card-validate = { version = "2.2", optional = true }
phonenumber = { version = "0.3", optional = true }
unic-ucd-common = { version = "0.9", optional = true }
indexmap = {version = "1", features = ["serde-1"], optional = true }


[features]
phone = ["phonenumber", "validator_derive/phone"]
card = ["card-validate", "validator_derive/card"]
unic = ["unic-ucd-common", "validator_derive/unic"]
derive = ["validator_derive"]
17 changes: 10 additions & 7 deletions validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,24 @@ mod validation;
pub use validation::cards::validate_credit_card;
pub use validation::contains::validate_contains;
pub use validation::does_not_contain::validate_does_not_contain;
pub use validation::email::validate_email;
pub use validation::email::{validate_email, ValidateEmail};
pub use validation::ip::{validate_ip, validate_ip_v4, validate_ip_v6};
pub use validation::length::validate_length;
pub use validation::length::{validate_length, ValidateLength};
pub use validation::must_match::validate_must_match;
#[cfg(feature = "unic")]
pub use validation::non_control_character::validate_non_control_character;
#[cfg(feature = "phone")]
pub use validation::phone::validate_phone;
pub use validation::range::validate_range;
pub use validation::range::{validate_range, ValidateRange};

pub use validation::required::validate_required;
pub use validation::urls::validate_url;
pub use validation::required::{validate_required, ValidateRequired};
pub use validation::urls::{validate_url, ValidateUrl};

pub use traits::{Contains, HasLen, Validate, ValidateArgs};
pub use types::{ValidationError, ValidationErrors, ValidationErrorsKind};
pub use traits::{Constraints, Contains, HasLen, Validate, ValidateArgs};
pub use types::{
LengthConstraint, ValidationConstraint, ValidationConstraints, ValidationConstraintsKind,
ValidationError, ValidationErrors, ValidationErrorsKind,
};

#[cfg(feature = "derive")]
pub use validator_derive::Validate;
118 changes: 117 additions & 1 deletion validator/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
#[cfg(feature = "indexmap")]
use indexmap::{IndexMap, IndexSet};

use crate::types::ValidationErrors;
use crate::types::{ValidationConstraints, ValidationErrors};

/// Trait to implement if one wants to make the `length` validator
/// work for more types
Expand Down Expand Up @@ -210,3 +210,119 @@ pub trait ValidateArgs<'v_a> {

fn validate_args(&self, args: Self::Args) -> Result<(), ValidationErrors>;
}

/// Provides an associated function that returns all of the validator constraints applied to its
/// fields.
///
/// This trait is implemented by deriving `Validate`.
pub trait Constraints {
fn constraints() -> ValidationConstraints;

fn is_collection() -> bool {
false
}
}

impl<T: Constraints> Constraints for &T {
fn constraints() -> ValidationConstraints {
T::constraints()
}
}

impl<T: Constraints> Constraints for &[T] {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<T: Constraints, const N: usize> Constraints for [T; N] {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<T: Constraints> Constraints for Option<T> {
fn constraints() -> ValidationConstraints {
T::constraints()
}
}

impl<T: Constraints> Constraints for Vec<T> {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<K, V: Constraints, S> Constraints for HashMap<K, V, S> {
fn constraints() -> ValidationConstraints {
V::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<T: Constraints, S> Constraints for HashSet<T, S> {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<K, V: Constraints> Constraints for BTreeMap<K, V> {
fn constraints() -> ValidationConstraints {
V::constraints()
}

fn is_collection() -> bool {
true
}
}

impl<T: Constraints> Constraints for BTreeSet<T> {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}

#[cfg(feature = "indexmap")]
impl<K, V: Constraints> Constraints for IndexMap<K, V> {
fn constraints() -> ValidationConstraints {
V::constraints()
}

fn is_collection() -> bool {
true
}
}

#[cfg(feature = "indexmap")]
impl<T: Constraints> Constraints for IndexSet<T> {
fn constraints() -> ValidationConstraints {
T::constraints()
}

fn is_collection() -> bool {
true
}
}
140 changes: 140 additions & 0 deletions validator/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,143 @@ impl std::error::Error for ValidationErrors {
None
}
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum LengthConstraint {
Range { min: Option<u64>, max: Option<u64> },
Equal(u64),
}

#[derive(Debug, Clone, PartialEq, Serialize)]
#[non_exhaustive]
pub enum ValidationConstraint {
Email {
code: &'static str,
},
Url {
code: &'static str,
},
Custom {
function: &'static str,
code: &'static str,
},
MustMatch {
other_field: &'static str,
code: &'static str,
},
Contains {
needle: &'static str,
code: &'static str,
},
DoesNotContain {
needle: &'static str,
code: &'static str,
},
Regex {
name: &'static str,
code: &'static str,
},
Range {
min: Option<f64>,
max: Option<f64>,
code: &'static str,
},
Length {
length: LengthConstraint,
code: &'static str,
},
#[cfg(feature = "card")]
CreditCard {
code: &'static str,
},
Nested,
#[cfg(feature = "unic")]
NonControlCharacter {
code: &'static str,
},
Required {
code: &'static str,
},
RequiredNested {
code: &'static str,
},
}

impl ValidationConstraint {
pub fn code(&self) -> &'static str {
match *self {
Self::Email { code, .. } => code,
Self::Url { code, .. } => code,
Self::Custom { code, .. } => code,
Self::MustMatch { code, .. } => code,
Self::Contains { code, .. } => code,
Self::DoesNotContain { code, .. } => code,
Self::Regex { code, .. } => code,
Self::Range { code, .. } => code,
Self::Length { code, .. } => code,
#[cfg(feature = "card")]
Self::CreditCard { code, .. } => code,
Self::Nested => "nested",
#[cfg(feature = "unic")]
Self::NonControlCharacter { code, .. } => code,
Self::Required { code, .. } => code,
Self::RequiredNested { code, .. } => code,
}
}
}

#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
pub enum ValidationConstraintsKind {
Struct(Box<ValidationConstraints>),
Collection(Box<ValidationConstraints>),
Field(Vec<ValidationConstraint>),
}

#[derive(Default, Debug, Clone, PartialEq, Serialize)]
pub struct ValidationConstraints(pub HashMap<&'static str, Vec<ValidationConstraintsKind>>);

impl ValidationConstraints {
pub fn new() -> Self {
Self(HashMap::new())
}

pub fn merge(
parent: &mut ValidationConstraints,
field: &'static str,
child: ValidationConstraints,
is_collection: bool,
) {
if is_collection {
parent.add_nested(field, ValidationConstraintsKind::Collection(Box::new(child)));
} else {
parent.add_nested(field, ValidationConstraintsKind::Struct(Box::new(child)));
}
}

pub fn add(&mut self, field: &'static str, constraint: ValidationConstraint) {
let entry = self.0.entry(field).or_insert_with(Vec::new);

let kind = entry.iter_mut().find_map(|kind| match kind {
ValidationConstraintsKind::Field(field) => Some(field),
_ => None,
});
match kind {
Some(field) => {
field.push(constraint);
}
None => {
entry.push(ValidationConstraintsKind::Field(vec![constraint]));
}
};
}

#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

fn add_nested(&mut self, field: &'static str, constraints: ValidationConstraintsKind) {
self.0.entry(field).or_insert_with(Vec::new).push(constraints);
}
}
Loading