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 2 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
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
11 changes: 9 additions & 2 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use nutype::nutype;

#[nutype(validate(min_len = 3, max_len = 255))]
#[nutype(
validate(max_len = 6)
default = "FooBar"
)]
#[derive(Debug, Display, Default)]
pub struct Name(String);

fn main() {}
fn main() {
let name = Name::default();
println!("name = {name}");
}
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
23 changes: 22 additions & 1 deletion nutype_macros/src/float/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{
gen_impl_trait_serde_serialize, gen_impl_trait_try_from, split_into_generatable_traits,
GeneratableTrait, GeneratableTraits, GeneratedTraits,
},
common::models::{ErrorTypeName, FloatInnerType, TypeName},
common::{
gen::traits::gen_impl_trait_default,
models::{ErrorTypeName, FloatInnerType, TypeName},
},
float::models::FloatDeriveTrait,
};

Expand Down Expand Up @@ -40,6 +43,7 @@ enum FloatIrregularTrait {
TryFrom,
Borrow,
Display,
Default,
SerdeSerialize,
SerdeDeserialize,
}
Expand Down Expand Up @@ -79,6 +83,9 @@ impl From<FloatDeriveTrait> for FloatGeneratableTrait {
FloatDeriveTrait::Display => {
FloatGeneratableTrait::Irregular(FloatIrregularTrait::Display)
}
FloatDeriveTrait::Default => {
FloatGeneratableTrait::Irregular(FloatIrregularTrait::Default)
}
FloatDeriveTrait::SerdeSerialize => {
FloatGeneratableTrait::Irregular(FloatIrregularTrait::SerdeSerialize)
}
Expand Down Expand Up @@ -110,6 +117,7 @@ pub fn gen_traits(
type_name: &TypeName,
inner_type: FloatInnerType,
maybe_error_type_name: Option<ErrorTypeName>,
maybe_default_value: Option<TokenStream>,
traits: HashSet<FloatDeriveTrait>,
) -> GeneratedTraits {
let GeneratableTraits {
Expand All @@ -127,6 +135,7 @@ pub fn gen_traits(
type_name,
inner_type,
maybe_error_type_name,
maybe_default_value,
irregular_traits,
);

Expand All @@ -140,6 +149,7 @@ fn gen_implemented_traits(
type_name: &TypeName,
inner_type: FloatInnerType,
maybe_error_type_name: Option<ErrorTypeName>,
maybe_default_value: Option<TokenStream>,
impl_traits: Vec<FloatIrregularTrait>,
) -> TokenStream {
impl_traits
Expand All @@ -156,6 +166,17 @@ fn gen_implemented_traits(
}
FloatIrregularTrait::Borrow => gen_impl_trait_borrow(type_name, inner_type),
FloatIrregularTrait::Display => gen_impl_trait_dislpay(type_name),
FloatIrregularTrait::Default => {
match maybe_default_value {
Some(ref default_value) => {
let has_validation = maybe_error_type_name.is_some();
gen_impl_trait_default(type_name, default_value, has_validation)
},
None => {
panic!("Default trait is derived for type {type_name}, but `default = ` is missing");
}
}
}
FloatIrregularTrait::SerdeSerialize => gen_impl_trait_serde_serialize(type_name),
FloatIrregularTrait::SerdeDeserialize => gen_impl_trait_serde_deserialize(
type_name,
Expand Down
1 change: 1 addition & 0 deletions nutype_macros/src/float/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ pub enum FloatDeriveTrait {
TryFrom,
Borrow,
Display,
Default,

// External crates
SerdeSerialize,
Expand Down
2 changes: 2 additions & 0 deletions nutype_macros/src/float/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ where
let Attributes {
new_unchecked,
guard: raw_guard,
maybe_default_value,
} = raw_attrs;
let guard = validate_number_meta(raw_guard)?;
Ok(Attributes {
new_unchecked,
guard,
maybe_default_value,
})
}

Expand Down
1 change: 1 addition & 0 deletions nutype_macros/src/float/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ fn to_float_derive_trait(
match tr {
NormalDeriveTrait::Debug => Ok(FloatDeriveTrait::Debug),
NormalDeriveTrait::Display => Ok(FloatDeriveTrait::Display),
NormalDeriveTrait::Default => Ok(FloatDeriveTrait::Default),
NormalDeriveTrait::Clone => Ok(FloatDeriveTrait::Clone),
NormalDeriveTrait::PartialEq => Ok(FloatDeriveTrait::PartialEq),
NormalDeriveTrait::Into => Ok(FloatDeriveTrait::Into),
Expand Down
12 changes: 11 additions & 1 deletion nutype_macros/src/integer/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ use crate::{
},
};

// TODO: These are too many arguments indeed.
// Consider refactoring.
#[allow(clippy::too_many_arguments)]
pub fn gen_nutype_for_integer<T>(
doc_attrs: Vec<syn::Attribute>,
vis: Visibility,
Expand All @@ -29,6 +32,7 @@ pub fn gen_nutype_for_integer<T>(
meta: IntegerGuard<T>,
traits: HashSet<IntegerDeriveTrait>,
new_unchecked: NewUnchecked,
maybe_default_value: Option<TokenStream>,
) -> TokenStream
where
T: ToTokens + PartialOrd,
Expand Down Expand Up @@ -58,7 +62,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,
traits,
maybe_default_value,
);

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