Home

Awesome

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/greyblake/nutype/master/art/rust_nutype_inverted.png"> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/greyblake/nutype/master/art/rust_nutype.png"> <img width="300" alt="Rust Nutype Logo" src="https://raw.githubusercontent.com/greyblake/nutype/master/art/rust_nutype.png"> </picture> </p> <h2 align="center">The newtype with guarantees.</h2> <p align="center"> <a href="https://github.com/greyblake/nutype/actions/workflows/ci.yml" rel="nofollow"><img src="https://github.com/greyblake/nutype/actions/workflows/ci.yml/badge.svg" alt="Nutype Build Status"></a> <a href="https://docs.rs/nutype" rel="nofollow"><img src="https://docs.rs/nutype/badge.svg" alt="Nutype Documentation"></a> <a href="https://github.com/greyblake/nutype/discussions"><img src="https://img.shields.io/github/discussions/greyblake/nutype"/></a> <p>

Nutype is a proc macro that allows adding extra constraints like sanitization and validation to the regular newtype pattern. The generated code makes it impossible to instantiate a value without passing the checks. It works this way even with serde deserialization.

Quick start

use nutype::nutype;

// Define newtype Username
#[nutype(
    sanitize(trim, lowercase),
    validate(not_empty, len_char_max = 20),
    derive(Debug, PartialEq, Clone),
)]
pub struct Username(String);

// We can obtain a value of Username with `::try_new()`.
// Note that Username holds a sanitized string
assert_eq!(
    Username::try_new("   FooBar  ").unwrap().into_inner(),
    "foobar"
);

// It's impossible to obtain an invalid Username
// Note that we also got `UsernameError` enum generated implicitly
// based on the validation rules.
assert_eq!(
    Username::try_new("   "),
    Err(UsernameError::NotEmptyViolated),
);
assert_eq!(
    Username::try_new("TheUserNameIsVeryVeryLong"),
    Err(UsernameError::LenCharMaxViolated),
);

For more please see:

Inner types

Available sanitizers, validators, and derivable traits are determined by the inner type, which falls into the following categories:

String

At the moment the string inner type supports only String (owned) type.

String sanitizers

SanitizerDescriptionExample
trimRemoves leading and trailing whitespacestrim
lowercaseConverts the string to lowercaselowercase
uppercaseConverts the string to uppercaseuppercase
withCustom sanitizer. A function or closure that receives String and returns Stringwith = |mut s: String| ( s.truncate(5); s )

String validators

ValidatorDescriptionError variantExample
len_char_minMin length of the string (in chars, not bytes)LenCharMinViolatedlen_char_min = 5
len_char_maxMax length of the string (in chars, not bytes)LenCharMaxViolatedlen_char_max = 255
not_emptyRejects an empty stringNotEmptyViolatednot_empty
regexValidates format with a regex. Requires regex feature.RegexViolatedregex = "^[0-9]{7}$" or regex = ID_REGEX
predicateCustom validator. A function or closure that receives &str and returns boolPredicateViolatedpredicate = |s: &str| s.contains('@')
withCustom validator with a custom errorN/A(see example below)

Regex validation

Requirements:

There are a number of ways you can use regex.

A regular expression can be defined right in place:

#[nutype(validate(regex = "^[0-9]{3}-[0-9]{3}$"))]
pub struct PhoneNumber(String);

or it can be defined with std::sync::LazyLock:

use regex::Regex;

static PHONE_NUMBER_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("^[0-9]{3}-[0-9]{3}$").unwrap());

#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);

or it can be defined with lazy_static:

use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    static ref PHONE_NUMBER_REGEX: Regex = Regex::new("^[0-9]{3}-[0-9]{3}$").unwrap();
}

#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);

or once_cell:

use once_cell::sync::Lazy;
use regex::Regex;

static PHONE_NUMBER_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new("[0-9]{3}-[0-9]{3}$").unwrap());

#[nutype(validate(regex = PHONE_NUMBER_REGEX))]
pub struct PhoneNumber(String);

String derivable traits

The following traits can be derived for a string-based type: Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Deref, From, TryFrom, Into, Hash, Borrow, Display, Default, Serialize, Deserialize.

Integer

The integer inner types are: u8, u16,u32, u64, u128, i8, i16, i32, i64, i128, usize, isize.

Integer sanitizers

SanitizerDescriptionExample
withCustom sanitizer.with = |raw| raw.clamp(0, 100)

Integer validators

ValidatorDescriptionError variantExample
lessExclusive upper boundLessViolatedless = 100
less_or_equalInclusive upper boundLessOrEqualViolatedless_or_equal = 99
greaterExclusive lower boundGreaterViolatedgreater = 17
greater_or_equalInclusive lower boundGreaterOrEqualViolatedgreater_or_equal = 18
predicateCustom predicatePredicateViolatedpredicate = |num| num % 2 == 0
withCustom validator with a custom errorN/A(see example below)

Integer derivable traits

The following traits can be derived for an integer-based type: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Deref, Into, From, TryFrom, Hash, Borrow, Display, Default, Serialize, Deserialize.

Float

The float inner types are: f32, f64.

Float sanitizers

SanitizerDescriptionExample
withCustom sanitizer.with = |val| val.clamp(0.0, 100.0)

Float validators

ValidatorDescriptionError variantExample
lessExclusive upper boundLessViolatedless = 100.0
less_or_equalInclusive upper boundLessOrEqualViolatedless_or_equal = 100.0
greaterExclusive lower boundGreaterViolatedgreater = 0.0
greater_or_equalInclusive lower boundGreaterOrEqualViolatedgreater_or_equal = 0.0
finiteCheck against NaN and infinityFiniteViolatedfinite
predicateCustom predicatePredicateViolatedpredicate = |val| val != 50.0
withCustom validator with a custom errorN/A(see example below)

Float derivable traits

The following traits can be derived for a float-based type: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Deref, Into, From, TryFrom, Hash, Borrow, Display, Default, Serialize, Deserialize.

It's also possible to derive Eq and Ord if the validation rules guarantee that NaN is excluded. This can be done applying by finite validation. For example:

#[nutype(
    validate(finite),
    derive(PartialEq, Eq, PartialOrd, Ord),
)]
struct Size(f64);

Other inner types and generics

For any other type it is possible to define custom sanitizers with with and custom validations with predicate:

use nutype::nutype;

#[nutype(
    derive(Debug, PartialEq, AsRef, Deref),
    sanitize(with = |mut guests| { guests.sort(); guests }),
    validate(predicate = |guests| !guests.is_empty() ),
)]
pub struct GuestList(Vec<String>);

It's also possible to use generics:

#[nutype(
    sanitize(with = |mut v| { v.sort(); v }),
    validate(predicate = |vec| !vec.is_empty()),
    derive(Debug, PartialEq, AsRef, Deref),
)]
struct SortedNotEmptyVec<T: Ord>(Vec<T>);

let wise_friends = SortedNotEmptyVec::try_new(vec!["Seneca", "Zeno", "Plato"]).unwrap();
assert_eq!(wise_friends.as_ref(), &["Plato", "Seneca", "Zeno"]);
assert_eq!(wise_friends.len(), 3);

let numbers = SortedNotEmptyVec::try_new(vec![4, 2, 7, 1]).unwrap();
assert_eq!(numbers.as_ref(), &[1, 2, 4, 7]);
assert_eq!(numbers.len(), 4);

Custom sanitizers

You can set custom sanitizers using the with option. A custom sanitizer is a function or closure that receives a value of an inner type with ownership and returns a sanitized value.

For example, this one

#[nutype(sanitize(with = new_to_old))]
pub struct CityName(String);

fn new_to_old(s: String) -> String {
    s.replace("New", "Old")
}

is equal to the following one:

#[nutype(sanitize(with = |s| s.replace("New", "Old") ))]
pub struct CityName(String);

And works the same way:

let city = CityName::new("New York");
assert_eq!(city.into_inner(), "Old York");

Custom validators

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.

#[nutype(validate(predicate = is_valid_name))]
pub struct Name(String);

fn is_valid_name(name: &str) -> bool {
    // A fancy way to verify if the first character is uppercase
    name.chars().next().map(char::is_uppercase).unwrap_or(false)
}

Custom validation with a custom error type

To define your own error type and implement custom validation logic, you can combine the with and error attributes:

// Define a custom error type for validation failures.
// Although it's best practice to implement `std::error::Error` for custom error types,
// we are omitting that for simplicity here.
#[derive(Debug, PartialEq)]
enum NameError {
    TooShort,
    TooLong,
}

// Define a custom validation function for `Name`.
// The function returns `Result<(), NameError>`, where `Ok(())` indicates a valid name,
// and `Err(NameError)` represents a specific validation failure.
fn validate_name(name: &str) -> Result<(), NameError> {
    if name.len() < 3 {
        Err(NameError::TooShort)
    } else if name.len() > 10 {
        Err(NameError::TooLong)
    } else {
        Ok(())
    }
}

// Define a newtype `Name` with custom validation logic and custom error.
#[nutype(
    validate(with = validate_name, error = NameError),
    derive(Debug, PartialEq),
)]
struct Name(String);

It's important to ensure that the type specified in the error attribute matches the error type returned by the validation function.

Recipes

Derive Default

#[nutype(
    derive(Default),
    default = "Anonymous",
)]
pub struct Name(String);

Derive Eq and Ord on float types

With nutype it's possible to derive Eq and Ord if there is finite validation set. The finite validation ensures that the valid value excludes NaN.

#[nutype(
    validate(finite),
    derive(PartialEq, Eq, PartialOrd, Ord),
)]
pub struct Weight(f64);

Breaking constraints with new_unchecked

It's discouraged, but it's possible to bypass the constraints by enabling new_unchecked crate feature and marking a type with new_unchecked:

#[nutype(
    new_unchecked,
    sanitize(trim),
    validate(len_char_min = 8)
)]
pub struct Name(String);

// Yes, you're forced to use `unsafe` here, so everyone will point fingers at YOU.
let name = unsafe { Name::new_unchecked(" boo ".to_string()) };

// `name` violates the sanitization and validation rules!!!
assert_eq!(name.into_inner(), " boo ");

Feature flags

When nutype is a good fit for you?

When nutype is not that good?

Support Ukrainian military forces

Today I live in Berlin, I have the 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 lives" in Kharkiv, doing there daily duties. And some 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.

Similar projects

License

MIT © Serhii Potapov