validus
--- validated string slices```rust use std::sync::OnceLock;
use validus::prelude::*; // vstr, cheaprule, <&str>.validate(), etc. use validus::cheaprule;
use regex::Regex;
const USERNAMERES: &str = r#"^[a-zA-Z0-9]{1,16}$"#;
static USERNAMERE: OnceLock
// 1. Define your rule. struct BadUsernameError; struct UsernameRule; cheaprule!( UsernameRule, err = BadUsernameError, msg = "bad username", |s: &str| { let re = USERNAMERE.getorinit(|| Regex::new(USERNAMERES).unwrap()); re.is_match(s) } );
// 2. Restrict your string slice with the rule.
type Username = vstr
let input = "hello"; let username: Result<&Username, BadUsernameError> = input.validate(); assert!(username.isok()); asserteq!(username.unwrap(), "hello");
let input = "haha 😊"; let username: Result<&Username, > = input.validate(); assert!(username.iserr());
// Plus, this library has serde support with validation, and more. ```
This library provides a VStr<Rule>
type, which is an un-sized
wrapper around regular string slices (str
). Since it is un-sized,
it can be used as a slice, but it cannot be used as a value. Instead,
it is used as a reference to a value.
The library provides inter-conversion between &VStr<_>
and
other smart pointers such as Box
, Rc
and Arc
. (And,
of course, &str
).
It also inter-converts with String
and exposes the internal
string slice with .as_str()
.
A vstr<_>
reference an be compared and hashed with other
vstr<_>
with possibly different rules and str
references
using the inner string slice.
(VStr
is aliased to vstr
for convenience.)
vstr
(or the proper name, VStr
).cheap_rule
.Since vstr<_>
compares and hashes the same as str
references,
they can be used directly as keys in HashMap
s and HashSet
s.
```rust // Illustration: using vstr<_> as a key in a HashMap.
use std::collections::HashMap;
use validus::prelude::*; use validus::cheap_rule;
struct BadUsernameError; struct UsernameRule; cheap_rule!( UsernameRule, err = BadUsernameError, msg = "bad username", |s: &str| s.len() <= 16 );
type Username = vstr
let mut map = HashMap::new(); map.insert("hello".validate().unwrap(), 1); map.insert("world".validate().unwrap(), 2);
// assumevalid bypasses validation, incurring no computational cost,
// so it's useful in this case.
asserteq!(map.get("hello".assumevalid::
With the optional serde
feature, this crate also
supports serialization and deserialization with validation.
This means that you can use vstr<_>
as a field in a
serde
-powered struct, and if the input fails the validation,
it will be rejected and an error according to the validation
rule's associated Error
type will be returned.
serde
feature is enabled by default. Disable it using
default-features = false
in your Cargo.toml
to disable it.```rust // Illustration: a struct with a validated email field.
use validus::prelude::*; use validus::cheap_rule; use serde::Deserialize;
// This rule is very generous. It accepts any string that // contains an at-symbol. // (When the error type is not specified, it is inferred to // be &'static str.) struct EmailRule; cheap_rule!(EmailRule, msg = "no at-symbol", |s: &str| s.contains('@'));
pub struct User {
pub email: Box
let input = r#"{"email": "notgood"}"#;
let result = serdejson::fromstr::
let input = r#"{"email": "hi@example.com"}"#;
let result = serdejson::fromstr::
} ```
You are also given the power to override the underlying
mechanism using assume_valid
. This is useful when you
have a vstr<_>
that you know is valid, but that is difficult to
decide at a given moment. The crate provides check()
method that can be used to establish the validity of a
vstr<_>
.
```rust // Illustration: overriding the validation mechanism.
use validus::prelude::*; use validus::easy_rule;
struct No; easy_rule!(No, err = &'static str, |s: &str| Err("i won't accept anything"));
let s = "hello";
let v: &vstr
// Yup, it works. We overrode the validation mechanism. assert_eq!(v, "hello");
// But it's not valid. Let's test that. assert!(v.check().is_err()); ```
(assume_valid
is NOT unsafe
: vstr
makes no further
guarantees about the validity of the string slice beyond what
str
provides. [it also doesn't make any fewer].
Thus, assume_valid
may not be blamed for
causing undefined behavior.)
Furthermore, since some pairs of rules can be converted
automatically (there is an IMPLIES relation between them),
you can use the change_rules
associated method to
convert a reference to vstr<Rule1>
to a reference to
vstr<Rule2>
. This requires Rule
to implement Into<Rule2>
.
(Otherwise, the regular try_change_rules
can be used
between any two rules.)
change_rules
try_change_rules
ValidateString
.Into
] and [From
]```rust // Illustration: declaring implication. // Implication means: "Whenever [rule] A says good, so does B."
use validus::prelude::*; use validus::cheap_rule;
// Less generous struct A; cheap_rule!(A, msg = "no wow", |s: &str| s.contains("wow"));
// More generous: includes all strings that A accepts and // perhaps more. struct B; cheap_rule!(B, msg = "neither wow nor bad found", |s: &str| { s.contains("wow") || s.contains("bad") });
// Assert that A implies B.
// In English: "whatever string A accepts, B accepts, too."
impl From for B {
// This particular formulation is idiomatic
// to the validus
crate because all rules are supposed
// to be freely constructed Zero-Sized Types (ZSTs).
fn from(_: A) -> Self {
// And, this value never gets used, anyway.
// All methods of ValidateString
(trait that
// defines rules) have static methods, not instance
// methods.
B
}
}
// The declaration of implication unlocks the change_rules
// method that converts a reference to vstr<A>
to a reference
// to vstr<B>
infallibly.
let good = "wow bad"; let a: &vstr = vstr::assumevalid(good); // we know it works, so. let _: &vstr = a.changerules(); // infallible. see, no Result or unwrap(). ```
Oh, one more. There are two special rules which validate
all strings and no strings, respectively. They are called
ValidateAll
and ()
. Though you can't use change_rules
to convert your rule to ValidateAll
, you can still use
a dedicated method called erase_rules
just for that.
From ValidateAll
, you can use try_change_rules
to
convert to any other rule.
serde
with validationIn this example, a string representing email is validated upon
deserialization. If it passes the validation, it is wrapped in a
Box<Email>
; otherwise, an error is returned.
(This example requires the serde
feature to be enabled.)
```rust
use validus::prelude::*; use validus::easy_rule;
use thiserror::Error;
// Here's my rule. My "email" needs to contain an at-symbol somewhere. // For the demonstration purpose only, I will use only that rule.
pub enum EmailError { #[error("invalid email")] InvalidEmail, }
struct EmailRule;
easyrule!(EmailRule, err = EmailError, |s: &str| { s.contains('@').then(|| ()).okor(EmailError::InvalidEmail) });
// Now that the rule has been laid out, let me make a type alias to a validated email slice.
type Email = vstr
// Great. Now, let me show you an example with acceptance and one with rejection.
// ACCEPTANCE
let goodinput = "myemail@example.com";
assert!(goodinput.validate::
// REJECTION
let badinput = "myemail";
assert!(badinput.validate::
} ```
Later
Sometimes, you want to validate a string slice only when it is actually used.
For this need, there is a rule called Later
that bypasses all validation, but specifies what rule it is supposed to be
validated with. When the validation is actually needed, you can call
make_strict
to validate the string slice
and convert it to a vstr
with the specified rule.
Here, I copy the example code from the Later
type documentation.
```rust use validus::prelude::*; use validus::cheap_rule;
struct EmailError; struct Email; cheap_rule!(Email, err = EmailError, msg = "no @ symbol", |s: &str| s.contains('@') );
// Here, we start with an email with deferred (postponed) validation.
// Validation of Later<_>
is infallible.
let v1: &vstr
// So, again, this is going to succeed.
let v2 = "notgood".validate::
// With the extension StrExt
, we can also call .assume_valid()
// to skip validation, since we know that Later<_>
doesn't validate.
let relaxed = "hi@example.com".assumevalid::Later<_>
is infallible.
assert!(relaxed.makestrict().isok()); // Later
let relaxed = "nonono".assumevalid::
vstrext
][crate::vstrext].
The module should already have been imported in the prelude module
(it's feature-gated by ext
, which is enabled by default.)cow
feature introduces a new type, VCow
,
which is like Cow<str>
, but with optional validation.serde
: enables serde
support.ext
: enables built-in extensions.