Skip to content

Releases: greyblake/nutype

Nutype 0.5.0 with custom errors

03 Sep 07:14
aad0699
Compare
Choose a tag to compare

Changes

  • [FEATURE] Added support for custom error types and validation functions via the error and with attributes.
  • [BREAKING] Replaced lazy_static with std::sync::LazyLock for regex validation. This requires Rust 1.80 or higher and may cause compilation issues on older Rust versions due to the use of std::sync::LazyLock. If upgrading Rust isn't an option, you can still use lazy_static explicitly as a workaround.
  • [BREAKING] The fallible ::new() constructor has been fully replaced by ::try_new().

Highlights

Custom errors

Previously, custom validation logic in nutype could be achieved by passing a predicate attribute, as shown below:

#[nutype(validate(predicate = |n| n % 2 == 1))]
struct OddNumber(i64);

This would automatically generate a simple error type:

enum OddNumberError {
    PredicateViolated,
}

However, this approach often lacked flexibility. Many users needed more detailed error handling. For example, some users wanted to attach additional information to errors or provide more descriptive error messages. Others preferred to use a single error type across their application, but found it cumbersome to map very specific errors to a more general error type.

To address these needs, nutype now introduces the with attribute for custom validation functions and the error attribute for specifying custom error types:

use nutype::nutype;

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

// Custom error type for `Name` validation.
// You can use `thiserror` or similar crates to provide more detailed error messages.
#[derive(Debug, PartialEq)]
enum NameError {
    TooShort { min: usize, length: usize },
    TooLong { max: usize, length: usize },
}

// Validation function for `Name` that checks its length.
fn validate_name(name: &str) -> Result<(), NameError> {
    const MIN: usize = 3;
    const MAX: usize = 10;
    let length = name.encode_utf16().count();

    if length < MIN {
        Err(NameError::TooShort { min: MIN, length })
    } else if length > MAX {
        Err(NameError::TooLong { max: MAX, length })
    } else {
        Ok(())
    }
}

With this enhancement, users have full control over the error type and how errors are constructed during validation, making the error handling process more powerful and adaptable to different use cases.

Transition from fallible::new() to ::try_new()

In version 0.4.3, the fallible ::new() constructor was deprecated but still available. Now, it has been fully replaced by ::try_new(). For example, to initialize a Name from the previous example:

let name = Name::try_new("Anton").unwrap();

This change ensures a more consistent and explicit error-handling approach when creating instances.
Note that ::new() is still used as a non-fallible constructor if there a newtype has no validation.

The sponsors ❀️

A big shoutout to the true sponsors of this release - my in-laws! Thanks for taking care of my wife and kid, giving me a free weekend to work on Nutype!

Links

Nutype 0.4.3

08 Jul 07:08
Compare
Choose a tag to compare

Changes

  • Support generics
  • [DEPRECATION] Fallible (when a newtype has validation) constructor ::new() is deprecated. Users should use ::try_new() instead.
  • [FIX] Use absolute path for ::core::result::Result when generating code for derive(TryFrom).

Highlights

This release comes with support of generic types for newtypes!
The example below defines SortedNotEmptyVec<T> wrapper around Vec<T> which is guaranteed to be not empty and sorted.
Note, that type bound T: Ord enables invocation of v.sort() in the sanitization function.

use nutype::nutype;

#[nutype(
    sanitize(with = |mut v| { v.sort(); v }),
    validate(predicate = |vec| !vec.is_empty()),
    derive(Debug, PartialEq, AsRef),
)]
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"]);

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

Links

Nutype 0.4.2

10 Apr 06:14
Compare
Choose a tag to compare

Changes

  • Support no_std ( the dependency needs to be declared as nutype = { default-features = false } )
  • Support integration with arbitrary crate (see arbitrary feature).
    • Support Arbitrary for integer types
    • Support Arbitrary for float types
    • Support Arbitrary for string inner types
    • Support Arbitrary for any inner types
  • Possibility to specify boundaries (greater, greater_or_equal, less, less_or_equal, len_char_min, len_char_max) with expressions or named constants.
  • Add #[inline] attribute to trivial functions
  • Improve error messages

Highlights

Here is an example of nutype and arbitrary playing together:

use nutype::nutype;
use arbtest::arbtest;
use arbitrary::Arbitrary;

#[nutype(
    derive(Arbitrary, AsRef),
    sanitize(trim),
    validate(
        not_empty,
        len_char_max = 100,
    ),
)]
pub struct Title(String);

fn main() {
    arbtest(|u| {
        // Generate an arbitrary valid Title
        let title = Title::arbitrary(u)?;

        // The inner string is guaranteed to be non-empty
        assert!(!title.as_ref().is_empty());

        // The inner string is guaranteed not to exceed 100 characters
        assert!(title.as_ref().chars().count() <= 100);
        Ok(())
    });
}

As you can see the derived implementation of Arbitrary respects the validation rules.
In the similar way Arbitrary can be derived for integer and float based types.

Links

Nutype 0.4.0

21 Nov 20:24
Compare
Choose a tag to compare

Acknowledgements

A heartfelt thanks to Daniyil Glushko for his invaluable assistance and exceptional work on this release. Daniyil, located in Zaporizhzhia, Ukraine, is a proficient Rust developer open to remote opportunities. I highly recommend reaching out to him for Rust development roles.

Changes

  • [FEATURE] Support of arbitrary inner types with custom sanitizers and validators.
  • [FEATURE] Add numeric validator greater
  • [FEATURE] Add numeric validator less
  • [BREAKING] Removal of asterisk derive
  • [BREAKING] Use commas to separate high level attributes
  • [BREAKING] Traits are derived with #[nutype(derive(Debug))]. The regular #[derive(Debug)] syntax is not supported anymore.
  • [BREAKING] Validator with has been renamed to predicate to reflect the boolean nature of its range
  • [BREAKING] String validator min_len has been renamed to len_char_min to reflect that is based on UTF8 chars.
  • [BREAKING] String validator max_len has been renamed to len_char_max to reflect that is based on UTF8 chars.
  • [BREAKING] Rename numeric validator max to less_or_equal
  • [BREAKING] Rename numeric validator min to greater_or_equal
  • [BREAKING] Rename error variants to follow the following formula: <ValidationRule>Violated. This implies the following renames:
    • TooShort -> LenCharMinViolated
    • TooLong -> LenCharMaxViolated
    • Empty -> NotEmptyViolated
    • RegexMismatch -> RegexViolated
    • Invalid -> PredicateViolated
    • TooBig -> LessOrEqualViolated
    • TooSmall -> GreaterOrEqualViolated
    • NotFinite -> FiniteViolated
  • Better error messages: in case of unknown attribute, validator or sanitizer the possible values are listed.
  • [FIX] Make derived Deserialize work with RON format

Feature highlights

Arbitrary inner type

Previously #[nutype] worked only with String, integers and floats.
Now it's possible to use it with any arbitrary type (e.g. Vec<String>):

#[nutype(
    validate(predicate = |friends| !friends.is_empty() ),
)]
pub struct Frieds(Vec<String>);

New numeric validators

Instead of former min and max integers and floats can now be validated with:

  • greater_or_equal - Inclusive lower bound
  • greater - Exclusive lower bound
  • less_or_equal - Inclusive upper bound
  • less - Exclusive upper bound

Example:

#[nutype(
    validate(
        greater_or_equal = 0,
        less_or_equal = 59,
    ),
)]
pub struct Minute(u8);

Derive

Deriving of traits now has to be done explicitly with #[nutype(derive(...))] instead of #[derive(...)]:

Example:

#[nutype(
    validate(with = |n| n % 2 == 1),
    derive(Debug, Clone, Copy)
)]
pub struct OddNumber(u64);

This makes it clear, that deriving is fully handled by #[nutype] and prevents a potential confusion.

Links

Nutype 0.3.1

30 Jun 05:04
Compare
Choose a tag to compare

Changes

  • Add ability to derive Deref on String, integer and float based types.

Examples

use nutype::nutype;

#[nutype]
#[derive(Deref)]
struct Email(String);

let email = Email::new("foo@bar.com")

// Call .len() which is delegated to the inner String due to the deref-coercion mechanism
assert_eq!(email.len(), 11);

Nutype 0.3.0

25 Jun 09:55
Compare
Choose a tag to compare

Changes

  • [BREAKING] min_len and max_len validators run against number of characters in a string (val.chars().count()), not number of bytes (val.len()).
  • Add finite validation for float types which checks against NaN and infinity.
  • Support deriving of Default
  • Support deriving of Eq and Ord on float types (if finite validation is present)
  • Support deriving of TryFrom for types without validation (in this case Error type is std::convert::Infallible)

Feature Highlights:

  • Deriving Eq and Ord on f32 and f64 types: The new release addresses the limitation in Rust where f32 and f64 types cannot implement the Ord and Eq traits due to the presence of NaN values. Nutype introduces finite validation, which allows the correct implementation of Eq and Ord traits for float-based newtypes.
use nutype::nutype;

#[nutype(validate(finite))]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Distance(f64);
  • Deriving Default: Nutype 0.3.0 introduces support for deriving the Default trait. This allows users to derive the Default trait for their custom types with validation logic. Nutype also generates a unit test to ensure the validity of the default value.
use nutype::nutype;

#[nutype(
    validate(with = |n| n % 2 == 1)
    default = 1
)]
#[derive(Debug, Default)]
pub struct OddNumber(u64);

Please note that dynamic validation makes it impossible to guarantee the validity of the default value at compile time. Panics will occur if an invalid default value is obtained.

For more details, refer to the Nutype documentation.

Blog post: Nutype 0.3.0 released

Nutype 0.2.0

13 Apr 19:30
98aa30b
Compare
Choose a tag to compare

Changes

  • [BREAKING] Rename string validator present -> not_empty. Rename error variant Missing -> Empty.
  • [BREAKING] Rename feature serde1 to serde.
  • Support string validation with regex (requires regex feature).
  • Introduce new_unchecked feature flag, that allows to bypass sanitization and validation.
  • Support derive of JsonSchema of schemars crate (requires schemars08 feature).

Blog post

Please consider reading Nutype 0.2.0 is out! blog post, which covers in detail the regex and new_unchecked features.