Skip to content

Commit

Permalink
Merge pull request #107 from greyblake/arbitrary
Browse files Browse the repository at this point in the history
Support of Arbitrary for integers
  • Loading branch information
greyblake authored Dec 2, 2023
2 parents 8fe551e + e1a96e8 commit e0daf53
Show file tree
Hide file tree
Showing 30 changed files with 390 additions and 94 deletions.
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
### v0.4.1 - xxxx-xx-xx

* Support integration with [`arbitrary`](https://crates.io/crates/arbitrary) crate (see `arbitrary` feature).
* Support `Arbitrary` for integer types

### v0.4.0 - 2023-11-21
* [FEATURE] Support of arbitrary inner types with custom sanitizers and validators.
* [FEATURE] Add numeric validator `greater`
* [FEATURE] Add numeric validator `less`
* Support of arbitrary inner types with custom sanitizers and validators.
* Add numeric validator `greater`
* Add numeric validator `less`
* [BREAKING] Removal of asterisk derive
* [BREAKING] Use commas to separate high level attributes
* [BREAKING] Traits are derived with `#[nutype(derive(Debug))]`. The regular `#[derive(Debug)]` syntax is not supported anymore.
Expand Down
20 changes: 18 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dummy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
nutype = { path = "../nutype", features = ["serde", "new_unchecked", "schemars08", "regex"] }
nutype = { path = "../nutype", features = ["serde", "new_unchecked", "schemars08", "regex", "arbitrary"] }
serde = "*"
serde_json = "*"
schemars = "*"
Expand Down
44 changes: 5 additions & 39 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,10 @@
use nutype::nutype;

#[nutype(
derive(Debug, PartialEq, Deref, AsRef),
sanitize(with = |mut guests| { guests.sort(); guests }),
validate(predicate = |guests| !guests.is_empty() ),
validate(greater = 0, less = 24, predicate = |x| x == &4),
derive(Debug, PartialEq, Deref, AsRef, Default),
default = 4
)]
pub struct GuestList(Vec<String>);
pub struct Hour(i32);

fn main() {
// Empty list is not allowed
assert_eq!(
GuestList::new(vec![]),
Err(GuestListError::PredicateViolated)
);

// Create the list of our guests
let guest_list = GuestList::new(vec![
"Seneca".to_string(),
"Marcus Aurelius".to_string(),
"Socrates".to_string(),
"Epictetus".to_string(),
])
.unwrap();

// The list is sorted (thanks to sanitize)
assert_eq!(
guest_list.as_ref(),
&[
"Epictetus".to_string(),
"Marcus Aurelius".to_string(),
"Seneca".to_string(),
"Socrates".to_string(),
]
);

// Since GuestList derives Deref, we can use methods from `Vec<T>`
// due to deref coercion (if it's a good idea or not, it's left up to you to decide!).
assert_eq!(guest_list.len(), 4);

for guest in guest_list.iter() {
println!("{guest}");
}
}
fn main() {}
11 changes: 11 additions & 0 deletions examples/integer_arbitrary/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "integer_arbitrary"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arbitrary = "1.3.2"
arbtest = "0.2.0"
nutype = { path = "../../nutype", features = ["arbitrary"] }
73 changes: 73 additions & 0 deletions examples/integer_arbitrary/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use arbitrary::Arbitrary;
use nutype::nutype;

// Inclusive boundaries. 1 and 6 are included, so the value can only be 1, 2, 3, 4, 5 or 6.
#[nutype(
validate(greater_or_equal = 1, less_or_equal = 6),
derive(Arbitrary, Debug)
)]
struct GermanTaxClass(i128);

// Exclusive boundaries.
//
// -2 and 2 are excluded, so the value can only be -1, 0 or 1.
#[nutype(
validate(greater = -2, less = 2),
derive(Arbitrary, Debug),
)]
struct MinusOneOrZeroOrOne(i128);

// Since the upper limit for i8 is 127, the GreaterThan125 can only be 126 or 127.
#[nutype(validate(greater = 125), derive(Arbitrary, Debug))]
struct GreaterThan125(i8);

// Since the upper limit for i8 is 127, the GreaterOrEqual125 can only be 125, 126 or 127.
#[nutype(validate(greater_or_equal = 125), derive(Arbitrary, Debug))]
struct GreaterOrEqual125(i8);

// u128::MIN is 0, so the LessThan2 can only be 0, 1
#[nutype(validate(less = 2), derive(Arbitrary, Debug))]
struct LessThan2(u128);

// u128::MIN is 0, so the LessOrEqual2 can only be 0, 1, 2
#[nutype(validate(less = 2), derive(Arbitrary, Debug))]
struct LessOrEqual2(u128);

fn main() {
arbtest::builder().run(|u| {
let tax_class = GermanTaxClass::arbitrary(u)?.into_inner();
assert!(tax_class >= 1);
assert!(tax_class <= 6);
Ok(())
});

arbtest::builder().run(|u| {
let value = GreaterThan125::arbitrary(u)?.into_inner();
assert!(value == 126 || value == 127);
Ok(())
});

arbtest::builder().run(|u| {
let value = GreaterOrEqual125::arbitrary(u)?.into_inner();
assert!(value == 125 || value == 126 || value == 127);
Ok(())
});

arbtest::builder().run(|u| {
let value = MinusOneOrZeroOrOne::arbitrary(u)?.into_inner();
assert!(value == -1 || value == 0 || value == 1);
Ok(())
});

arbtest::builder().run(|u| {
let value = LessThan2::arbitrary(u)?.into_inner();
assert!(value == 0 || value == 1);
Ok(())
});

arbtest::builder().run(|u| {
let value = LessOrEqual2::arbitrary(u)?.into_inner();
assert!(value == 0 || value == 1 || value == 2);
Ok(())
});
}
1 change: 1 addition & 0 deletions nutype/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ serde = ["nutype_macros/serde"]
regex = ["nutype_macros/regex"]
schemars08 = ["nutype_macros/schemars08"]
new_unchecked = ["nutype_macros/new_unchecked"]
arbitrary = ["nutype_macros/arbitrary"]
2 changes: 2 additions & 0 deletions nutype_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ syn = { version = "2.0", features = ["extra-traits", "full"] }
regex = { version = "1", optional = true }
cfg-if = "1.0.0"
kinded = "0.3.0"
urlencoding = "2.0"

[lib]
proc-macro = true
Expand All @@ -32,6 +33,7 @@ proc-macro = true
serde = []
schemars08 = []
new_unchecked = []
arbitrary = []

# 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
Expand Down
9 changes: 5 additions & 4 deletions nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::common::{
use self::error::gen_validation_error_type;

use super::{
models::{AnyDeriveTrait, AnyInnerType, AnySanitizer, AnyValidator},
models::{AnyDeriveTrait, AnyGuard, AnyInnerType, AnySanitizer, AnyValidator},
AnyNewtype,
};

Expand Down Expand Up @@ -105,13 +105,14 @@ impl GenerateNewtype for AnyNewtype {
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits {
gen_traits(
_guard: &AnyGuard,
) -> Result<GeneratedTraits, syn::Error> {
Ok(gen_traits(
type_name,
inner_type,
maybe_error_type_name,
traits,
maybe_default_value,
)
))
}
}
4 changes: 3 additions & 1 deletion nutype_macros/src/any/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ impl Newtype for AnyNewtype {
validate_any_derive_traits(guard, derive_traits)
}

fn generate(params: GenerateParams<AnyInnerType, Self::TypedTrait, AnyGuard>) -> TokenStream {
fn generate(
params: GenerateParams<AnyInnerType, Self::TypedTrait, AnyGuard>,
) -> Result<TokenStream, syn::Error> {
AnyNewtype::gen_nutype(params)
}
}
5 changes: 5 additions & 0 deletions nutype_macros/src/any/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ fn to_any_derive_trait(
DeriveTrait::SerdeSerialize => Ok(AnyDeriveTrait::SerdeSerialize),
DeriveTrait::SerdeDeserialize => Ok(AnyDeriveTrait::SerdeDeserialize),
DeriveTrait::Hash => Ok(AnyDeriveTrait::Hash),
DeriveTrait::ArbitraryArbitrary => {
// TODO: Allow deriving Arbitrary if there is no validation
let msg = "Deriving Arbitrary trait for any type is not yet possible";
Err(syn::Error::new(span, msg))
}
DeriveTrait::SchemarsJsonSchema => {
let msg =
format!("Deriving of trait `{tr:?}` is not (yet) supported for an arbitrary type");
Expand Down
12 changes: 7 additions & 5 deletions nutype_macros/src/common/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ pub trait GenerateNewtype {
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits;
guard: &Guard<Self::Sanitizer, Self::Validator>,
) -> Result<GeneratedTraits, syn::Error>;

fn gen_new_with_validation(
type_name: &TypeName,
Expand Down Expand Up @@ -283,7 +284,7 @@ pub trait GenerateNewtype {
Self::TypedTrait,
Guard<Self::Sanitizer, Self::Validator>,
>,
) -> TokenStream {
) -> Result<TokenStream, syn::Error> {
let GenerateParams {
doc_attrs,
traits,
Expand Down Expand Up @@ -328,9 +329,10 @@ pub trait GenerateNewtype {
maybe_error_type_name,
traits,
maybe_default_value,
);
&guard,
)?;

quote!(
Ok(quote!(
#[doc(hidden)]
mod #module_name {
use super::*;
Expand All @@ -343,6 +345,6 @@ pub trait GenerateNewtype {
#implement_traits
}
#reimports
)
))
}
}
7 changes: 5 additions & 2 deletions nutype_macros/src/common/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ pub enum DeriveTrait {

#[cfg_attr(not(feature = "schemars08"), allow(dead_code))]
SchemarsJsonSchema,

#[cfg_attr(not(feature = "arbitrary"), allow(dead_code))]
ArbitraryArbitrary,
}

pub type SpannedDeriveTrait = SpannedItem<DeriveTrait>;
Expand Down Expand Up @@ -319,7 +322,7 @@ pub trait Newtype {
Self::TypedTrait,
Guard<Self::Sanitizer, Self::Validator>,
>,
) -> TokenStream;
) -> Result<TokenStream, syn::Error>;

fn expand(
typed_meta: TypedMeta,
Expand Down Expand Up @@ -347,7 +350,7 @@ pub trait Newtype {
new_unchecked,
maybe_default_value,
inner_type,
});
})?;
Ok(generated_output)
}
}
Expand Down
9 changes: 9 additions & 0 deletions nutype_macros/src/common/parse/derive_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ impl Parse for SpannedDeriveTrait {
}
}
}
"Arbitrary" => {
cfg_if! {
if #[cfg(feature = "arbitrary")] {
DeriveTrait::ArbitraryArbitrary
} else {
return Err(syn::Error::new(ident.span(), "To derive Arbitrary, the feature `arbitrary` of the crate `nutype` needs to be enabled."));
}
}
}
_ => {
return Err(syn::Error::new(
ident.span(),
Expand Down
9 changes: 5 additions & 4 deletions nutype_macros/src/float/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use quote::{quote, ToTokens};

use self::error::gen_validation_error_type;
use super::{
models::{FloatDeriveTrait, FloatSanitizer, FloatType, FloatValidator},
models::{FloatDeriveTrait, FloatGuard, FloatSanitizer, FloatType, FloatValidator},
FloatNewtype,
};
use crate::{
Expand Down Expand Up @@ -132,13 +132,14 @@ where
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits {
gen_traits(
_guard: &FloatGuard<T>,
) -> Result<GeneratedTraits, syn::Error> {
Ok(gen_traits(
type_name,
inner_type,
maybe_error_type_name,
maybe_default_value,
traits,
)
))
}
}
Loading

0 comments on commit e0daf53

Please sign in to comment.