# `validus` --- validated string slices ```rust // A three-step process. // 1. Define your validation. No need to re-implement your validation logic! // (though, we have a macro that makes it easy if you do have control.) // Validus offers multiple tradeoff points between flexibility and convenience. // 2. Replace `str` with `vstr` where you need validation. // `&vstr` coerces to `&str`, so you can use it deep inside your codebase, // or even a foreign codebase. (Limitations exist.) // 3. Use `vstr` as though it were `str`. # use std::sync::OnceLock; # use validus::prelude::*; // vstr, fast_rule, <&str>.validate(), etc. use validus::fast_rule; # # use regex::Regex; # // Let's define a string validation rule that uses a Regex. const USERNAME_RE_S: &str = r#"^[a-zA-Z0-9_]{1,16}$"#; static USERNAME_RE: OnceLock = OnceLock::new(); // 1. Defining the rule type and the error type. // - I used fast_rule! because I have control over the error type. // - If you are using existing error types and/or function, // you can use easy_rule! instead. There's also a macro-free way; // see the `vstr` module docs for more info. struct BadUsernameError; struct UsernameRule; fast_rule!( UsernameRule, err = BadUsernameError, msg = "bad username", // The closure below could very well be a function. // So if the function were called `username_rule`, // I could do fast_rule!(..., ..., ..., username_rule) |s: &str| { let re = USERNAME_RE.get_or_init(|| Regex::new(USERNAME_RE_S).unwrap()); re.is_match(s) } ); // (There's also easy_rule that allows you to bring your own error type.) // (That's helpful when you have existing error types.) // (... or, walk the macro-free path and implement ValidateString manually.) // (consult the `vstr` module docs for more info.) // 2. Restrict your string slice with the rule. type Username = vstr; // 3. Use your `vstr` as though it were `str`. let input = "hello"; let username: Result<&Username, BadUsernameError> = input.validate(); assert!(username.is_ok()); assert_eq!(username.unwrap(), "hello"); let input = "haha 😊"; let username: Result<&Username, _> = input.validate(); assert!(username.is_err()); // It's that easy! Let's do a recap. // - Error and the rule: // struct MyError; // struct MyRule; // fn my_rule_priv(s: &str) -> Result<(), MyError> { ... } // fast_rule!(MyRule, err = MyError, msg = "my error", my_rule_priv); // - Use `vstr` as though it were `str`: // type MyStr = vstr; // let a: &vstr = "hello".validate().unwrap(); // Plus, this library has serde support with validation, and more. ``` ## Synopsis 1. **Bring your own validation**, and use [`vstr<_>`](crate::vstr::vstr) as though it were `str` (where immutable `str` references are expected). 2. **Mix it into your code.** If you have existing validation modules, don't touch it. Validus has multiple easy ways to wrap your validation into a `vstr<_>`-compatible rule type. 3. **Take a shortcut when starting out.** If you don't have existing validation modules, you can use the [`fast_rule!`](crate::fast_rule) macro to quickly define a rule type with a predicate (closure or external function, which may or may not belong to your crate). Your error type will be a proper `std::error::Error` type; you can use an ad-hoc `&'static str` error message, even. 3. **Work with `serde` with no extra code.** Simply add `#[derive(Serialize, Deserialize)]` to your struct as usual. Now, no need to worry about invalid strings getting deserialized. 4. **Opt into lazy validation if you want.** With a special rule called [`Later<_>`](crate::vstr::Later), you can also choose to either eagerly validate or defer validation until when you decide you need it. (Pretty useful for `serde` deserialization, for example.) 5. **Combine rules on the spot.** Three logical connectives (and, or, not) are provided as an extension enabled by default; those are in your prelude, so you can use them right away. The Validus library provides a newtype over regular string slices called `vstr`. This `vstr` is parameterized by your own validation rule type, like `vstr`. Any zero-sized type can be used as a rule if it implements the trait [`ValidateString`](crate::vstr::ValidateString). `vstr` is meant to be a replacement of `str` in certain contexts. So, - `vstr` implements `Eq` with `vstr` of other rules as well as `str`, - ... and `Ord` and `Hash` the same as any other `str` reference. - `vstr` is aware of `String`, `Cow` and smart pointer types such as `Box`, `Rc` and `Arc`. To show you how `vstr<_>` compares and hashes the same as `str` references, I will give you an example of directly using `vstr` 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::fast_rule; struct BadUsernameError; struct UsernameRule; fast_rule!( UsernameRule, err = BadUsernameError, msg = "bad username", |s: &str| s.len() <= 16 ); type Username = vstr; let mut map = HashMap::<&Username, i32>::new(); map.insert("hello".validate().unwrap(), 1); map.insert("world".validate().unwrap(), 2); // assume_valid bypasses validation, incurring no computational cost, // so it's useful in this case. assert_eq!(map.get("hello".assume_valid()), Some(&1)); assert_eq!(map.get("world".assume_valid()), Some(&2)); // So every time you need a `&str` but with validation, // you know that Validus and `vstr<_>` have got you covered, // anywhere in your codebase, or a foreign codebase. // (Limitations exist.) ``` ## `serde` with validation 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. - The `serde` feature is enabled by default. Disable it using `default-features = false` in your `Cargo.toml`. ```rust // Illustration: a struct with a validated email field. #[cfg(feature = "serde")] { use validus::prelude::*; use validus::fast_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; fast_rule!(EmailRule, msg = "no at-symbol", |s: &str| s.contains('@')); #[derive(Deserialize)] pub struct User { pub email: Box>, } let input = r#"{"email": "notgood"}"#; let result = serde_json::from_str::(input); assert!(result.is_err()); let input = r#"{"email": "hi@example.com"}"#; let result = serde_json::from_str::(input); assert!(result.is_ok()); assert!(result.unwrap().email.as_str() == "hi@example.com"); } ``` ## Deferred validation with [`Later`](crate::vstr::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. - [`Later`](crate::vstr::Later) - [`make_strict`](crate::vstr::VStr::make_strict) - [`assume_valid`](crate::vstr::VStr::assume_valid) ```rust use validus::prelude::*; use validus::fast_rule; struct EmailError; struct Email; fast_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> = "hi@example.com".validate().unwrap(); // Now, we truly validate it. let v1: Result<&vstr, _> = v1.make_strict(); assert!(v1.is_ok()); // So, again, this is going to succeed. let v2 = "notgood".validate::>().unwrap(); // But, when we check it, it will fail, since it is not a good email address // (according to the rule we defined). let v2 = v2.make_strict(); assert!(v2.is_err()); // 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".assume_valid::>(); assert!(relaxed.check().is_ok()); // This is infallible because `Later<_>` is infallible. assert!(relaxed.make_strict().is_ok()); // Later -> Email. let relaxed = "nonono".assume_valid::>(); assert!(relaxed.check().is_ok()); // Yup, it is still infallible. let strict = relaxed.make_strict(); // Now, we made it strict. assert!(strict.is_err()); // It didn't make it (it was a bad email address.) ``` ## Overriding a rule with `assume_valid` and checking with `check` 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; or, when, for some reason, you don't need to validate a `vstr<_>` (for example, when you are using it as a look-up key in a `HashMap`). The crate provides the `check()` method that can be used to establish the validity of a `vstr<_>`. - [`assume_valid`](crate::vstr::VStr::assume_valid) - [`check`](crate::vstr::VStr::check) ```rust // Illustration: overriding the validation mechanism. use validus::prelude::*; use validus::easy_rule; // easy_rule is a different macro that helps you define rules. // The difference with fast_rule! is that leaves the error type // untouched, and you need to return a Result<(), YourError> // instead of a bool in the closure. struct No; easy_rule!(No, err = &'static str, |s: &str| Err("i won't accept anything")); let s = "hello"; let v: &vstr = vstr::assume_valid(s); // 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.**) ## Defining implication among rules 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` to a reference to `vstr`. This requires `Rule` to implement `Into`. (Otherwise, the regular `try_change_rules` can be used between any two rules.) - [`change_rules`](crate::vstr::VStr::change_rules) - [`try_change_rules`](crate::vstr::VStr::try_change_rules) - [`ValidateString`](crate::vstr::ValidateString) - [`Into`] and [`From`] ```rust // Illustration: declaring implication. // Implication means: "Whenever [rule] A says good, so does B." use validus::prelude::*; use validus::fast_rule; // Less generous struct A; fast_rule!(A, msg = "no wow", |s: &str| s.contains("wow")); // More generous: includes all strings that A accepts and // perhaps more. struct B; fast_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` to a reference // to `vstr` infallibly. let good = "wow bad"; let a: &vstr = vstr::assume_valid(good); // we know it works, so. let _: &vstr = a.change_rules(); // infallible. see, no Result or unwrap(). ``` ### The special rule `ValidateAll` 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. - [`try_change_rules`](crate::vstr::VStr::try_change_rules) - [`change_rules`](crate::vstr::VStr::change_rules) - [`erase_rules`](crate::vstr::VStr::erase_rules) - [`ValidateAll`](crate::vstr::ValidateAll) ## Batteries included ... (but I need your help!) Check out some of the prepared validation rules in the module [`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.) Currently, three *logical connectives* and a few rules are implemented in the extension module: - [`Conj`](crate::vstrext::Conj), which requires two rules to be satisfied. - [`Disj`](crate::vstrext::Disj), which requires at least one of two rules to be satisfied. - [`Neg`](crate::vstrext::Neg), which requires a rule to be **un**-satisfied. - [`StringSizeRule`](crate::vstrext::StringSizeRule) (and its variants), which check the size of a string. - [`StringAsciiRule`](crate::vstrext::StringAsciiRule), our only rule that checks the content of a string so far. I would really appreciate your help in adding more rules to the extension module. ## Experimental features ### Contingent validation The experimental `cow` feature introduces a new type, `VCow` that represents a `Cow<'_, vstr<_>>` that is either valid or invalid. The validity is tracked at runtime. **NOTE**: `vstr` already supports being in a Cow like this: `Cow<'_, vstr<_>>` even without the `cow` feature. The `cow` feature adds an experimental wrapper type that tracks the validity of the `vstr` that may change at runtime. - [`VCow`](crate::vstr::VCow) ## Features - (default) `serde`: enables `serde` support. Pulls in `serde` as a dependency. - (default) `ext`: enables built-in extensions. Pulls in `thiserror` as a dependency. - (default) `std`: enables `std` support. Necessary to implement the `Error` trait. Pulls in `std` as a dependency. - (default) `alloc`: integrates with `alloc` crate., and enables smart pointers. - `cow`: enables the experimental `VCow` type. Pulls in `alloc` as a dependency.