Crate wary

Source
Expand description

§Wary

https://img.shields.io/crates/v/wary https://img.shields.io/docsrs/wary ci status

An optionally no_std and no_alloc validation and transformation library.

§Why use wary over other libraries?

-warygardevalidatorvalidify
no_std
no_alloc
async✅ (optional)
enums
transform input
custom rules
pass context
respect serde field attributes

§Basic struct example

use std::borrow::Cow;
use wary::Wary;

#[derive(Wary)]
#[wary(transparent)]
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

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");
}

§Accessing context

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<u8>,
  #[as_ref(skip)]
  useless: bool,
}

struct RangeRule<C> {
  ctx: PhantomData<C>,
}

impl<C> RangeRule<C> {
  fn new() -> Self {
    Self {
      ctx: PhantomData,
    }
  }
}

impl<C> wary::Rule<u8> for RangeRule<C>
where
  C: AsRef<Range<u8>>,
{
  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<C> = super::RangeRule<C>;
}

#[derive(Wary)]
#[wary(context = Context)]
struct Age {
  #[validate(custom(range))]
  number: u8,
}

§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 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.

* optional

§addr

Validates an address (currently only an IP).

use wary::Wary;

#[derive(Wary)]
struct Packet {
  #[validate(addr(ipv4))]
  src: String,
  #[validate(addr(ipv6))]
  dest: String,
  #[validate(addr(ip))]
  more: String,
}

§alphanumeric

Validates that the input is alphanumeric.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[validate(alphanumeric)]
  left: String,
  #[validate(alphanumeric(ascii))]
  right: String,
}

§and

Meta-rule that combines multiple rules. Unlike other rule lists, this one short-circuits on the first error.

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);

§ascii

Validates that the input is ascii.

use wary::Wary;

#[derive(Wary)]
struct Name(
  #[validate(ascii)]
  String
);

§contains

Validates that the input contains a substring or subslice.

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).

use wary::Wary;

#[derive(Wary)]
struct Card(
  #[validate(credit_card)]
  String
);

§custom

Validates the input with a custom Rule.

use wary::Wary;
use wary::toolbox::rule::*;

struct SecretRule;

impl SecretRule {
  fn new() -> Self {
    Self
  }
}

impl<I> wary::Rule<I> for SecretRule
where
  I: AsRef<str>,
{
  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,
}

§dive

Validates the inner fields of a struct or enum.

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.

use wary::Wary;

#[derive(Wary)]
struct Email(
  #[validate(email)]
  String
);

§equals

Validates that the input is equal to a value. Currently does not support self fields.

use wary::Wary;

#[derive(Wary)]
struct Name(
  #[validate(equals(other = "John"))]
  String
);

§func

Validates the input with a function.

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,
}

§inner

Validates the inner fields of a slice-like type.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[validate(inner(ascii))]
  items: Vec<String>,
}

§length

Validates the length of the input.

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,
}

§lowercase

Validates that the input is lowercase.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[validate(lowercase)]
  left: String,
  #[validate(lowercase(ascii))]
  right: String,
}

§or

Meta-rule that combines multiple rules. Short-circuits on the first success.

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<I> wary::Rule<I> 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
}

let name = NameOr {
  value: 1,
};

let report = name.validate(&()).unwrap();

assert_eq!(DEBUG_COUNTER.load(Ordering::Relaxed), 0);

§prefix

Validates that the input starts with a substring or subslice.

use wary::Wary;

#[derive(Wary)]
struct Name(
  #[validate(prefix(str = "hello"))]
  String
);

§range

Validates that the input is within a range.

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.

use wary::Wary;

#[derive(Wary)]
struct Name(
  #[validate(regex(pat = "^[a-z]+$"))]
  String
);

§required

Validates that the input is not empty. For example, that an Option is Some or a Vec is not empty.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[validate(required)]
  first: String,
  #[validate(required)]
  last: Option<String>,
}

§semver (requires feature semver)

Validates that the input is a semver.

use wary::Wary;

#[derive(Wary)]
struct Version(
  #[validate(semver)]
  String
);

§suffix

Validates that the input ends with a substring or subslice.

use wary::Wary;

#[derive(Wary)]
struct Name(
  #[validate(suffix(str = "hello"))]
  String
);

§time (requires feature chrono or jiff)

Validates that the input time is within a range.

use wary::Wary;
use jiff::Zoned;

#[derive(Wary)]
struct Time(
  #[validate(time(after = Zoned::now()))]
  Zoned
);

§uppercase

Validates that the input is uppercase.

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.

use wary::Wary;

#[derive(Wary)]
struct Url(
  #[validate(url)]
  String
);

§uuid (requires feature uuid)

Validates that the input is a uuid.

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.

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 trait instead.

ruletraitfeaturedependency
customTransformer--
diveTransform--
lowercaseAsMut<str> (for ascii only)--
innerAsMutSlice--
uppercaseAsMut<str> (for ascii only)--

§custom

Transforms the input with a custom Transformer.

use wary::{Wary, Transformer};

struct SecretTransformer;

impl SecretTransformer {
  fn new() -> Self {
    Self
  }
}

impl Transformer<String> 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,
}

§dive

Transforms the inner fields of a struct or enum.

use wary::Wary;

#[derive(Wary)]
struct Item {
  #[transform(lowercase)]
  name: String,
}

#[derive(Wary)]
struct Name {
  #[transform(dive)]
  item: Item,
}

§lowercase

Transforms the input to lowercase.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[transform(lowercase)]
  left: String,
  #[transform(lowercase(ascii))]
  right: String,
}

§inner

Transforms the inner fields of a slice-like type.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[transform(inner(lowercase))]
  items: Vec<String>,
}

§uppercase

Transforms the input to uppercase.

use wary::Wary;

#[derive(Wary)]
struct Name {
  #[transform(uppercase)]
  left: String,
  #[transform(uppercase(ascii))]
  right: String,
}

§Implementing Transform manually

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");

§Async support

Wary supports async validation and transformation out of the box. This is useful for cases where a validation step may need to reach out to a database or an external service.

All traits have an async variant:

use wary::{Wary, AsyncWary, AsyncTransformer};

struct SecretTransformer;

impl SecretTransformer {
  const fn new() -> Self {
    Self
  }
}

impl AsyncTransformer<String> for SecretTransformer {
  type Context = ();

  async fn transform_async(&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_async(secret))]
  name: String,
}

#[pollster::main]
async fn main() {
  let mut person = Person {
    name: "hello".into(),
  };

  person.wary_async(&()).await;

  assert_eq!(person.name, "secret");
}

Re-exports§

pub use error::Error;
pub use error::Report;
pub use options::rule::length::Length;
pub use options::rule::range::Compare;

Modules§

error
options
toolbox
A collection of common imports for various use-cases.

Macros§

init_regex

Traits§

AsMut
Trait for cheap mutable-to-mutable reference conversion.
AsMutSlice
Trait for cheap reference-to-slice conversion with mutability.
AsRef
Trait for cheap reference-to-reference conversion.
AsSlice
Trait for cheap reference-to-slice conversion.
AsyncRule
AsyncTransform
AsyncTransformer
AsyncValidate
AsyncWary
Rule
Trait for validating other data.
Transform
Trait for transforming itself.
Transformer
Trait for transforming other data.
Validate
Trait for validating itself.
Wary
Trait for validating and transforming data.

Derive Macros§

AsRef
Wary