prae 0.7.1

A crate that aims to provide a better way to define types that require validation
docs.rs failed to build prae-0.7.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: prae-0.8.4

What is prae?

This crate aims to provide a better way to define types that require validation. prae is not a validation library, but a library that helps developers to define validation-requiring types with very little effort.

How it works?

The main way to use prae is through define! macro.

For example, suppose you want to create a Username type. You want this type to be a string, and you don't want it to be empty. Traditionally, would create a wrapper struct with getter and setter functions, like this simplified example:

#[derive(Debug)]
pub struct Username(String);

impl Username {
pub fn new(username: &str) -> Result<Self, &'static str> {
let username = username.trim().to_owned();
if username.is_empty() {
Err("value is invalid")
} else {
Ok(Self(username))
}
}

pub fn get(&self) -> &str {
&self.0
}

pub fn set(&mut self, username: &str) -> Result<(), &'static str> {
let username = username.trim().to_owned();
if username.is_empty() {
Err("value is invalid")
} else {
self.0 = username;
Ok(())
}
}
}

let username = Username::new(" my username ").unwrap();
assert_eq!(username.get(), "my username");

let err = Username::new("  ").unwrap_err();
assert_eq!(err, "value is invalid");

Using prae, you will do it like this:

use prae::define;

define! {
pub Username: String
adjust |username| *username = username.trim().to_owned()
ensure |username| !username.is_empty()
}

let username = Username::new(" my username ").unwrap();
assert_eq!(username.get(), "my username");

let err = Username::new("  ").unwrap_err();
assert_eq!(err.inner, "value is invalid");
assert_eq!(err.value, "");

Futhermore, prae allows you to use custom errors and extend your types. See docs for define! for more information and examples.

Additional features

*_unprocessed functions

By default, all methods of the wrappers generated by define! (which are just aliases of the Bounded type) will run the adjustment/validation (if present) routines on every construction and mutation.

If you find yourself in a situation where you know for sure that some construction/mutation is valid, you can opt out of this using *_unprocessed functions (e.g. foo.set_unprocessed(value) instead of foo.set(value)) and save a bit of computations.

To be able to use these functions, just enable the unprocessed feature of the crate.

Serde integration

You can enable serde integration with the serde feature. It will implement Serialize and Deserialize traits for the wrappers if their inner type implements them. The deserialization will automatically fail if it contains invalid value. Here is an example:

use serde::{Deserialize, Serialize};
use prae::define;

define! {
Username: String
adjust   |username| *username = username.trim().to_string()
validate(&'static str) |username| {
if username.is_empty() {
Err("username is empty")
} else {
Ok(())
}
}
}

#[derive(Debug, Deserialize, Serialize)]
struct User {
username: Username,
}

// Serialization works as expected.
let u = User {
username: Username::new("  john doe  ").unwrap(),
};
let j = serde_json::to_string(&u).unwrap();
assert_eq!(j, r#"{"username":"john doe"}"#);

// Deserialization with invalid data fails.
let e = serde_json::from_str::<User>(r#"{ "username": "  " }"#).unwrap_err();
assert_eq!(e.to_string(), "username is empty at line 1 column 20");

// And here we get a nice adjusted value.
let u = serde_json::from_str::<User>(r#"{ "username": "  john doe  " }"#).unwrap();
assert_eq!(u.username.get(), "john doe");

Drawbacks

Although proc macros are very powerful, they aren't free. In this case, you have to pull up additional dependencies such as syn and quote, and expect a slightly slower compile times.