Changes
- [FEATURE] Added support for custom error types and validation functions via the
error
andwith
attributes. - [BREAKING] Replaced
lazy_static
withstd::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 ofstd::sync::LazyLock
. If upgrading Rust isn't an option, you can still uselazy_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!