Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default #39

Merged
merged 5 commits into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,30 @@ jobs:
with:
toolchain: stable

- name: cargo test
- name: cargo test --features nutype_test
uses: actions-rs/cargo@v1
with:
command: test

- name: cargo test --features serde
- name: cargo test --features nutype_test,serde
uses: actions-rs/cargo@v1
with:
command: test
args: --features serde

- name: cargo test --features regex
- name: cargo test --features nutype_test,regex
uses: actions-rs/cargo@v1
with:
command: test
args: --features regex

- name: cargo test --features new_unchecked
- name: cargo test --features nutype_test,new_unchecked
uses: actions-rs/cargo@v1
with:
command: test
args: --features new_unchecked

- name: cargo test --features schemars08
- name: cargo test --features nutype_test,schemars08
uses: actions-rs/cargo@v1
with:
command: test
Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
### v0.3.0 - 2023-??-??
* [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.
* Enable deriving of `Eq` and `Ord` on float types (if `finite` validation is present)
* Enable deriving of `TryFrom` for types without validation (in this case Error type is `std::convert::Infallible`)
* 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`)

### v0.2.0 - 2023-04-13

Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
test: clippy
cargo test
cargo test --features serde
cargo test --features regex
cargo test --features new_unchecked
cargo test --features schemars08
cargo test --features nutype_test
cargo test --features nutype_test,serde
cargo test --features nutype_test,regex
cargo test --features nutype_test,new_unchecked
cargo test --features nutype_test,schemars08
cargo test --all-features

watch:
Expand Down
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ 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`, `From`, `TryFrom`, `Into`, `Hash`, `Borrow`, `Display`, `Serialize`, `Deserialize`.
`Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `From`, `TryFrom`, `Into`, `Hash`, `Borrow`, `Display`, `Default`, `Serialize`, `Deserialize`.


## Integer
Expand All @@ -204,7 +204,7 @@ The integer inner types are: `u8`, `u16`,`u32`, `u64`, `u128`, `i8`, `i16`, `i32
### Integer derivable traits

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


## Float
Expand All @@ -229,7 +229,7 @@ The float inner types are: `f32`, `f64`.
### Float derivable traits

The following traits can be derived for a float-based type:
`Debug`, `Clone`, `Copy`, `PartialEq`, `PartialOrd`, `FromStr`, `AsRef`, `Into`, `From`, `TryFrom`, `Hash`, `Borrow`, `Display`, `Serialize`, `Deserialize`.
`Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `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:
Expand Down Expand Up @@ -285,6 +285,28 @@ fn is_valid_name(name: &str) -> bool {
}
```

## Deriving recipes

### Deriving `Default`

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

### Deriving `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`.

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


## How to break the constraints?

First you need to know, you SHOULD NOT do it.
Expand Down
5 changes: 3 additions & 2 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use nutype::nutype;

#[nutype(validate(min_len = 3, max_len = 255))]
pub struct Name(String);
#[nutype(validate(finite))]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Weight(f64);

fn main() {}
30 changes: 27 additions & 3 deletions nutype/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
//! ### String derivable traits
//!
//! The following traits can be derived for a string-based type:
//! `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `From`, `TryFrom`, `Into`, `Hash`, `Borrow`, `Display`, `Serialize`, `Deserialize`.
//! `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `From`, `TryFrom`, `Into`, `Hash`, `Borrow`, `Display`, `Default`, `Serialize`, `Deserialize`.
//!
//!
//! ## Integer
Expand All @@ -201,7 +201,7 @@
//! ### Integer derivable traits
//!
//! The following traits can be derived for an integer-based type:
//! `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `Into`, `From`, `TryFrom`, `Hash`, `Borrow`, `Display`, `Serialize`, `Deserialize`.
//! `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `Into`, `From`, `TryFrom`, `Hash`, `Borrow`, `Display`, `Default`, `Serialize`, `Deserialize`.
//!
//!
//! ## Float
Expand All @@ -226,7 +226,7 @@
//! ### Float derivable traits
//!
//! The following traits can be derived for a float-based type:
//! `Debug`, `Clone`, `Copy`, `PartialEq`, `PartialOrd`, `FromStr`, `AsRef`, `Into`, `From`, `TryFrom`, `Hash`, `Borrow`, `Display`, `Serialize`, `Deserialize`.
//! `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromStr`, `AsRef`, `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 by applying `finite` validation. For example:
Expand Down Expand Up @@ -286,6 +286,30 @@
//! name.chars().next().map(char::is_uppercase).unwrap_or(false)
//! }
//! ```
//! ## Deriving recipes
//!
//! ### Deriving `Default`
//!
//! ```
//! use nutype::nutype;
//!
//! #[nutype(default = "Anonymous")]
//! #[derive(Default)]
//! pub struct Name(String);
//! ```
//!
//! ### Deriving `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`.
//!
//! ```
//! use nutype::nutype;
//!
//! #[nutype(validate(finite))]
//! #[derive(PartialEq, Eq, PartialOrd, Ord)]
//! pub struct Weight(f64);
//! ```
//!
//! ## How to break the constraints?
//!
Expand Down
5 changes: 5 additions & 0 deletions nutype_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ proc-macro = true
serde = []
schemars08 = []
new_unchecked = []

# nutype_test is set when unit tests for nutype is running.
# Why: we don't want to generate unit tests when we're already within a unit test, because it results
# into warnings.
nutype_test = []
53 changes: 53 additions & 0 deletions nutype_macros/src/common/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,56 @@ pub fn gen_impl_trait_serde_deserialize(
}
}
}

pub fn gen_impl_trait_default(
type_name: &TypeName,
default_value: impl ToTokens,
has_validation: bool,
) -> TokenStream {
let default_implementation = if has_validation {
let tp = type_name.to_string();
quote!(
Self::new(#default_value)
.unwrap_or_else(|err| {
let tp = #tp;
panic!("\nDefault value for type {tp} is invalid.\nERROR: {err}\n")
})
)
} else {
quote!(
Self::new(#default_value)
)
};

// Unfortunately it's not possible to guarantee at the compile time that the default value will always
// satisfy the validation rules.
// For this purpose we generate a unit test to verify this at run time.
let unit_test = if has_validation {
#[cfg(not(feature = "nutype_test"))]
Copy link
Owner Author

@greyblake greyblake Jun 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is becoming a comment pattern, so consider coming up with a macro rule. E.g.

if_feature!("nutype_test",
    then: quote!("Feature is ON"),
    else: quote!("Feature is OFF"),
)

{
quote!(
#[test]
fn should_have_valid_default_value() {
let default_inner_value = #type_name::default().into_inner();
#type_name::new(default_inner_value).expect("Default value must be valid");
}
)
}

#[cfg(feature = "nutype_test")]
{
quote!()
}
} else {
quote!()
};

quote!(
impl ::core::default::Default for #type_name {
fn default() -> Self {
#default_implementation
}
}
#unit_test
)
}
4 changes: 4 additions & 0 deletions nutype_macros/src/common/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ pub struct Attributes<G> {

/// `new_unchecked` flag
pub new_unchecked: NewUnchecked,

/// Value for Default trait. Provide with `default = `
pub maybe_default_value: Option<TokenStream>,
}

impl<Sanitizer, Validator> Guard<Sanitizer, Validator> {
Expand Down Expand Up @@ -234,6 +237,7 @@ pub enum NormalDeriveTrait {
Hash,
Borrow,
Display,
Default,

// External crates
//
Expand Down
28 changes: 28 additions & 0 deletions nutype_macros/src/common/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ pub fn parse_nutype_attributes<S, V>(
#[allow(unused_mut)]
let mut new_unchecked = NewUnchecked::Off;

// Value which is used to derive Default trait
let mut maybe_default_value: Option<TokenStream> = None;

let mut iter = input.into_iter();

loop {
Expand All @@ -101,6 +104,7 @@ pub fn parse_nutype_attributes<S, V>(
let attributes = Attributes {
guard: raw_guard,
new_unchecked,
maybe_default_value,
};
return Ok(attributes);
}
Expand All @@ -127,6 +131,29 @@ pub fn parse_nutype_attributes<S, V>(
let validate_stream = group.stream();
raw_guard.validators = parse_validate_attrs(validate_stream)?;
}
"default" => {
{
// Take `=` sign
if let Some(eq_t) = iter.next() {
if !is_eq(&eq_t) {
return Err(syn::Error::new(
ident.span(),
"Invalid syntax for `default`. Expected `=`, got `{eq_t}`",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"Invalid syntax for `default`. Missing `=`",
));
}
}
// TODO: parse it properly till some delimeter?
let default_value = iter
.next()
.ok_or_else(|| syn::Error::new(ident.span(), "Missing default value"))?;
maybe_default_value = Some(TokenStream::from(default_value));
}
"new_unchecked" => {
// The feature is not enabled, so we return an error
#[cfg(not(feature = "new_unchecked"))]
Expand Down Expand Up @@ -320,6 +347,7 @@ fn parse_ident_into_derive_trait(ident: Ident) -> Result<SpannedDeriveTrait, syn
"Into" => NormalDeriveTrait::Into,
"Hash" => NormalDeriveTrait::Hash,
"Borrow" => NormalDeriveTrait::Borrow,
"Default" => NormalDeriveTrait::Default,
"Serialize" => {
#[cfg(not(feature = "serde"))]
return Err(syn::Error::new(ident.span(), "To derive Serialize, the feature `serde` of the crate `nutype` needs to be enabled."));
Expand Down
12 changes: 11 additions & 1 deletion nutype_macros/src/float/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ use crate::{
};
use traits::gen_traits;

// TODO: These are too many arguments indeed.
// Consider refactoring.
#[allow(clippy::too_many_arguments)]
pub fn gen_nutype_for_float<T>(
doc_attrs: Vec<syn::Attribute>,
vis: Visibility,
Expand All @@ -30,6 +33,7 @@ pub fn gen_nutype_for_float<T>(
meta: FloatGuard<T>,
traits: HashSet<FloatDeriveTrait>,
new_unchecked: NewUnchecked,
maybe_default_value: Option<TokenStream>,
) -> TokenStream
where
T: ToTokens + PartialOrd,
Expand Down Expand Up @@ -59,7 +63,13 @@ where
let GeneratedTraits {
derive_transparent_traits,
implement_traits,
} = gen_traits(type_name, inner_type, maybe_error_type_name, traits);
} = gen_traits(
type_name,
inner_type,
maybe_error_type_name,
maybe_default_value,
traits,
);

quote!(
#[doc(hidden)]
Expand Down
Loading