Expand description
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.
Macros
A macro that makes defining new types easy.
Structs
A thin wrapper around an inner type that utilizes Bound
B
to ensure the inner value is always valid.
An error that occurs when bounded type is constructed with a value that isn’t valid. It contains some inner error that describes the reason for the error as well as the value that caused the error.
An error that occurs when bounded type is mutated in a way so that it’s inner value isn’t valid anymore. It contains some inner error that describes the reason for the error as well as both the value before mutation and the value after mutation (that caused the error).
Traits
A trait that describes a type, a process through which the type’s value must go on every construction and mutation, and a type of an error that can occur during the process.
Convenience trait that allows mapping from Result<_, ConstructionError<Bound>>
and Result<_, MutationError<Bound>
to
Result<_, Bound::Error>
.