Nutype embraces the simple idea: the type system can be leveraged to track the fact that something was done, so there is no need to do it again.
If a piece of data was once sanitized and validated we can rely on the types instead of sanitizing and validating again and again.
```rust use nutype::nutype;
sanitize(trim, lowercase)
validate(present, max_len = 20)
)] pub struct Username(String); ```
Now we can create usernames:
rust
assert_eq!(
Username::new(" FooBar ").unwrap().into_inner(),
"foobar"
);
But we cannot create invalid ones:
```rust assert_eq!( Username::new(" "), Err(UsernameError::Missing), );
assert_eq!( Username::new("TheUserNameIsVeryVeryLong"), Err(UsernameError::TooLong), ); ```
Note, that we also explicitly got UsernameError
enum generated.
Ok, but let's try to obtain an instance of Username
that violates the validation rules:
```rust let username = Username("".to_string())
// error[E0423]: cannot initialize a tuple struct which contains private fields ```
```rust let mut username = Username::new("foo").unwrap(); username.0 = "".to_string();
// error[E0616]: field 0
of struct Username
is private
```
Haha. It's does not seem to be easy!
Here are some other examples of what you can do with nutype
.
You can skip sanitize
and use a custom validator with
:
```rust
struct OddNumber(i64); ```
You can skip validation, if you need sanitization only:
```rust
struct Username(String); ```
In that case Username::new(String)
simply returns Username
, not Result
.
You can derive traits. A lot of traits! For example:
```rust
struct Username(String); ```
The code above derives the following traits for Username
: Debug
, Clone
, PartialEq
, Eq
, PartialOrd
, Ord
, FromStr
, AsRef
, Hash
.
*
is just a syntax sugar for "derive whatever makes sense to derive by default", which is very subjective and opinionated. It's rather an experimental feature that was born
from the fact that #[nutype]
has to mess with #[derive]
anyway, because users are not supposed to be able to derive traits like DerefMut
or BorrowMut
.
That would allow to mutate the inner (protected) value which undermines the entire idea of nutype.
Available sanitizers, validators and derivable traits are determined by the inner type, which falls into the following categories:
* String
* Integer (u8
, u16
,u32
, u64
, u128
, i8
, i16
, i32
, i64
, i128
, usize
, isize
)
* Float (f32
, f64
)
At the moment the string inner type supports only String
(owned) type.
| Sanitizer | Description | Example |
|-------------|-------------------------------------------------------------------------------------|-------------------------------------------------|
| trim
| Removes leading and trailing whitespaces | trim
|
| lowercase
| Converts the string to lowercase | lowercase
|
| uppercase
| Converts the string to uppercase | uppercase
|
| with
| Custom sanitizer. A function or closure that receives String
and returns String
| with = \|mut s: String\| { s.truncate(5); s }
|
| Validator | Description | Error variant | Example |
|-----------|---------------------------------------------------------------------------------|---------------|--------------------------------------|
| max_len
| Max length of the string | TooLong
| max_len = 255
|
| min_len
| Min length of the string | TooShort
| min_len = 5
|
| present
| Rejects an empty string | Missing
| present
|
| with
| Custom validator. A function or closure that receives &str
and returns bool
| Invalid
| with = \|s: &str\| s.contains('@')
|
The following traits can be derived for a string-based type:
Debug
, Clone
, PartialEq
, Eq
, PartialOrd
, Ord
, FromStr
, AsRef
, From
, TryFrom
, Into
, Hash
, Borrow
, Display
, Serialize
, Deserialize
.
The integer inner types are: u8
, u16
,u32
, u64
, u128
, i8
, i16
, i32
, i64
, i128
, usize
, isize
.
| Sanitizer | Description | Example |
|-----------|-------------------|------------------------------------|
| with
| Custom sanitizer. | with = \|raw\| raw.clamp(0, 100)
|
| Validator | Description | Error variant | Example |
|-----------|---------------------|---------------|-------------------------------|
| max
| Maximum valid value | TooBig
| max = 99
|
| min
| Minimum valid value | TooSmall
| min = 18
|
| with
| Custom validator | Invalid
| with = \|num\| num % 2 == 0
|
The following traits can be derived for an integer-based type:
Debug
, Clone
, Copy
, PartialEq
, Eq
, PartialOrd
, Ord
, FromStr
, AsRef
, Into
, From
, TryFrom
, Hash
, Borrow
, Display
, Serialize
, Deserialize
.
The float inner types are: f32
, f64
.
| Sanitizer | Description | Example |
|-----------|-------------------|----------------------------------------|
| with
| Custom sanitizer. | with = \|val\| val.clamp(0.0, 100.0)
|
| Validator | Description | Error variant | Example |
|-----------|---------------------|---------------|-------------------------------|
| max
| Maximum valid value | TooBig
| max = 100.0
|
| min
| Minimum valid value | TooSmall
| min = 0.0
|
| with
| Custom validator | Invalid
| with = \|val\| val != 50.0
|
The following traits can be derived for a float-based type:
Debug
, Clone
, Copy
, PartialEq
, PartialOrd
, FromStr
, AsRef
, Into
, From
, TryFrom
, Hash
, Borrow
, Display
, Serialize
, Deserialize
.
You can set custom sanitizers using option with
.
A custom sanitizer is a function or closure that receives a value of an inner type with ownership and returns a sanitized value back.
For example, this one
```rust
pub struct CityName(String);
fn newtoold(s: String) -> String { s.replace("New", "Old") } ```
is equal to the following one:
```rust
pub struct CityName(String); ```
And works the same way:
rust
let city = CityName::new("New York");
assert_eq!(city.into_inner(), "Old York");
In similar fashion it's possible to define custom validators, but a validation function receives a reference and returns bool
.
Think of it as a predicate.
```rust
pub struct Name(String);
fn isvalidname(name: &str) -> bool { // A fancy way to verify if the first character is uppercase name.chars().next().map(char::isuppercase).unwrapor(false) } ```
serde1
- integrations with serde
crate. Allows to derive Serialize
and Deserialize
traits.The following snippet
```rust
sanitize(trim, lowercase)
validate(present, max_len = 20)
)] pub struct Username(String); ```
eventually is transformed into something similar to this:
```rust // Everything is wrapped into module, // so the internal tuple value of Username is private and cannot be directly manipulated. mod nutypeprivateUsername { pub struct Username(String);
pub enum UsernameError {
// Occurres when a string is not present
Missing,
// Occurres when a string is longer than 255 chars.
TooLong,
}
impl Username {
// The only legit way to construct Username.
// All other constructors (From, FromStr, Deserialize, etc.)
// are built on top of this one.
pub fn new(raw_username: impl Into<String>) -> Result<Username, UsernameError> {
// Sanitize
let sanitized_username = raw_username.into().trim().lowercase();
// Validate
if sanitized_username.empty() {
Err(UsernameError::Missing)
} else if (sanitized_username.len() > 40 {
Err(UsernameError::TooLong)
} else {
Ok(Username(sanitized_username))
}
}
// Convert back to the inner type.
pub fn into_inner(self) -> String {
self.0
}
}
}
pub use nutypeprivateUsername::{Username, UsernameError}; ```
As you can see, #[nutype]
macro gets sanitization and validation rules and turns them into Rust code.
The Username::new()
constructor performs sanitization and validation and in case of success returns an instance of Username
.
The Username::into_inner(self)
allows to convert Username
back into the inner type (String
).
And of course the variants of UsernameError
are derived from the validation rules.
But the whole point of the nutype
crate is that there is no legit way to obtain an instance of Username
that violates the sanitization or validation rules.
The author put a lot of effort into this. If you find a way to obtain the instance of a newtype bypassing the validation rules, please open an issue.
You've got to know that the #[nutype]
macro intercepts #[derive(...)]
macro.
It's done on purpose to ensure that anything like DerefMut
or BorrowMut
, that can lead to violation of the validation rules is excluded.
The library takes a conservative approach and it has its downside: deriving traits which are not known to the library is not possible.
did you mean ...?
hintsDerefMut
and co cannot be derivedfinite
validator and allow to derive Eq
and Ord
regex
to validate string typesToday I live in Berlin, I have a luxury to live a physically safe life. But I am Ukrainian. The first 25 years of my life I spent in Kharkiv, the second-largest city in Ukraine, 60km away from the border with russia. Today about a third of my home city is destroyed by russians. My parents, my relatives and my friends had to survive the artillery and air attack, living for over a month in basements.
Some of them have managed to evacuate to EU. Some others are trying to live "normal lifes" in Kharkiv, doing there daily duties. And there are some who are at the front line right now, risking their lives every second to protect the rest.
I encourage you to donate to Charity foundation of Serhiy Prytula. Just pick the project you like and donate. This is one of the best known foundations, you can watch a little documentary about it. Your contribution to the Ukrainian military force is a contribution to my calmness, so I can spend more time developing the project.
Thank you.
MIT © Sergey Potapov