# type-rules
A tool to easily constrain a struct and recover errors.
## Table of Contents
1. [Install](#install)
2. [Basic checking](#basic-checking)
3. [Advanced checking](#advanced-checking)
4. [Make your own rule](#make-your-own-rule)
5. [Rules list](#rules-list)
## Install
```toml
# Cargo.toml
[dependencies]
type-rules = { version = "0.2.3", features = ["derive", "regex", "serde"] }
```
## Basic checking
You can declare a struct and impose some constraints on each field
and check the validity like this:
```rust
use chrono::prelude::*;
use type_rules::prelude::*;
#[derive(Validator)]
struct NewUser {
#[rule(MaxLength(100), RegEx(r"^\S+@\S+\.\S+"))]
email: String,
#[rule(MinMaxLength(8, 50))]
password: String,
#[rule(Opt(MaxRange(Utc::now())))]
birth_date: Option>
}
let new_user = NewUser {
email: "examples@examples.com".to_string(),
password: "OPw$5%hJ".to_string(),
birth_date: None,
};
assert!(new_user.check_validity().is_ok());
let new_user = NewUser {
email: "examples@examples.com".to_string(),
password: "O".to_string(),
birth_date: None,
};
assert!(new_user.check_validity().is_err()); //Value is too short
```
Also works with enums :
```rust
use type_rules::prelude::*;
#[derive(Validator)]
enum MyEnum {
Option1(#[rule(MaxLength(200))] String),
Option2 {
#[rule(MinMaxRange(1, 10))]
integer: u32
},
Option3,
}
```
## Advanced checking
To check recursively, you can use the `Validate` rule
```rust
use type_rules::prelude::*;
#[derive(Validator)]
struct EmailWrapper(#[rule(MaxLength(100), RegEx(r"^\S+@\S+\.\S+"))] String);
#[derive(Validator)]
struct User {
#[rule(Validate())]
email: EmailWrapper,
#[rule(MinMaxLength(8, 50))]
password: String,
}
```
You can use expressions directly in rule derive attribute.
For example, you can use const or function directly in the rule parameters:
```rust
use type_rules::prelude::*;
use chrono::prelude::*;
#[derive(Validator)]
struct BirthDate(#[rule(MaxRange(Utc::now()))] DateTime);
```
```rust
use type_rules::prelude::*;
#[derive(Validator)]
struct Range {
#[rule(MaxRange(self.max))]
min: u32,
#[rule(MinRange(self.min))]
max: u32,
};
```
Or use expressions to express a rule directly.
Here is an example of using a rule with more complex values:
```rust
use std::env;
use type_rules::prelude::*;
fn generate_max_payload_rule() -> MaxLength {
MaxLength(match env::var("MAX_PAYLOAD") {
Ok(val) => val.parse().unwrap_or_else(|_| 10000),
Err(_) => 10000,
})
}
#[derive(Validator)]
struct Payload(#[rule(generate_max_payload_rule())] String);
```
In this case the `generate_max_payload_rule` function is executed at each check
## Make your own rule
If you need a specific rule, just make a tuple struct (or struct if you make the declaration outside the struct
definition)
that implements the `Rule` feature :
```rust
use type_rules::prelude::*;
struct IsEven();
impl Rule for IsEven {
fn check(&self, value: &i32) -> Result<(), String> {
if value % 2 == 0 {
Ok(())
} else {
Err("Value is not even".into())
}
}
}
#[derive(Validator)]
struct MyInteger(#[rule(IsEven())] i32);
```
## Valid wrapper
`Valid` is a wrapper for any type that implements `Validator`
it permit to ensure at compile time that the inner type as been
verified.
With the `serde` feature, Valid can be serialized and deserialized
with validity check.
```rust
use type_rules::prelude::*;
#[derive(Validator)]
struct NewUser {
#[rule(MinMaxLength(3, 50))]
username: String,
#[rule(MinMaxLength(8, 100))]
password: String,
}
fn do_something(user: Valid) {
// No need to check if user is valid
}
let new_user = NewUser {
username: "example".to_string(),
password: "OPw$5%hJJ".to_string(),
};
do_something(Valid::new(new_user).unwrap());
```
## Rules list
Here a list of the rules you can find in this crate.
Each rule has its own [documentation](https://docs.rs/type-rules/latest/type_rules/rules/index.html)
with examples.
Check the length of any type that implements `AsRef` such
as `String` or `&str`:
- `MinLength`: Minimum length ex: `MinLength(5)`
- `MaxLength`: Maximum length ex: `MaxLength(20)`
- `MinMaxLength`: Minimum and maximum length ex: `MinMaxLength(5, 20)`
Check the range for anything that implements `PartialOrd` like all numeric/floating types
or dates with `chrono`:
- `MinRange`: Minimum range ex: `MinRange(5)`
- `MaxRange`: Maximum range ex: `MaxRange(20)`
- `MinMaxRange`: Minimum and maximum range ex: `MinMaxRange(5, 20)`
Check the size of a `Vec` :
- `MinSize`: Minimum size ex: `MinSize(5)`
- `MaxSize`: Maximum size ex: `MaxSize(20)`
- `MinMaxSize`: Minimum and maximum size ex: `MinMaxSize(5, 20)`
others :
- `Opt`: Apply another rule to inner value of an `Option` ex: `Opt(MinMaxRange(1, 4))`
- `And`: Rule to ensure that 2 other rules are `Ok` ex: `And(MaxLength(1000), RegEx(r"^\S+@\S+\.\S+"))`
- `Or`: Rule to apply an Or condition on two other rules. ex: `Or(MaxRange(-1), MinRange(1))`
- `Eval`: Rule to constrain any type to a predicate ex: `Eval(predicate, "Error message")`
- `Validate`: Recursive checking ex: `Validate()`
- `In`: Rule to constrain a type to be `in` a collection
ex: `In(["apple", "banana", "orange", "pear"], "Value need to be a fruit")`
- `All`: Rule to constrain a collection to valid the specified rule
ex: `All(MinLength(1), "You can't use empty string")`
- `RegEx`: check if a type that implement `AsRef` (String, &str, ...) matches the regex.
You need the `regex` feature to use it.
ex: `RegEx(r"^\S+@\S+\.\S+")`