Skip to content

Nutype 0.5.0 with custom errors

Latest
Compare
Choose a tag to compare
@greyblake greyblake released this 03 Sep 07:14
· 21 commits to master since this release
aad0699

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