https://github.com/matteopolak/wary
A no_std-compatible validation and transformation library.
https://github.com/matteopolak/wary
no-alloc no-std rust transform validate
Last synced: about 1 month ago
JSON representation
A no_std-compatible validation and transformation library.
- Host: GitHub
- URL: https://github.com/matteopolak/wary
- Owner: matteopolak
- License: apache-2.0
- Created: 2024-07-28T17:22:00.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-01-07T03:23:38.000Z (5 months ago)
- Last Synced: 2025-04-14T02:27:52.281Z (about 1 month ago)
- Topics: no-alloc, no-std, rust, transform, validate
- Language: Rust
- Homepage:
- Size: 177 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# Wary
[](https://crates.io/crates/wary)
[](https://docs.rs/wary/latest/wary/)
[](https://github.com/matteopolak/wary/actions)An optionally `no_std` and `no_alloc` validation and transformation library.
- Basic usage
- [Basic struct example](#basic-struct-example)
- [Basic enum example](#basic-enum-example)
- [Accessing context](#context)
- [Validation rules](#validation-rules)
- [Implementing custom `Rule`s](#rule-custom)
- [Implementing `Validate` manually](#manual-validate)
- [Transformation rules](#transformation-rules)
- [Implementing custom `Transformer`s](#transformer-custom)
- [Implementing `Transform` manually](#manual-transform)### Basic struct example
```rust
use std::borrow::Cow;
use wary::Wary;#[derive(Wary)]
struct Name<'n>(
#[validate(alphanumeric, length(chars, 5..=20), equals(not, other = "john"))]
Cow<'n, str>
);#[derive(Wary)]
struct Person<'n> {
#[validate(dive)]
name: Name<'n>,
#[validate(range(..=100))]
age: u8,
}let mut person = Person {
name: Name(Cow::Borrowed("jane")),
age: 25,
};if let Err(report) = person.wary(&()) {
eprintln!("invalid person: {report:?}");
}
```### Basic enum example
```rust
use std::borrow::Cow;
use wary::Wary;#[derive(Wary)]
struct Name<'n>(
#[validate(alphanumeric, length(chars, 5..=20), equals(not, other = "john"))]
#[transform(lowercase(ascii))]
&'n mut str
);// for length(bytes)
impl wary::AsRef<[u8]> for Name<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}#[derive(Wary)]
enum Person<'n> {
Child {
#[validate(dive)]
name: Name<'n>,
#[validate(range(..=17))]
age: u8,
},
Adult {
#[validate(dive, length(bytes, ..=32))]
name: Name<'n>,
#[validate(range(18..=100))]
age: u8,
},
}let mut name = "Jane".to_string();
let mut person = Person::Adult {
name: Name(&mut name),
age: 25,
};if let Err(report) = person.wary(&()) {
eprintln!("invalid person: {report:?}");
} else {
let Person::Adult { name, age } = person else {
unreachable!();
};assert_eq!(name.0, "jane");
}
``````rust
use wary::Wary;
use wary::toolbox::rule::*;
use std::ops::Range;// allows one context to be passed to all rules
#[derive(AsRef)]
struct Context {
range: Range,
#[as_ref(skip)]
useless: bool,
}struct RangeRule {
ctx: PhantomData,
}impl RangeRule {
fn new() -> Self {
Self {
ctx: PhantomData,
}
}
}impl wary::Rule for RangeRule
where
C: AsRef>,
{
type Context = C;fn validate(&self, ctx: &Self::Context, item: &u8) -> Result<()> {
if ctx.as_ref().contains(item) {
Ok(())
} else {
Err(wary::Error::with_message("out_of_range", "The number is out of range"))
}
}
}#[allow(non_camel_case_types)]
mod rule {
pub type range = super::RangeRule;
}#[derive(Wary)]
#[wary(context = Context)]
struct Age {
#[validate(custom(range))]
number: u8,
}# fn main() {}
```## Validation rules
Validation rules applied through the proc-macro `Wary` attribute are (for the most part) simply forwarded
directly to their respective builders inside the [`rule`](crate::options::rule) module. As a result of this
decision, all rules (except `and`, `or`, `inner`, and `dive`) will have auto-completion when writing macro attributes!If you're providing no options to a rule, you can omit the parentheses. For example: `#[validate(alphanumeric)]`
and `#[validate(alphanumeric())]` are equivalent.| rule | trait | feature | dependency |
| ---- | ----- | ------- | ---------- |
| [`addr`](#rule-addr) | [`AsRef`](wary::AsRef) | - | - |
| [`alphanumeric`](#rule-alphanumeric) | [`AsRef`](wary::AsRef) | - | - |
| [`and`](#rule-and) | - | - | - |
| [`ascii`](#rule-ascii) | [`AsRef`](wary::AsRef) | - | - |
| [`contains`](#rule-contains) | [`AsSlice`](wary::AsSlice) | - | - |
| [`credit_card`](#rule-credit-card) | [`AsRef`](wary::AsRef) | `credit_card` | [`creditcard`](https://github.com/matteopolak/creditcard) |
| [`custom`](#rule-custom) | [`Rule`](wary::Rule) | - | - |
| [`dive`](#rule-dive) | [`Validate`](wary::Validate) | - | - |
| [`email`](#rule-email) | [`AsRef`](wary::AsRef) | `email` | [`email_address`](https://github.com/johnstonskj/rust-email_address) |
| [`equals`](#rule-equals) | [`std::cmp::PartialEq`](std::cmp::PartialEq) | - | - |
| [`func`](#rule-func) | `Fn(&T) -> Result<(), wary::Error>` | - | - |
| [`inner`](#rule-inner) | [`AsSlice`](wary::AsSlice) | - | - |
| [`length`](#rule-length) | [`Length`](wary::Length) | `graphemes`\* | [`unicode-segmentation`](https://github.com/unicode-rs/unicode-segmentation) |
| [`lowercase`](#rule-lowercase) | [`AsRef`](wary::AsRef) | - | - |
| [`or`](#rule-or) | - | - | - |
| [`prefix`](#rule-prefix) | [`AsSlice`](wary::AsSlice) | - | - |
| [`range`](#rule-range) | [`Compare`](wary::Compare) | - | - |
| [`regex`](#rule-regex) | [`AsRef`](wary::AsRef) | `regex` | [`regex`](https://github.com/rust-lang/regex) |
| [`required`](#rule-required) | [`AsSlice`](wary::AsSlice) | - | - |
| [`semver`](#rule-semver) | [`AsRef`](wary::AsRef) | `semver` | [`semver`](https://github.com/dtolnay/semver) |
| [`suffix`](#rule-suffix) | [`AsSlice`](wary::AsSlice) | - | - |
| [`uppercase`](#rule-uppercase) | [`AsRef`](wary::AsRef) | - | - |
| [`url`](#rule-url) | [`AsRef`](wary::AsRef) | `url` | [`url`](https://github.com/servo/rust-url) |
| [`uuid`](#rule-uuid) | [`AsRef`](wary::AsRef) | `uuid` | [`uuid`](https://github.com/uuid-rs/uuid) |\* optional
Validates an address (currently only an IP).
```rust
use wary::Wary;#[derive(Wary)]
struct Packet {
#[validate(addr(ipv4))]
src: String,
#[validate(addr(ipv6))]
dest: String,
#[validate(addr(ip))]
more: String,
}
```Validates that the input is alphanumeric.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[validate(alphanumeric)]
left: String,
#[validate(alphanumeric(ascii))]
right: String,
}
```Meta-rule that combines multiple rules. Unlike other rule lists, this one **short-circuits on the first error**.
```rust
use wary::{Wary, Validate};#[derive(Wary)]
struct NameAnd {
#[validate(and(equals(other = 1), range(2..=2)))]
value: u8
}let name = NameAnd {
value: 3,
};let report = name.validate(&()).unwrap_err();
assert_eq!(report.len(), 1);
#[derive(Wary)]
struct Name {
#[validate(equals(other = 1), range(2..=2))]
value: u8
}let name = Name {
value: 3,
};let report = name.validate(&()).unwrap_err();
assert_eq!(report.len(), 2);
```Validates that the input is ascii.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(ascii)]
String
);
```Validates that the input contains a substring or subslice.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(contains(str = "hello"))]
String
);
```### `credit_card` (requires feature `credit_card`)
Validates that the input is a credit card number (PAN).
```rust
use wary::Wary;#[derive(Wary)]
struct Card(
#[validate(credit_card)]
String
);
```Validates the input with a custom [`Rule`](wary::Rule).
```rust
use wary::Wary;
use wary::toolbox::rule::*;struct SecretRule;
impl SecretRule {
fn new() -> Self {
Self
}
}impl wary::Rule for SecretRule
where
I: AsRef,
{
type Context = ();fn validate(&self, _ctx: &Self::Context, item: &I) -> Result<()> {
let string = item.as_ref();if string.contains("secret") {
Err(Error::with_message("secret_found", "You cannot use the word 'secret'"))
} else {
Ok(())
}
}
}#[allow(non_camel_case_types)]
mod rule {
pub type secret = super::SecretRule;
}#[derive(Wary)]
struct Person {
#[validate(custom(secret))]
name: String,
}# fn main() {}
```Validates the inner fields of a struct or enum.
```rust
use wary::Wary;#[derive(Wary)]
struct Item {
#[validate(ascii)]
name: &'static str,
}#[derive(Wary)]
struct Name {
#[validate(dive)]
item: Item,
}
```### `email` (requires feature `email`)
Validates that the input is an email.
```rust
use wary::Wary;#[derive(Wary)]
struct Email(
#[validate(email)]
String
);
```Validates that the input is equal to a value. Currently does not support `self` fields.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(equals(other = "John"))]
String
);
```Validates the input with a function.
```rust
use wary::{Wary, Error};fn check(_ctx: &(), name: &str) -> Result<(), Error> {
if name.len() > 5 {
Ok(())
} else {
Err(Error::with_message("name_too_short", "Your name must be longer than 5 characters"))
}
}#[derive(Wary)]
struct Name {
#[validate(func = |ctx: &(), name: &str| {
if name.len() > 5 {
Ok(())
} else {
Err(Error::with_message("name_too_short", "Your name must be longer than 5 characters"))
}
})]
left: String,
#[validate(func = check)]
right: String,
}
```Validates the inner fields of a slice-like type.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[validate(inner(ascii))]
items: Vec,
}
```Validates the length of the input.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
// counts the length in bytes
#[validate(length(bytes, 5..=20))]
bytes: String,
// counts the length in characters
#[validate(length(chars, 5..=20))]
chars: String,
// counts the length in UTF-16 code units
#[validate(length(code_units, 5..=20))]
code_points: String,
// counts the length in grapheme clusters
#[validate(length(graphemes, 5..=20))]
graphemes: String,
}
```Validates that the input is lowercase.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[validate(lowercase)]
left: String,
#[validate(lowercase(ascii))]
right: String,
}
```Meta-rule that combines multiple rules. Short-circuits on the first success.
```rust
use wary::{Wary, Validate};
use std::sync::atomic::{AtomicUsize, Ordering};mod rule {
pub type debug = super::DebugRule;
}struct DebugRule;
impl DebugRule {
fn new() -> Self {
Self
}
}static DEBUG_COUNTER: AtomicUsize = AtomicUsize::new(0);
impl wary::Rule for DebugRule {
type Context = ();fn validate(&self, _ctx: &Self::Context, item: &I) -> Result<(), wary::Error> {
DEBUG_COUNTER.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}#[derive(Wary)]
struct NameOr {
#[validate(or(equals(other = 1), custom(debug)))]
value: u8
}# fn main() {
let name = NameOr {
value: 1,
};let report = name.validate(&()).unwrap();
assert_eq!(DEBUG_COUNTER.load(Ordering::Relaxed), 0);
# }
```Validates that the input starts with a substring or subslice.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(prefix(str = "hello"))]
String
);
```Validates that the input is within a range.
```rust
use wary::Wary;#[derive(Wary)]
struct Age {
#[validate(range(18..=100))]
number: u8,
#[validate(range('a'..='z'))]
char: char,
#[validate(range("hello".."world"))]
string: String,
}
```### `regex` (requires feature `regex`)
Validates that the input matches a regex.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(regex(pat = "^[a-z]+$"))]
String
);
```Validates that the input is not empty. For example, that an `Option` is `Some` or a `Vec` is not empty.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[validate(required)]
first: String,
#[validate(required)]
last: Option,
}
```### `semver` (requires feature `semver`)
Validates that the input is a semver.
```rust
use wary::Wary;#[derive(Wary)]
struct Version(
#[validate(semver)]
String
);
```Validates that the input ends with a substring or subslice.
```rust
use wary::Wary;#[derive(Wary)]
struct Name(
#[validate(suffix(str = "hello"))]
String
);
```Validates that the input is uppercase.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[validate(uppercase)]
left: String,
#[validate(uppercase(ascii))]
right: String,
}
```### `url` (requires feature `url`)
Validates that the input is a url.
```rust
use wary::Wary;#[derive(Wary)]
struct Url(
#[validate(url)]
String
);
```### `uuid` (requires feature `uuid`)
Validates that the input is a uuid.
```rust
use wary::Wary;#[derive(Wary)]
struct Uuid(
#[validate(uuid)]
String
);
```### Implementing `Validate` manually
In the rare case you need to manually implement `Validate`, you will need to keep in mind about reporting errors properly.
```rust
use wary::{Validate, Error, error::{Path, Report}};struct Name {
value: String,
}impl Validate for Name {
type Context = ();fn validate_into(&self, _ctx: &Self::Context, parent: &Path, report: &mut Report) {
if self.value.len() < 5 {
report.push(
parent.append("value"),
Error::with_message("name_too_short", "Your name must be longer than 5 characters"),
);
}
}
}let name = Name {
value: "Jane".to_string(),
};assert!(name.validate(&()).is_err());
let longer = Name {
value: "Jane Doe".to_string(),
};assert!(longer.validate(&()).is_ok());
```## Transformation rules
Transformation rules are applied similarly to validation rules, but are implemented in the [`Transform`](wary::Transform) trait instead.
| rule | trait | feature | dependency |
| ---- | ----- | ------- | ---------- |
| [`custom`](#transformer-custom) | [`Transformer`](wary::Transformer) | - | - |
| [`dive`](#transformer-dive) | [`Transform`](wary::Transform) | - | - |
| [`lowercase`](#transformer-lowercase) | [`AsMut`](wary::AsMut) (for `ascii` only) | - | - |
| [`inner`](#transformer-inner) | [`AsMutSlice`](wary::AsMutSlice) | - | - |
| [`uppercase`](#transformer-uppercase) | [`AsMut`](wary::AsMut) (for `ascii` only) | - | - |Transforms the input with a custom [`Transformer`](wary::transformer).
```rust
use wary::{Wary, Transformer};struct SecretTransformer;
impl SecretTransformer {
fn new() -> Self {
Self
}
}impl Transformer for SecretTransformer {
type Context = ();fn transform(&self, _ctx: &Self::Context, item: &mut String) {
item.clear();
item.push_str("secret");
}
}#[allow(non_camel_case_types)]
mod transformer {
pub type secret = super::SecretTransformer;
}#[derive(Wary)]
struct Person {
#[transform(custom(secret))]
name: String,
}# fn main() {}
```Transforms the inner fields of a struct or enum.
```rust
use wary::Wary;#[derive(Wary)]
struct Item {
#[transform(lowercase)]
name: String,
}#[derive(Wary)]
struct Name {
#[transform(dive)]
item: Item,
}
```Transforms the input to lowercase.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[transform(lowercase)]
left: String,
#[transform(lowercase(ascii))]
right: String,
}
```Transforms the inner fields of a slice-like type.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[transform(inner(lowercase))]
items: Vec,
}
```Transforms the input to uppercase.
```rust
use wary::Wary;#[derive(Wary)]
struct Name {
#[transform(uppercase)]
left: String,
#[transform(uppercase(ascii))]
right: String,
}
```### Implementing `Transform` manually
```rust
use wary::Transform;struct Name {
value: String,
}impl Transform for Name {
type Context = ();fn transform(&mut self, _ctx: &Self::Context) {
self.value.make_ascii_lowercase();
}
}let mut name = Name {
value: "Jane".to_string(),
};name.transform(&());
assert_eq!(name.value, "jane");
```