Code Monkey home page Code Monkey logo

nutype's Introduction

Rust Nutype Logo

The newtype with guarantees.

Nutype Build Status Nutype Documentation

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 `::new()`.
// Note that Username holds a sanitized string
assert_eq!(
    Username::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::new("   "),
    Err(UsernameError::NotEmptyViolated),
);
assert_eq!(
    Username::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
  • Integer (u8, u16,u32, u64, u128, i8, i16, i32, i64, i128, usize, isize)
  • Float (f32, f64)
  • Anything else

String

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

String sanitizers

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 }

String validators

Validator Description Error variant Example
len_char_min Min length of the string (in chars, not bytes) LenCharMinViolated len_char_min = 5
len_char_max Max length of the string (in chars, not bytes) LenCharMaxViolated len_char_max = 255
not_empty Rejects an empty string NotEmptyViolated not_empty
regex Validates format with a regex. Requires regex feature. RegexViolated regex = "^[0-9]{7}$" or regex = ID_REGEX
predicate Custom validator. A function or closure that receives &str and returns bool PredicateViolated predicate = |s: &str| s.contains('@')

Regex validation

Requirements:

  • regex feature of nutype is enabled.
  • You have to explicitly include regex and lazy_static as dependencies.

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

Sanitizer Description Example
with Custom sanitizer. with = |raw| raw.clamp(0, 100)

Integer validators

Validator Description Error variant Example
less Exclusive upper bound LessViolated less = 100
less_or_equal Inclusive upper bound LessOrEqualViolated less_or_equal = 99
greater Exclusive lower bound GreaterViolated greater = 17
greater_or_equal Inclusive lower bound GreaterOrEqualViolated greater_or_equal = 18
predicate Custom predicate PredicateViolated predicate = |num| num % 2 == 0

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

Sanitizer Description Example
with Custom sanitizer. with = |val| val.clamp(0.0, 100.0)

Float validators

Validator Description Error variant Example
less Exclusive upper bound LessViolated less = 100.0
less_or_equal Inclusive upper bound LessOrEqualViolated less_or_equal = 100.0
greater Exclusive lower bound GreaterViolated greater = 0.0
greater_or_equal Inclusive lower bound GreaterOrEqualViolated greater_or_equal = 0.0
finite Check against NaN and infinity FiniteViolated finite
predicate Custom predicate PredicateViolated predicate = |val| val != 50.0

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

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, Deref, AsRef),
    sanitize(with = |mut guests| { guests.sort(); guests }),
    validate(predicate = |guests| !guests.is_empty() ),
)]
pub struct GuestList(Vec<String>);

// Empty list is not allowed
assert_eq!(
    GuestList::new(vec![]),
    Err(GuestListError::PredicateViolated)
);

// Create the list of our guests
let guest_list = GuestList::new(vec![
    "Seneca".to_string(),
    "Marcus Aurelius".to_string(),
    "Socrates".to_string(),
    "Epictetus".to_string(),
]).unwrap();

// The list is sorted (thanks to sanitize)
assert_eq!(
    guest_list.as_ref(),
    &[
        "Epictetus".to_string(),
        "Marcus Aurelius".to_string(),
        "Seneca".to_string(),
        "Socrates".to_string(),
    ]
);

// Since GuestList derives Deref, we can use methods from `Vec<T>`
// due to deref coercion (if it's a good idea or not, it's left up to you to decide!).
assert_eq!(guest_list.len(), 4);

for guest in guest_list.iter() {
    println!("{guest}");
}

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

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

  • arbitrary - enables derive of arbitrary::Arbitrary.
  • new_unchecked - enables generation of unsafe ::new_unchecked() function.
  • regex - allows to use regex = validation on string-based types. Note: your crate also has to explicitly have regex and lazy_static within dependencies.
  • serde - integrations with serde crate. Allows to derive Serialize and Deserialize traits.
  • schemars08 - allows to derive JsonSchema trait of schemars crate. Note that at the moment validation rules are not respected.
  • std - enabled by default. Use default-features = false to disable.

When nutype is a good fit for you?

  • If you enjoy newtype pattern and you like the idea of leveraging the Rust type system to enforce the correctness of the business logic.
  • If you want to use type system to hold invariants
  • If you're a DDD fan, nutype is a great helper to make your domain models even more expressive.
  • You want to prototype quickly without sacrificing quality.

When nutype is not that good?

  • You care too much about compiler time (nutype relies on heavy usage of proc macros).
  • You think metaprogramming is too much implicit magic.
  • IDEs may not be very helpful at giving you hints about proc macros.
  • Design of nutype may enforce you to run unnecessary validation (e.g. on loading data from DB), which may have a negative impact if you aim for extreme performance.

A note about #[derive(...)]

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 a violation of the validation rules is excluded. The library takes a conservative approach and it has its downside: deriving traits that are not known to the library is not possible.

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

  • prae - A very similar crate that aims to solve the same problems but with slightly different approach.
  • bounded-integer - Bounded integers for Rust.
  • refinement - Convenient creation of type-safe refinement types (based on generics).
  • semval - Semantic validation for Rust.
  • validator - Simple validation for Rust structs (powered by macros).

License

MIT Β© Serhii Potapov

nutype's People

Contributors

danma3x avatar greyblake avatar jssblck avatar memark avatar sajjon avatar zperk13 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

nutype's Issues

Change `present` -> `not_empty`.

See the thread on reddit

Currently

Currently it's possible to use present validator on string-based types:

#[nutype(validate(present))]
pub struct Username(String);

It results into s.is_empty() check and returns UsernameError::Missing.

For many people it seems to be confusing, present is not immediately gives a clue what it's doing.

Suggestion

  • Rename present to not_empty
  • Rename Missing to Empty.

So the following:

#[nutype(validate(not_empty))]
pub struct Username(String);

may produce errors like UsernameError::Empty.

Add `finite` validation rule on f32,f64 types

Spec

  • For float based types implement finite validation rule, which will invoke is_finite under the hood.
  • Generate NotFinite error variant

Example

#[nutype(validate(finite))]
struct Weight(f64)

// valid
Weight::new(123.45);

// invalid
Weight::new(f64::NAN)
Weight::new(f64::INFINITY)

Best practice in Rust: "fallible new" vs TryFrom?

Firstly, 🀩 thank you so much - I think many folx have bits of this lying in our code bases!

Secondly, I am hoping to learn whether, in fact "fallible new" is the best practice in Rust today. I had come to understand that, for fallibe NewType wrappers*, the best practice was to impl TryFrom.

Why? I had understood (perhaps incorrectly) that:

  • Fallible TryFrom is supported out of the box by Serde
  • Fallible new is NOT supported by Serde (and similar tooling)
  • TryFrom is the most clear idiom when validation can fail in construction

Very interested in your thoughts, and to be clear I am asking a genuine question, not advocating for the TryFrom approach.

[Feature Request]: newtypes for arbitrary inner types

Currently

Currently #[nutype] macro can be applied only on predefined set of types: integers, floats, String.

It enables defining specific sanitizers/validators depending on the inner type.

Feature request

However, there is some demand to use #[nutype] for other (unknown to nutype) types.
It must be still possible to define custom sanitizers and validators with with = .

Related issues

Incompatibility with Ron crate prevents Nutype deserialization from RON format

The Rusty Object Notation crate provides a data serialization format. When a String-based Nutype that derives Serialize and Deserialize is serialized to RON, it cannot be deserialized back from RON. It produces an ExpectedString error during deserialization. I believe this may be due to the implementation of the Deserialize derive in Nutype. The following two tests demonstrate the issue.

#[cfg(feature = "serde")]
#[test]
fn test_ron_roundtrip_with_nutype() {
    #[nutype::nutype(derive(PartialEq, Debug, Serialize, Deserialize))]
    struct NuTypeString(String);

    let value = NuTypeString::new("foo");

    let serialized = ron::ser::to_string(&value).unwrap();
    println!("Serialized as:{}", serialized);
    let deserialized: Result<NuTypeString, _> = ron::de::from_str(&serialized);
    
    assert_eq!(Ok(value), deserialized);
}

#[cfg(feature = "serde")]
#[test]
fn test_ron_roundtrip_without_nutype() {
    #[derive(PartialEq, Debug, serde::Serialize, serde::Deserialize)]
    struct SerdeNewTypeString(String);

    let value = SerdeNewTypeString("foo".to_string());

    let serialized = serde_json::to_string(&value).unwrap();
    println!("Serialized as:{}", serialized);
    let deserialized: SerdeNewTypeString = serde_json::from_str(&serialized).unwrap();
    
    assert_eq!(value, deserialized);
}

When executed on current master (commit 748ce29), with Ron v0.8.1 and the serde "derive" feature enabled, test_ron_roundtrip_with_nutype fails and test_ron_roundtrip_without_nutype passes.

This is the output from test_ron_roundtrip_with_nutype:

Serialized as:("foo")
thread 'derives::test_ron_roundtrip_with_nutype' panicked at 'assertion failed: `(left == right)`
  left: `Ok(NuTypeString("foo"))`,
 right: `Err(SpannedError { code: ExpectedString, position: Position { line: 1, col: 1 } })`', test_suite\tests\string.rs:500:9
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library\std\src\panicking.rs:593
   1: core::panicking::panic_fmt
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library\core\src\panicking.rs:67
   2: core::fmt::Arguments::new_v1
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library\core\src\fmt\mod.rs:311
   3: core::panicking::assert_failed_inner
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library\core\src\panicking.rs:274
   4: core::panicking::assert_failed<enum2$<core::result::Result<string::derives::test_ron_roundtrip_with_nutype::__nutype_private_NuTypeString__::NuTypeString,ron::error::SpannedError> >,enum2$<core::result::Result<string::derives::test_ron_roundtrip_with_nuty
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26\library\core\src\panicking.rs:228
   5: string::derives::test_ron_roundtrip_with_nutype
             at .\tests\string.rs:500
   6: string::derives::test_ron_roundtrip_with_nutype::closure$0
             at .\tests\string.rs:490
   7: core::ops::function::FnOnce::call_once<string::derives::test_ron_roundtrip_with_nutype::closure_env$0,tuple$<> >    
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26\library\core\src\ops\function.rs:250
   8: core::ops::function::FnOnce::call_once
             at /rustc/eb26296b556cef10fb713a38f3d16b9886080f26/library\core\src\ops\function.rs:250
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
test derives::test_ron_roundtrip_with_nutype ... FAILED

I believe this is because Ron wraps the serialized value in parentheses, while Nutype's derive implementation attemps to directly deserialize the whole thing as a string, expecing quotes to come first.

Comparing the generated code, we can see the differences between Nutype's Deserialize implementation and the one generated by serde:

Click to expand code
impl<'de> ::serde::Deserialize<'de> for NuTypeString {
    fn deserialize<D: ::serde::Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Self, D::Error> {
        let raw_value = String::deserialize(deserializer)?;
        Ok(NuTypeString::new(raw_value))
    }
}
// --- Nutype ↑ VS Serde ↓--- //
impl<'de> _serde::Deserialize<'de> for SerdeNewTypeString {
    fn deserialize<__D>(
        __deserializer: __D,
    ) -> _serde::__private::Result<Self, __D::Error>
    where
        __D: _serde::Deserializer<'de>,
    {
        #[doc(hidden)]
        struct __Visitor<'de> {
            marker: _serde::__private::PhantomData<SerdeNewTypeString>,
            lifetime: _serde::__private::PhantomData<&'de ()>,
        }
        impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
            type Value = SerdeNewTypeString;
            fn expecting(
                &self,
                __formatter: &mut _serde::__private::Formatter,
            ) -> _serde::__private::fmt::Result {
                _serde::__private::Formatter::write_str(
                    __formatter,
                    "tuple struct SerdeNewTypeString",
                )
            }
            #[inline]
            fn visit_newtype_struct<__E>(
                self,
                __e: __E,
            ) -> _serde::__private::Result<Self::Value, __E::Error>
            where
                __E: _serde::Deserializer<'de>,
            {
                let __field0: String = match <String as _serde::Deserialize>::deserialize(
                    __e,
                ) {
                    _serde::__private::Ok(__val) => __val,
                    _serde::__private::Err(__err) => {
                        return _serde::__private::Err(__err);
                    }
                };
                _serde::__private::Ok(SerdeNewTypeString(__field0))
            }
            #[inline]
            fn visit_seq<__A>(
                self,
                mut __seq: __A,
            ) -> _serde::__private::Result<Self::Value, __A::Error>
            where
                __A: _serde::de::SeqAccess<'de>,
            {
                let __field0 = match match _serde::de::SeqAccess::next_element::<
                    String,
                >(&mut __seq) {
                    _serde::__private::Ok(__val) => __val,
                    _serde::__private::Err(__err) => {
                        return _serde::__private::Err(__err);
                    }
                } {
                    _serde::__private::Some(__value) => __value,
                    _serde::__private::None => {
                        return _serde::__private::Err(
                            _serde::de::Error::invalid_length(
                                0usize,
                                &"tuple struct SerdeNewTypeString with 1 element",
                            ),
                        );
                    }
                };
                _serde::__private::Ok(SerdeNewTypeString(__field0))
            }
        }
        _serde::Deserializer::deserialize_newtype_struct(
            __deserializer,
            "SerdeNewTypeString",
            __Visitor {
                marker: _serde::__private::PhantomData::<SerdeNewTypeString>,
                lifetime: _serde::__private::PhantomData,
            },
        )
    }
}

I'm not certain exactly what serde's impl is doing, but somehow it handles the parentheses at the start, where Nutype's doesn't. This prevented me from fully using a Nutype-based struct as persistent data while using egui, which uses RON to persist data on disk. I instead ended up manually converting to/from a string when interacting with my persistence struct, losing some of the benefits Nutype provides.

Allow to pass `mut` with callback to sanitize without spacifing full type

Currently:

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

We want also to be able to do:

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

without specyfing Vec<String> explcitly

Add examples

Add directory examples with the most common examples.

Use Expr for default values instead of TokenStream

Context

Currently we use maybe_default_value: Option<TokenStream> to keep the value of default attribute.

Specs

  • Use syn::Expr instead of TokenStream to keep the value of default attribute

Acceptance Criteria

All the current tests pass without changes.

Rewrite parse logic for float types with syn::parser::Parse

Context

At the moment we're rewriting parsing logic.
The parsing was already rewritten for

Spec

  • Get familiar with the PRs above. Use the for inspiration.

In nutype_macros/src/float/parse.rs file:

  • Implement Parse (from syn) for SpannedFloatValidator<T>
  • Implement Parse for SpannedFloatSanitizer<T>
  • Reimplement fn parse_attributes in terms of ParseableAttributes from nutype_macros/src/common/parse/mod.rs

When you need to parse a float please introduce

pub fn parse_float<T: FromStr>(input: ParseStream) -> syn::Result<(T, Span)> {
    parse_number::<T, LitFloat>(input)
}

function in common/parse/mod.rs, next to fn parse_integer

  • Remove all the dead code after the refactoring
  • Fix tests (at least we'll need to add , in many places)
  • Fix the examples in nutype/src/lib.rs
  • DO NOT fix examples in README (yet). It will confuse users on github, because they will try the examples against the latest published version, which as a bit different syntax.

Acceptance Criteria

  • All the tests pass with make test

[Bug] Unknown validation rule `not_empty`

Context

Hello! I've created a brand new project, and tried to use nutype in it, but faced an issue when trying to use validators

Problem

I've created a brand new project (with cargo new my-crate --lib), and added nutype=0.1.1 dependency, and tried the following piece of code:

// lib.rs
pub mod video {
    use nutype::nutype;

    #[nutype(
        sanitize(trim)
        validate(min_length = 1, max_length = 100)
    )]
    pub struct VideoTitle(String);
}

This piece of code generates the following error:

error: Unknown validation rule `min_length`
 --> lib/my-crate/src/lib.rs:6:18
  |
6 |         validate(min_length = 1, max_length = 100)
  |                  ^^^^^^^^^^

This error is also reached when using any other validator (not_empty...)

Additional information

Cargo version: 1.66.1
Nutype version: 0.1.1

Suggestion: Add `#[inline(always)]` to most derives

Since most derives don't really contain any logic, it would be best to mark then as #[inline] or #[inline(always)] to allow them to be easily optimized away across crates and make them zero cost abstractions.

If you are interested I could work on a PR.

Allow derive(Eq, Ord) on float based types

It depends on #27

Spec

  • Enable deriving of Eq and Ord for float-based types, which has finite validation set.
  • Eq must be based on PartialEq, Ord must be based on PartialOrd
  • If finite validation is not set, but user tries to derive Eq or `Ord then show a helpful explaining message.

Example

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

[BREAKING] Remove derive(*)

It was controversial experiment from the very beginning.
At this point we want to get rid of it.

See also:
https://www.reddit.com/r/rust/comments/14j7w46/comment/jpzag6f/?utm_source=reddit&utm_medium=web2x&context=3

Context

At the moment in version 0.3.1 it's possible to derive traits with #[derive(*)], where * means traits that make sense "by default".

You can find those traits be search for fn unfold_asterisk_traits in the code.

Spec

  • In crate nutype_macros
  • Remove code related to parsing * in derive and the logic that unfolds it.
  • Generate a friendly message at compile time, saying that #[derive(*)] is no longer possible.
  • Adjust all the tests that are necessary (run all tests with make test)
  • Add a note in the CHANGELOG about the breaking change

Acceptance Criteria

I have the following code:

#[nutype()]
#[derive(*)]
pub struct Name(String);

It does not compile. Th error message points to

#[derive(*)]
         ^

The same applies if the inner type is integer of float.

Add meaningful error messages

Without using nutype, I use the thiserror crate to create Error types and give them relevant error messages:

#[derive(Error, Debug, PartialEq)]
pub enum PackageVersionError {
    #[error(
        "Package version length must be between {} and {} characters",
        PackageVersion::MIN_LENGTH,
        PackageVersion::MAX_LENGTH
    )]
    InvalidLength,
    #[error("Package version may not contain whitespace")]
    ContainsWhitespace,
    #[error("Package version may not contain any control characters")]
    ContainsControlChars,
    #[error(
        "Package version may not contain any of the following characters: {:?}",
        PackageVersion::DISALLOWED_CHARACTERS
    )]
    DisallowedCharacters,
}

Currently, trying to achieve a similar thing with nutype will just return error messages like "too short". This isn't helpful as I show the error to the user.

#[nutype(
    sanitize(trim),
    validate(
        not_empty,
        char_len_min = 4,
        char_len_max = 128,
    ),
    default = "",
    derive(AsRef, Clone, Default, Deref, Display, FromStr, Deserialize, Serialize),
)]

Ideally, I'd like for nutype's error to instead say something like Value is too short. The value length must be between 4 and 128 characters, or the same message but have a value name, such as Package version in my case.

Following type does not work

Hi, nice crate!

I tried to use it with something like:
MyRequest(http::Request<Option>)

And build failed with an unsupported message. Is that right?

Thanks,
H.

Refactor: match_feature

From #39 (comment)

Introduce match_feature! macro:

let msg = match_feature!("foo",
    on => "Foo is on!",
    off => "Foo is off",
);

Refactor the existing code to use it.

[BREAKING] Rename min_len and max_len on String-based types

See https://www.reddit.com/r/rust/comments/14j7w46/nutype_030_released/

Term len in context of String has very strong association with String::len() which returns a length of string in bytes.
However, practically in most of the common scenarios strings needs to be validated in terms of chars with str.chars().count().

To avoid the confusion min_len and max_len validators on String-based types have to be renamed.

Candidates:

  • min_chars, max_chars

Respect constant names in validation rules

As for now it's not possible to use constant name in the most of validation rules.

Examples

use nutype::nutype;

const NAME_MIN_LEN: usize = 3;

#[nutype(
    sanitize(trim)
    validate(min_len = NAME_MIN_LEN)
)]
pub struct Name(String);
previous errors
error: Expected usize, got `NAME_MIN_LEN`
  --> src/main.rs:12:24
   |
12 |     validate(min_len = NAME_MIN_LEN)

Or

const AMOUNT_MAX: f64 = 42.0;

#[nutype(
    validate(max = 42.0)
)]
pub struct Amount(f64);
error: Expected f64, got `AMOUNT_MAX`
  --> src/main.rs:11:20
   |
11 |     validate(max = AMOUNT_MAX)

Reasons

The original reason for this was to be able to validate correctness of the given values at compile time.

For example, if min is greater than max, the following example does not compile:

#[nutype(
    validate(max = 10, min = 20)
)]
pub struct Number(i32)
error: `min` cannot be greater than `max`.
       Sometimes we all need a little break.
 --> src/main.rs:9:14
  |
9 |     validate(max = 10, min = 20)
  |              ^^^

Desired

  • Allow specifying the values for the validation rules as named constants
  • Keep the validation, if it can be executed at compile time
  • Consider generating extra tests to ensure the the dynamic (named constant) values still make sense at runtime.
  • Use this trick to ensure that max is greater that min: TedDriggs/darling#244 (comment)

DRY logic in the module generation

Context

At the moment we support 3 type families: integers, floats and String.
Recently Newtype trait was introduced, which formalizes parse -> validate -> generate flow.
The generation logic is handled individually by each single type in gen modules, by the following functions:

Those functions are very similar and have a similar structure.
Since we're going to add new type families in the nearest future we want to find a way to avoid those duplications and DRY them.

Specs

  • Find a way to DRY the duplications (consider higher order functions, traits with template methods, or something else

Acceptance Criteria

  • The tests remain untouched and pass
  • The code is restructured in the way that adding a new type family will require writing less code for the generation logic.

Feature request: Add derived implementations for `num` traits

Although num traits are usually implemented rather than derived, the num-derive crate shows that it is possible to write derive macros for them even for types more complex than newtype constructors.

In fact, with the constraints this crate can offer on new types, it would be possible to even derive traits like num::Bounded.

[Feature Request]: string validation with regex

It was requested a few times on reddit and also stays in my TODO/WORKLOG.

Syntax

#[nutype(
    validate(
        regex = "^\w+@\w(\.\w)+$"
    )
)]
pub struct Username(String);

Alternative syntax could be with // like in scripting languages:

regex = /^\w+@\w(\.\w)+$/

If you're reading this, please share your opinion.

Crates

  • regex
  • lazy-regex - seems to be based on top of regex. Can be very helpful, since the regex is check at compile time

Concerns

Will it play well with potential support of Arbitrary?
I think rand_regex can be helpful here.

String len validation: bytes VS chars

Currently len validation is based str::len() which returns length in bytes.
It can be sometimes desired to validate length against UTF-8 chars, which shell be obtained through

let utf8_len = str_value.chars().count();

Opt-in for `new_unchecked`

First, thanks for such a nice crate!

Feature request

We do use the very similar approach in our codebases, and this crate may strip a lot of boilerplate in our projects. However, it lacks one feature we do often use: generating unsafe pub fn new_unchecked() -> Self. It would be nice to be able to opt-in for it via some attribute argument, like: #[nutype(unchecked)] for example.

Motivation

There are 2 main situations where we use new_unchecked() in our codebases. Both are related to the situations where validation represents quite a heavy operation:

  1. Reading a value from database in FromSql implementation, where the type invariants are guaranteed by database constraints, thus no need to perform heavy validation.
  2. From conversion between the same types declared on different layers (like GraphQL API layer <-> domain layer), thus no need to perform the validation again once it's already guaranteed by another type.

Support Arbitrary for `any` types

Specs

  • Add ArbitraryArbitrary variant to AnyDeriveTrait enum.
  • Adjust to_any_derive_trait() validation function to return AnyDeriveTrait::ArbitraryArbitrary
  • Add module nutype_macros/src/any/gen/traits/arbitrary.rs
  • Implement there a derive of ::arbitrary::Arbitrary crate
    • If type has custom validation set with predicate = , then it should return a compile error, saying that derive of Arbitrary is not possible
    • If inner type implements Arbitrary it should work (though, it's not possible to know at the compile time if the inner type implements Arbitrary`
  • Create a new example in any_arbitrary that demos derive of Arbitrary. See integer_arbitrary for example.

Move derive into nutype attributes

Context

At the moment it's possible to derive traits by using the regular #[derive] macro. For example:

#[nutype(validate(not_empty))]
#[derive(Debug)]
pub struct Name(String);

The initial idea behind this was to give a "natural" syntax/feeling to Rust developers.
In reality however, nutype fully controls #[derive()]: it parses it, and generates code. It's not done by the standard derive macro.

Note also that the following will not compile:

#[derive(Debug)]
#[nutype(validate(not_empty))]
pub struct Name(String);

Because in this case, nutype has no notion about derive and the standard derive macro is applied on tuple struct with private inner value, which cannot be access directly.
However, people got confused sometimes, thinking that the 2 snippets above are equal.

Motivation

To eliminate confusion and make it clear for the library users, we want to move derive macro into set of nutype attributes, e.g.:

#[nutype(
    validate(not_empty),
    derive(Debug)
)]
pub struct Name(String);

Spec

  • Add a new field to ParseableAttributes : derive_traits: Vec<SpannedItem<DeriveTrait>>,
  • Update impl Parse for ParseableAttributes to parse derive into derive_traits, for inspiration take the current implementation in fn parse_ident_into_derive_trait
  • Adjust the tests (to run all possible tests run make test)
  • When #[derive(...)] is used outside of #[nutype(...)] return a compilation error, explaining how to use derive correctly. Add a compiletests to test the error message.
  • Update CHANGELOG.md with the information about the breaking change. Do not update README (will do it before the release).

Acceptance Criteria

  1. The following code should compile and Name should have Debug implemented:
#[nutype(
    validate(not_empty),
    derive(Debug)
)]
pub struct Name(String)
  1. The following code should result into an error message with an example how to use derive(..) properly:
#[nutype(
    validate(not_empty)
)]
#[derive(Debug)]
pub struct Name(String)

Unify the error types in the nutype package

Hi,

I'm writing to you today to suggest that you consider unifying the error types for each struct in the nutype package. Currently, each struct has its own enum error type, which can make it difficult to track and handle errors.

I propose that we unify the error types into a single error type called ValidateError. This would make it easier to track and handle errors, as we would only need to handle a single error type instead of multiple error types.

I believe that this change would be a valuable improvement to the nutype package. Thank you for your time and consideration.

Rename error variants to have Violated postfix

Context

With more validators on the way, we need to have a simple way for users to draw a connection between a validation rule and error variant

Spec

Rename all the validation error variants to match the following formula: <ValidationRule>Violated.

Example:

  • char_len_max is currently results into error variant TooLong. Should be CharLenMaxViolated
  • not_empty results into Empty. Should be NotEmptyViolated.

Make clippy ignore the same postfix if necessary.

Update CHANGELOG and docs in lib.rs. Don't touch README yet.

Ability to derive `fake-rs`'s `Dummy` trait

fake-rs is commonly used to generate fake data for testing purposes. Any struct that implements the Dummy trait can easily be generated with fake data. They also have a derive macro to derive the impl of the Dummy trait for a particular struct.

It would be super cool to have fake-rs available under a feature flag to easily impl the Dummy trait for a nutype struct.

If you are open to having this added to nutype I would be happy to create a PR. I would just need to be pointed in the correct direction to get started.

Rename validator `with =` to `predicate = `

Context

Currently it's possible to use for validation a custom function that returns either true or false. This function or closure is specified with with = attribute.
In this issue we want it to rename to predicate =

Motivation

  1. It's natural to call predicate things that return bool
  2. It will enable us to introduce custom with = function, which instead of bool will return any variant of error, so users may have more control the validation logic.

Specs

  • Rename validation rule for all type families (integer, float, string) from with to predicate
  • Do not rename with = sanitizer
  • Adjust the tests correspondingly (to run all suite make test)
  • Handle with = validation attribute by returning an error, saying that it's renamed into predicate = . Add an ui test for this.
  • Add a note about breaking change in CHANGELOG.md

Acceptance Criteria

  1. The following compiles
#[nutype(validate(predicate = |x| x % 2 == 0))]
pub struct Even(u32);
  1. The following fails ,saying that with = was renamed to predicate =
#[nutype(validate(with = |x| x % 2 == 0))]
pub struct Even(u32);

Considering `try_` prefix for creating nutype objects

Hello! =)

Thanks your awesome work on nutype, it has been very helpful!

I was thinking, since the new() method can fail, this could be better described by adding a try_ prefix (e.g. try_new()).

Since Rust already has the TryFrom trait (had a little trouble implementing it myself), I'd like to suggest that to be the de-facto way to create nutypes objects.

What do you think?

Feature request: enable setting custom default values

Currently

nutype enables users to derive several derivable traits. However deriving Default yields an error (unsupported trait derive: Default).

Feature Request

  • enable nutypes to handle/derive the Default trait as expected.
  • implement another annotation (alongside validate and sanitize) like default = , which enables users to specify a default value and/or the name of a function that yields the default value.

Workaround

It is possible to manually implement the Default trait on a nutype struct.
This is functionally sufficient. Just not quite elegant, compared to what nutypes annotations might enable us to do. :)

Considerations/constraints

The thing to keep in mind is that the default value should not be able to violate the promises of the nutype validation. So presumably we need to be ensured this at compile time.
I can't estimate how difficult a constraint that is to implement. But I thought I'd mention it immediately.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.