aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Prouillet2019-10-20 18:18:41 +0200
committerGitHub2019-10-20 18:18:41 +0200
commit97a11c88f26b00e897d21709d9576ad3cea6f220 (patch)
tree7469738d72627423c5b99df7dfae099951f4d322
parent87cf7cdac24dd0d4ea83a8a88640da95ae7ac93a (diff)
parent52cd62998b8f45be56512ce1d89aef8bf1a9f670 (diff)
downloadvalidator-97a11c88f26b00e897d21709d9576ad3cea6f220.tar.bz2
Merge pull request #87 from Keats/next
Use Github actions for CI
-rw-r--r--.github/workflows/ci.yml45
-rw-r--r--.travis.yml11
-rw-r--r--README.md17
-rw-r--r--validator/Cargo.toml8
-rw-r--r--validator/src/lib.rs16
-rw-r--r--validator/src/types.rs5
-rw-r--r--validator/src/validation/cards.rs2
-rw-r--r--validator/src/validation/email.rs3
-rw-r--r--validator/src/validation/mod.rs6
-rw-r--r--validator/src/validation/non_control_character.rs46
-rw-r--r--validator_derive/Cargo.toml12
-rw-r--r--validator_derive/src/asserts.rs2
-rw-r--r--validator_derive/src/lib.rs110
-rw-r--r--validator_derive/src/lit.rs7
-rw-r--r--validator_derive/src/quoting.rs31
-rw-r--r--validator_derive/src/validation.rs99
-rw-r--r--validator_derive/tests/non_control.rs75
-rw-r--r--validator_derive/tests/regex.rs8
-rw-r--r--validator_derive/tests/run-pass/custom.rs4
-rw-r--r--validator_derive/tests/run-pass/regex.rs4
20 files changed, 363 insertions, 148 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3212907
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,45 @@
+name: ci
+on: [push, pull_request]
+
+jobs:
+ test_validator:
+ name: test validator
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ build: [pinned, stable, nightly]
+ include:
+ - build: pinned
+ os: ubuntu-18.04
+ rust: 1.33.0
+ - build: stable
+ os: ubuntu-18.04
+ rust: stable
+ - build: nightly
+ os: ubuntu-18.04
+ rust: nightly
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install Rust
+ uses: hecrj/setup-rust-action@v1
+ with:
+ rust-version: ${{ matrix.rust }}
+ - name: Build System Info
+ run: rustc --version
+
+ - name: tests validator with all features
+ run: cd validator && cargo test --all-features
+ - name: tests validator with no features
+ run: cd validator && cargo test --no-default-features
+
+ test_validator_derive:
+ name: test validator_derive
+ runs-on: ubuntu-18.04
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install Rust
+ uses: hecrj/setup-rust-action@v1
+ with:
+ rust-version: nightly
+ - name: tests validator_derive with all features
+ run: cd validator_derive && cargo test --all-features
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e379ead..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: rust
-
-rust:
- - nightly
-
-script:
- - (cd validator && cargo test --all-features)
- - (cd validator_derive && cargo clean && cargo test --all-features)
-
-notifications:
- email: false
diff --git a/README.md b/README.md
index 875f066..9330c6f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Macros 1.1 custom derive to simplify struct validation inspired by [marshmallow](http://marshmallow.readthedocs.io/en/latest/) and
[Django validators](https://docs.djangoproject.com/en/1.10/ref/validators/).
-It requires Rust 1.30.
+It requires Rust 1.33.
A short example:
@@ -255,6 +255,11 @@ Examples:
#[validate]
```
+### non_control_character
+Tests whether the String has any utf-8 control caracters, fails validation if it does.
+To use this validator, you must enable the `unic` feature for the `validator_derive` crate.
+This validator doesn't take any arguments: `#[validate(non_control_character)]`;
+
## Struct level validation
Often, some error validation can only be applied when looking at the full struct, here's how it works here:
@@ -297,6 +302,10 @@ For example, the following attributes all work:
### validator
+#### 0.10.0 (2019/10/18)
+
+- Add `non_control_characters` validation
+
#### 0.9.0 (2019/05/xx)
- `ValidationErrors::errors` and `ValidationErrors::field_errors` now use `&self` instead of `self`
@@ -328,6 +337,12 @@ For example, the following attributes all work:
### validator_derive
+
+#### 0.10.0 (2019/10/18)
+
+- Update syn & quote
+- Move to edition 2018
+
#### 0.9.0 (2019/05/xx)
- Use literals in macros now that it's stable -> bumping minimum Rust version to 1.30
diff --git a/validator/Cargo.toml b/validator/Cargo.toml
index 96e8104..b7e43c0 100644
--- a/validator/Cargo.toml
+++ b/validator/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "validator"
-version = "0.9.0"
+version = "0.10.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com"]
license = "MIT"
description = "Common validation functions (email, url, length, ...) and trait - to be used with `validator_derive`"
@@ -10,16 +10,18 @@ keywords = ["validation", "api", "validator"]
edition = "2018"
[dependencies]
-url = "1"
+url = "2"
regex = "1"
lazy_static = "1"
-idna = "0.1"
+idna = "0.2"
serde = "1"
serde_derive = "1"
serde_json = "1"
card-validate = { version = "2.1", optional = true }
phonenumber = { version = "0.2.0+8.7.0", optional = true }
+unic-ucd-common = { version = "0.9.0", optional = true }
[features]
phone = ["phonenumber"]
card = ["card-validate"]
+unic = ["unic-ucd-common"] \ No newline at end of file
diff --git a/validator/src/lib.rs b/validator/src/lib.rs
index f6aa8f0..5313c41 100644
--- a/validator/src/lib.rs
+++ b/validator/src/lib.rs
@@ -1,17 +1,3 @@
-extern crate regex;
-extern crate url;
-#[macro_use]
-extern crate lazy_static;
-extern crate idna;
-extern crate serde;
-extern crate serde_json;
-#[macro_use]
-extern crate serde_derive;
-#[cfg(feature = "card")]
-extern crate card_validate;
-#[cfg(feature = "phone")]
-pub extern crate phonenumber;
-
mod traits;
mod types;
mod validation;
@@ -23,6 +9,8 @@ pub use validation::email::validate_email;
pub use validation::ip::{validate_ip, validate_ip_v4, validate_ip_v6};
pub use validation::length::validate_length;
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;
diff --git a/validator/src/types.rs b/validator/src/types.rs
index 09e4c7d..5d90d04 100644
--- a/validator/src/types.rs
+++ b/validator/src/types.rs
@@ -3,6 +3,7 @@ use std::collections::{hash_map::Entry::Vacant, BTreeMap, HashMap};
use std::{self, fmt};
use serde::ser::Serialize;
+use serde_derive::{Deserialize, Serialize};
use serde_json::{to_value, Value};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
@@ -32,7 +33,7 @@ impl std::error::Error for ValidationError {
fn description(&self) -> &str {
&self.code
}
- fn cause(&self) -> Option<&std::error::Error> {
+ fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
}
@@ -168,7 +169,7 @@ impl std::error::Error for ValidationErrors {
fn description(&self) -> &str {
"Validation failed"
}
- fn cause(&self) -> Option<&std::error::Error> {
+ fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
}
diff --git a/validator/src/validation/cards.rs b/validator/src/validation/cards.rs
index 8c97694..19d91ab 100644
--- a/validator/src/validation/cards.rs
+++ b/validator/src/validation/cards.rs
@@ -1,6 +1,6 @@
use std::borrow::Cow;
-use crate::card_validate::Validate as CardValidate;
+use card_validate::Validate as CardValidate;
pub fn validate_credit_card<'a, T>(card: T) -> bool
where
diff --git a/validator/src/validation/email.rs b/validator/src/validation/email.rs
index 6ea2fae..7a48f19 100644
--- a/validator/src/validation/email.rs
+++ b/validator/src/validation/email.rs
@@ -1,4 +1,5 @@
use idna::domain_to_ascii;
+use lazy_static::lazy_static;
use regex::Regex;
use std::borrow::Cow;
@@ -120,7 +121,7 @@ mod tests {
];
for (input, expected) in tests {
- println!("{} - {}", input, expected);
+ // println!("{} - {}", input, expected);
assert_eq!(validate_email(input), expected);
}
}
diff --git a/validator/src/validation/mod.rs b/validator/src/validation/mod.rs
index 95d9b79..1675730 100644
--- a/validator/src/validation/mod.rs
+++ b/validator/src/validation/mod.rs
@@ -5,6 +5,8 @@ pub mod email;
pub mod ip;
pub mod length;
pub mod must_match;
+#[cfg(feature = "unic")]
+pub mod non_control_character;
#[cfg(feature = "phone")]
pub mod phone;
pub mod range;
@@ -41,6 +43,8 @@ pub enum Validator {
#[cfg(feature = "phone")]
Phone,
Nested,
+ #[cfg(feature = "unic")]
+ NonControlCharacter,
}
impl Validator {
@@ -59,6 +63,8 @@ impl Validator {
#[cfg(feature = "phone")]
Validator::Phone => "phone",
Validator::Nested => "nested",
+ #[cfg(feature = "unic")]
+ Validator::NonControlCharacter => "non_control_character",
}
}
}
diff --git a/validator/src/validation/non_control_character.rs b/validator/src/validation/non_control_character.rs
new file mode 100644
index 0000000..ec4308c
--- /dev/null
+++ b/validator/src/validation/non_control_character.rs
@@ -0,0 +1,46 @@
+use std::borrow::Cow;
+use unic_ucd_common::control;
+
+pub fn validate_non_control_character<'a, T>(alphabetic: T) -> bool
+where
+ T: Into<Cow<'a, str>> + Clone,
+{
+ alphabetic.into().chars().into_iter().all(|code| !control::is_control(code))
+}
+
+#[cfg(test)]
+mod tests {
+ use std::borrow::Cow;
+
+ use super::validate_non_control_character;
+
+ #[test]
+ fn test_non_control_character() {
+ let tests = vec![
+ ("Himmel", true),
+ ("आकाश", true),
+ ("வானத்தில்", true),
+ ("하늘", true),
+ ("небо", true),
+ ("2H₂ + O₂ ⇌ 2H₂O", true),
+ ("\u{000c}", false),
+ ("\u{009F}", false),
+ ];
+
+ for (input, expected) in tests {
+ assert_eq!(validate_non_control_character(input), expected);
+ }
+ }
+
+ #[test]
+ fn test_non_control_character_cow() {
+ let test: Cow<'static, str> = "आकाश".into();
+ assert_eq!(validate_non_control_character(test), true);
+ let test: Cow<'static, str> = String::from("வானத்தில்").into();
+ assert_eq!(validate_non_control_character(test), true);
+ let test: Cow<'static, str> = "\u{000c}".into();
+ assert_eq!(validate_non_control_character(test), false);
+ let test: Cow<'static, str> = String::from("\u{009F}").into();
+ assert_eq!(validate_non_control_character(test), false);
+ }
+}
diff --git a/validator_derive/Cargo.toml b/validator_derive/Cargo.toml
index e8e50aa..4062170 100644
--- a/validator_derive/Cargo.toml
+++ b/validator_derive/Cargo.toml
@@ -1,12 +1,13 @@
[package]
name = "validator_derive"
-version = "0.9.0"
+version = "0.10.0"
authors = ["Vincent Prouillet <prouillet.vincent@gmail.com"]
license = "MIT"
description = "Macros 1.1 implementation of #[derive(Validate)]"
homepage = "https://github.com/Keats/validator"
repository = "https://github.com/Keats/validator"
keywords = ["validation", "api", "validator"]
+edition = "2018"
[lib]
proc-macro = true
@@ -14,13 +15,14 @@ proc-macro = true
[features]
phone = ["validator/phone"]
card = ["validator/card"]
+unic = ["validator/unic"]
[dependencies]
-syn = { version = "0.15", features = ["extra-traits"] }
-quote = "0.6"
-proc-macro2 = "0.4"
+syn = { version = "1", features = ["extra-traits"] }
+quote = "1"
+proc-macro2 = "1"
if_chain = "1"
-validator = { version = "0.9", path = "../validator"}
+validator = { version = "0.10", path = "../validator"}
regex = "1"
lazy_static = "1"
diff --git a/validator_derive/src/asserts.rs b/validator_derive/src/asserts.rs
index a5892a8..023dd0d 100644
--- a/validator_derive/src/asserts.rs
+++ b/validator_derive/src/asserts.rs
@@ -1,5 +1,7 @@
use regex::Regex;
+use lazy_static::lazy_static;
+
lazy_static! {
pub static ref COW_TYPE: Regex = Regex::new(r"Cow<'[a-z]+,str>").unwrap();
}
diff --git a/validator_derive/src/lib.rs b/validator_derive/src/lib.rs
index f3796b4..6f74202 100644
--- a/validator_derive/src/lib.rs
+++ b/validator_derive/src/lib.rs
@@ -1,21 +1,11 @@
#![recursion_limit = "128"]
-
-#[macro_use]
-extern crate if_chain;
-#[macro_use]
-extern crate lazy_static;
extern crate proc_macro;
-extern crate proc_macro2;
-#[macro_use]
-extern crate quote;
-extern crate regex;
-#[macro_use]
-extern crate syn;
-extern crate validator;
-
-use proc_macro::TokenStream;
+
+use if_chain::if_chain;
+use quote::quote;
use quote::ToTokens;
use std::collections::HashMap;
+use syn::parse_quote;
use validator::Validator;
mod asserts;
@@ -29,7 +19,7 @@ use quoting::{quote_field_validation, quote_schema_validation, FieldQuoter};
use validation::*;
#[proc_macro_derive(Validate, attributes(validate))]
-pub fn derive_validation(input: TokenStream) -> TokenStream {
+pub fn derive_validation(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse(input).unwrap();
impl_validate(&ast).into()
}
@@ -110,11 +100,12 @@ fn find_struct_validation(struct_attrs: &Vec<syn::Attribute>) -> Option<SchemaVa
}
if_chain! {
- if let Some(syn::Meta::List(syn::MetaList { ref nested, .. })) = attr.interpret_meta();
+ if let Ok(syn::Meta::List(syn::MetaList { ref nested, .. })) = attr.parse_meta();
if let syn::NestedMeta::Meta(ref item) = nested[0];
- if let &syn::Meta::List(syn::MetaList { ref ident, ref nested, .. }) = item;
+ if let &syn::Meta::List(syn::MetaList { ref path, ref nested, .. }) = item;
then {
+ let ident = path.get_ident().unwrap();
if ident != "schema" {
error("Only `schema` is allowed as validator on a struct")
}
@@ -127,9 +118,10 @@ fn find_struct_validation(struct_attrs: &Vec<syn::Attribute>) -> Option<SchemaVa
for arg in nested {
if_chain! {
if let syn::NestedMeta::Meta(ref item) = *arg;
- if let syn::Meta::NameValue(syn::MetaNameValue { ref ident, ref lit, .. }) = *item;
+ if let syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) = *item;
then {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"function" => {
function = match lit_to_string(lit) {
@@ -251,8 +243,8 @@ fn find_validators_for_field(
has_validate = true;
}
- match attr.interpret_meta() {
- Some(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
+ match attr.parse_meta() {
+ Ok(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
let meta_items = nested.iter().collect();
// original name before serde rename
if attr.path == parse_quote!(serde) {
@@ -266,32 +258,43 @@ fn find_validators_for_field(
for meta_item in meta_items {
match *meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
- // email, url, phone
- syn::Meta::Word(ref name) => match name.to_string().as_ref() {
- "email" => {
- assert_string_type("email", field_type);
- validators.push(FieldValidation::new(Validator::Email));
- }
- "url" => {
- assert_string_type("url", field_type);
- validators.push(FieldValidation::new(Validator::Url));
- }
- #[cfg(feature = "phone")]
- "phone" => {
- assert_string_type("phone", field_type);
- validators.push(FieldValidation::new(Validator::Phone));
- }
- #[cfg(feature = "card")]
- "credit_card" => {
- assert_string_type("credit_card", field_type);
- validators.push(FieldValidation::new(Validator::CreditCard));
+ // email, url, phone, credit_card, non_control_character
+ syn::Meta::Path(ref name) => {
+ match name.get_ident().unwrap().to_string().as_ref() {
+ "email" => {
+ assert_string_type("email", field_type);
+ validators.push(FieldValidation::new(Validator::Email));
+ }
+ "url" => {
+ assert_string_type("url", field_type);
+ validators.push(FieldValidation::new(Validator::Url));
+ }
+ #[cfg(feature = "phone")]
+ "phone" => {
+ assert_string_type("phone", field_type);
+ validators.push(FieldValidation::new(Validator::Phone));
+ }
+ #[cfg(feature = "card")]
+ "credit_card" => {
+ assert_string_type("credit_card", field_type);
+ validators
+ .push(FieldValidation::new(Validator::CreditCard));
+ }
+ #[cfg(feature = "unic")]
+ "non_control_character" => {
+ assert_string_type("non_control_character", field_type);
+ validators.push(FieldValidation::new(
+ Validator::NonControlCharacter,
+ ));
+ }
+ _ => panic!("Unexpected validator: {:?}", name.get_ident()),
}
- _ => panic!("Unexpected validator: {}", name),
- },
+ }
// custom, contains, must_match, regex
syn::Meta::NameValue(syn::MetaNameValue {
- ref ident, ref lit, ..
+ ref path, ref lit, ..
}) => {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"custom" => {
match lit_to_string(lit) {
@@ -324,8 +327,9 @@ fn find_validators_for_field(
};
}
// Validators with several args
- syn::Meta::List(syn::MetaList { ref ident, ref nested, .. }) => {
+ syn::Meta::List(syn::MetaList { ref path, ref nested, .. }) => {
let meta_items = nested.iter().cloned().collect();
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"length" => {
assert_has_len(rust_ident.clone(), field_type);
@@ -341,7 +345,11 @@ fn find_validators_for_field(
&meta_items,
));
}
- "email" | "url" | "phone" | "credit_card" => {
+ "email"
+ | "url"
+ | "phone"
+ | "credit_card"
+ | "non_control_character" => {
validators.push(extract_argless_validation(
ident.to_string(),
rust_ident.clone(),
@@ -396,10 +404,11 @@ fn find_validators_for_field(
};
}
}
- Some(syn::Meta::Word(_)) => validators.push(FieldValidation::new(Validator::Nested)),
- _ => unreachable!(
- "Got something other than a list of attributes while checking field `{}`",
- field_ident
+ Ok(syn::Meta::Path(_)) => validators.push(FieldValidation::new(Validator::Nested)),
+ Ok(syn::Meta::NameValue(_)) => panic!("Unexpected name=value argument"),
+ Err(e) => unreachable!(
+ "Got something other than a list of attributes while checking field `{}`: {:?}",
+ field_ident, e
),
}
}
@@ -422,8 +431,9 @@ fn find_original_field_name(meta_items: &Vec<&syn::NestedMeta>) -> Option<String
for meta_item in meta_items {
match **meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
- syn::Meta::Word(_) => continue,
- syn::Meta::NameValue(syn::MetaNameValue { ref ident, ref lit, .. }) => {
+ syn::Meta::Path(_) => continue,
+ syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) => {
+ let ident = path.get_ident().unwrap();
if ident == "rename" {
original_name = Some(lit_to_string(lit).unwrap());
}
diff --git a/validator_derive/src/lit.rs b/validator_derive/src/lit.rs
index 41deffc..f11cdd6 100644
--- a/validator_derive/src/lit.rs
+++ b/validator_derive/src/lit.rs
@@ -1,4 +1,5 @@
use proc_macro2;
+use quote::quote;
use syn;
pub fn lit_to_string(lit: &syn::Lit) -> Option<String> {
@@ -10,15 +11,15 @@ pub fn lit_to_string(lit: &syn::Lit) -> Option<String> {
pub fn lit_to_int(lit: &syn::Lit) -> Option<u64> {
match *lit {
- syn::Lit::Int(ref s) => Some(s.value()),
+ syn::Lit::Int(ref s) => Some(s.base10_parse().unwrap()),
_ => None,
}
}
pub fn lit_to_float(lit: &syn::Lit) -> Option<f64> {
match *lit {
- syn::Lit::Float(ref s) => Some(s.value()),
- syn::Lit::Int(ref s) => Some(s.value() as f64),
+ syn::Lit::Float(ref s) => Some(s.base10_parse::<f64>().unwrap()),
+ syn::Lit::Int(ref s) => Some(s.base10_parse::<f64>().unwrap()),
_ => None,
}
}
diff --git a/validator_derive/src/quoting.rs b/validator_derive/src/quoting.rs
index b94962c..dc466ac 100644
--- a/validator_derive/src/quoting.rs
+++ b/validator_derive/src/quoting.rs
@@ -1,10 +1,11 @@
use proc_macro2::{self, Span};
+use quote::quote;
use syn;
use validator::Validator;
-use asserts::{COW_TYPE, NUMBER_TYPES};
-use lit::{option_f64_to_tokens, option_u64_to_tokens};
-use validation::{FieldValidation, SchemaValidation};
+use crate::asserts::{COW_TYPE, NUMBER_TYPES};
+use crate::lit::{option_f64_to_tokens, option_u64_to_tokens};
+use crate::validation::{FieldValidation, SchemaValidation};
/// Pass around all the information needed for creating a validation
#[derive(Debug)]
@@ -258,6 +259,26 @@ pub fn quote_phone_validation(
field_quoter.wrap_if_option(quoted)
}
+#[cfg(feature = "unic")]
+pub fn quote_non_control_character_validation(
+ field_quoter: &FieldQuoter,
+ validation: &FieldValidation,
+) -> proc_macro2::TokenStream {
+ let field_name = &field_quoter.name;
+ let validator_param = field_quoter.quote_validator_param();
+
+ let quoted_error = quote_error(&validation);
+ let quoted = quote!(
+ if !::validator::validate_non_control_character(#validator_param) {
+ #quoted_error
+ err.add_param(::std::borrow::Cow::from("value"), &#validator_param);
+ errors.add(#field_name, err);
+ }
+ );
+
+ field_quoter.wrap_if_option(quoted)
+}
+
pub fn quote_url_validation(
field_quoter: &FieldQuoter,
validation: &FieldValidation,
@@ -440,6 +461,10 @@ pub fn quote_field_validation(
#[cfg(feature = "phone")]
Validator::Phone => validations.push(quote_phone_validation(&field_quoter, validation)),
Validator::Nested => nested_validations.push(quote_nested_validation(&field_quoter)),
+ #[cfg(feature = "unic")]
+ Validator::NonControlCharacter => {
+ validations.push(quote_non_control_character_validation(&field_quoter, validation))
+ }
}
}
diff --git a/validator_derive/src/validation.rs b/validator_derive/src/validation.rs
index 5f42d7b..f0657ef 100644
--- a/validator_derive/src/validation.rs
+++ b/validator_derive/src/validation.rs
@@ -2,7 +2,7 @@ use syn;
use validator::Validator;
-use lit::*;
+use crate::lit::*;
#[derive(Debug)]
pub struct SchemaValidation {
@@ -41,32 +41,33 @@ pub fn extract_length_validation(
for meta_item in meta_items {
if let syn::NestedMeta::Meta(ref item) = *meta_item {
- if let syn::Meta::NameValue(syn::MetaNameValue { ref ident, ref lit, .. }) = *item {
+ if let syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) = *item {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
- "message" | "code" => continue,
- "min" => {
- min = match lit_to_int(lit) {
- Some(s) => Some(s),
- None => error("invalid argument type for `min` of `length` validator: only integers are allowed"),
- };
- },
- "max" => {
- max = match lit_to_int(lit) {
- Some(s) => Some(s),
- None => error("invalid argument type for `max` of `length` validator: only integers are allowed"),
- };
- },
- "equal" => {
- equal = match lit_to_int(lit) {
- Some(s) => Some(s),
- None => error("invalid argument type for `equal` of `length` validator: only integers are allowed"),
- };
- },
- v => error(&format!(
- "unknown argument `{}` for validator `length` (it only has `min`, `max`, `equal`)",
- v
- ))
- }
+ "message" | "code" => continue,
+ "min" => {
+ min = match lit_to_int(lit) {
+ Some(s) => Some(s),
+ None => error("invalid argument type for `min` of `length` validator: only integers are allowed"),
+ };
+ },
+ "max" => {
+ max = match lit_to_int(lit) {
+ Some(s) => Some(s),
+ None => error("invalid argument type for `max` of `length` validator: only integers are allowed"),
+ };
+ },
+ "equal" => {
+ equal = match lit_to_int(lit) {
+ Some(s) => Some(s),
+ None => error("invalid argument type for `equal` of `length` validator: only integers are allowed"),
+ };
+ },
+ v => error(&format!(
+ "unknown argument `{}` for validator `length` (it only has `min`, `max`, `equal`)",
+ v
+ ))
+ }
} else {
panic!(
"unexpected item {:?} while parsing `length` validator of field {}",
@@ -107,7 +108,8 @@ pub fn extract_range_validation(
for meta_item in meta_items {
match *meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
- syn::Meta::NameValue(syn::MetaNameValue { ref ident, ref lit, .. }) => {
+ syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) => {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"message" | "code" => continue,
"min" => {
@@ -123,9 +125,9 @@ pub fn extract_range_validation(
};
}
v => error(&format!(
- "unknown argument `{}` for validator `range` (it only has `min`, `max`)",
- v
- )),
+ "unknown argument `{}` for validator `range` (it only has `min`, `max`)",
+ v
+ )),
}
}
_ => panic!("unexpected item {:?} while parsing `range` validator", item),
@@ -146,7 +148,7 @@ pub fn extract_range_validation(
}
}
-/// Extract url/email/phone field validation with a code or a message
+/// Extract url/email/phone/non_control_character field validation with a code or a message
pub fn extract_argless_validation(
validator_name: String,
field: String,
@@ -157,7 +159,8 @@ pub fn extract_argless_validation(
for meta_item in meta_items {
match *meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
- syn::Meta::NameValue(syn::MetaNameValue { ref ident, .. }) => {
+ syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"message" | "code" => continue,
v => panic!(
@@ -178,6 +181,8 @@ pub fn extract_argless_validation(
"credit_card" => Validator::CreditCard,
#[cfg(feature = "phone")]
"phone" => Validator::Phone,
+ #[cfg(feature = "unic")]
+ "non_control_character" => Validator::NonControlCharacter,
_ => Validator::Url,
};
@@ -201,7 +206,8 @@ pub fn extract_one_arg_validation(
for meta_item in meta_items {
match *meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
- syn::Meta::NameValue(syn::MetaNameValue { ref ident, ref lit, .. }) => {
+ syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. }) => {
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"message" | "code" => continue,
v if v == val_name => {
@@ -257,29 +263,30 @@ fn extract_message_and_code(
for meta_item in meta_items {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
- ref ident,
+ ref path,
ref lit,
..
})) = *meta_item
{
+ let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"code" => {
code = match lit_to_string(lit) {
- Some(s) => Some(s),
- None => panic!(
- "Invalid argument type for `code` for validator `{}` on field `{}`: only a string is allowed",
- validator_name, field
- ),
- };
+ Some(s) => Some(s),
+ None => panic!(
+ "Invalid argument type for `code` for validator `{}` on field `{}`: only a string is allowed",
+ validator_name, field
+ ),
+ };
}
"message" => {
message = match lit_to_string(lit) {
- Some(s) => Some(s),
- None => panic!(
- "Invalid argument type for `message` for validator `{}` on field `{}`: only a string is allowed",
- validator_name, field
- ),
- };
+ Some(s) => Some(s),
+ None => panic!(
+ "Invalid argument type for `message` for validator `{}` on field `{}`: only a string is allowed",
+ validator_name, field
+ ),
+ };
}
_ => continue,
}
diff --git a/validator_derive/tests/non_control.rs b/validator_derive/tests/non_control.rs
new file mode 100644
index 0000000..37fb153
--- /dev/null
+++ b/validator_derive/tests/non_control.rs
@@ -0,0 +1,75 @@
+#[macro_use]
+extern crate validator_derive;
+extern crate validator;
+
+use validator::Validate;
+
+#[cfg(feature = "unic")]
+#[test]
+fn can_validate_utf8_ok() {
+ #[derive(Debug, Validate)]
+ struct TestStruct {
+ #[validate(non_control_character)]
+ val: String,
+ }
+
+ let s = TestStruct { val: "하늘".to_string() };
+
+ assert!(s.validate().is_ok());
+}
+
+#[cfg(feature = "unic")]
+#[test]
+fn utf8_with_control_fails_validation() {
+ #[derive(Debug, Validate)]
+ struct TestStruct {
+ #[validate(non_control_character)]
+ val: String,
+ }
+
+ let s = TestStruct { val: "\u{009F}하늘".to_string() };
+ let res = s.validate();
+ assert!(res.is_err());
+ let err = res.unwrap_err();
+ let errs = err.field_errors();
+ assert!(errs.contains_key("val"));
+ assert_eq!(errs["val"].len(), 1);
+ assert_eq!(errs["val"][0].code, "non_control_character");
+}
+
+#[cfg(feature = "unic")]
+#[test]
+fn can_specify_code_for_non_control_character() {
+ #[derive(Debug, Validate)]
+ struct TestStruct {
+ #[validate(non_control_character(code = "oops"))]
+ val: String,
+ }
+ let s = TestStruct { val: "\u{009F}하늘".to_string() };
+ let res = s.validate();
+ assert!(res.is_err());
+ let err = res.unwrap_err();
+ let errs = err.field_errors();
+ assert!(errs.contains_key("val"));
+ assert_eq!(errs["val"].len(), 1);
+ assert_eq!(errs["val"][0].code, "oops");
+ assert_eq!(errs["val"][0].params["value"], "\u{9F}하늘");
+}
+
+#[cfg(feature = "unic")]
+#[test]
+fn can_specify_message_for_non_control_character() {
+ #[derive(Debug, Validate)]
+ struct TestStruct {
+ #[validate(non_control_character(message = "oops"))]
+ val: String,
+ }
+ let s = TestStruct { val: "\u{009F}하늘".to_string() };
+ let res = s.validate();
+ assert!(res.is_err());
+ let err = res.unwrap_err();
+ let errs = err.field_errors();
+ assert!(errs.contains_key("val"));
+ assert_eq!(errs["val"].len(), 1);
+ assert_eq!(errs["val"][0].clone().message.unwrap(), "oops");
+}
diff --git a/validator_derive/tests/regex.rs b/validator_derive/tests/regex.rs
index 7e3dfc5..d3e3c1e 100644
--- a/validator_derive/tests/regex.rs
+++ b/validator_derive/tests/regex.rs
@@ -16,7 +16,7 @@ lazy_static! {
fn can_validate_valid_regex() {
#[derive(Debug, Validate)]
struct TestStruct {
- #[validate(regex = "RE2")]
+ #[validate(regex = "crate::RE2")]
val: String,
}
@@ -29,7 +29,7 @@ fn can_validate_valid_regex() {
fn bad_value_for_regex_fails_validation() {
#[derive(Debug, Validate)]
struct TestStruct {
- #[validate(regex = "RE2")]
+ #[validate(regex = "crate::RE2")]
val: String,
}
@@ -48,7 +48,7 @@ fn bad_value_for_regex_fails_validation() {
fn can_specify_code_for_regex() {
#[derive(Debug, Validate)]
struct TestStruct {
- #[validate(regex(path = "RE2", code = "oops"))]
+ #[validate(regex(path = "crate::RE2", code = "oops"))]
val: String,
}
let s = TestStruct { val: "2".to_string() };
@@ -65,7 +65,7 @@ fn can_specify_code_for_regex() {
fn can_specify_message_for_regex() {
#[derive(Debug, Validate)]
struct TestStruct {
- #[validate(regex(path = "RE2", message = "oops"))]
+ #[validate(regex(path = "crate::RE2", message = "oops"))]
val: String,
}
let s = TestStruct { val: "2".to_string() };
diff --git a/validator_derive/tests/run-pass/custom.rs b/validator_derive/tests/run-pass/custom.rs
index dff6375..f745de6 100644
--- a/validator_derive/tests/run-pass/custom.rs
+++ b/validator_derive/tests/run-pass/custom.rs
@@ -6,13 +6,13 @@ use validator::{Validate, ValidationError};
#[derive(Validate)]
struct Test {
- #[validate(custom = "validate_something")]
+ #[validate(custom = "crate::validate_something")]
s: String,
}
#[derive(Validate)]
struct TestPath {
- #[validate(custom = "::validate_something")]
+ #[validate(custom = "crate::validate_something")]
s: String,
}
diff --git a/validator_derive/tests/run-pass/regex.rs b/validator_derive/tests/run-pass/regex.rs
index dcac3c0..e15b3a6 100644
--- a/validator_derive/tests/run-pass/regex.rs
+++ b/validator_derive/tests/run-pass/regex.rs
@@ -14,13 +14,13 @@ lazy_static! {
#[derive(Validate)]
struct Test {
- #[validate(regex = "RE2")]
+ #[validate(regex = "crate::RE2")]
s: String,
}
#[derive(Validate)]
struct TestPath {
- #[validate(regex = "::RE2")]
+ #[validate(regex = "crate::RE2")]
s: String,
}