Skip to content

Commit

Permalink
Merge pull request #39 from greyblake/default
Browse files Browse the repository at this point in the history
Default
  • Loading branch information
greyblake authored Jun 24, 2023
2 parents 9f25afb + 0931c60 commit ee8d8ee
Show file tree
Hide file tree
Showing 31 changed files with 408 additions and 33 deletions.
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"))]
{
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

0 comments on commit ee8d8ee

Please sign in to comment.