Skip to content

Commit

Permalink
Merge pull request #119 from greyblake/arbitrary-any
Browse files Browse the repository at this point in the history
Support derive of Arbitrary for any inner type
  • Loading branch information
greyblake committed Jan 13, 2024
2 parents 5e4b930 + 469ab9d commit 1bc35f9
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Support integration with [`arbitrary`](https://crates.io/crates/arbitrary) crate (see `arbitrary` feature).
* Support `Arbitrary` for integer types
* Support `Arbitrary` for float types
* Support `Arbitrary` for any inner types
* Ability to specify boundaries (`greater`, `greater_or_equal`, `less`, `less_or_equal`, `len_char_min`, `len_char_max`) with expressions or named constants.
* Add `#[inline]` attribute to trivial functions

Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

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

11 changes: 11 additions & 0 deletions examples/any_arbitrary/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "any_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 = { version = "1.3.2", features = ["derive"] }
arbtest = "0.2.0"
nutype = { path = "../../nutype", features = ["arbitrary"] }
31 changes: 31 additions & 0 deletions examples/any_arbitrary/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use arbitrary::Arbitrary;
use nutype::nutype;

#[derive(Arbitrary)]
struct Point {
x: i32,
y: i32,
}

// Inner type is custom type Point, which implements Arbitrary.
// There is no validation, but custom sanitization.
// Deriving Arbitrary with custom validation would not be possible in this case.
#[nutype(
derive(Arbitrary),
sanitize(with = |mut point| {
point.x = point.x.clamp(0, 100);
point.y = point.y.clamp(-200, 200);
point
})
)]
pub struct Location(Point);

fn main() {
arbtest::builder().run(|u| {
let location = u.arbitrary::<Location>()?;
let point = location.into_inner();
assert!(point.x >= 0 && point.x <= 100);
assert!(point.y >= -200 && point.y <= 200);
Ok(())
});
}
7 changes: 4 additions & 3 deletions nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,16 @@ impl GenerateNewtype for AnyNewtype {
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
_guard: &AnyGuard,
guard: &AnyGuard,
) -> Result<GeneratedTraits, syn::Error> {
Ok(gen_traits(
gen_traits(
type_name,
inner_type,
maybe_error_type_name,
traits,
maybe_default_value,
))
guard,
)
}

fn gen_tests(
Expand Down
39 changes: 39 additions & 0 deletions nutype_macros/src/any/gen/traits/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;

use crate::{
any::models::{AnyGuard, AnyInnerType},
common::models::TypeName,
};

pub fn gen_impl_trait_arbitrary(
type_name: &TypeName,
inner_type: &AnyInnerType,
guard: &AnyGuard,
) -> Result<TokenStream, syn::Error> {
// It's not possible to generate implementation of `Arbitrary` trait, because we don't know nor
// type nor validation rules.
if guard.has_validation() {
let msg = format!(
"Cannot derive trait `Arbitrary` for a custom type `{type_name}` which contains validation.\nYou have to implement `Arbitrary` trait manually to guarantee that it respects the validation rules.",
);
return Err(syn::Error::new(Span::call_site(), msg));
}

// Generate implementation of `Arbitrary` trait, assuming that inner type implements Arbitrary
// too.
Ok(quote!(
impl ::arbitrary::Arbitrary<'_> for #type_name {
fn arbitrary(u: &mut ::arbitrary::Unstructured<'_>) -> ::arbitrary::Result<Self> {
let inner_value: #inner_type = u.arbitrary()?;
Ok(#type_name::new(inner_value))
}
}

#[inline]
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
let n = ::core::mem::size_of::<#inner_type>();
(n, Some(n))
}
))
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod arbitrary;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use std::collections::HashSet;

use crate::{
any::models::AnyDeriveTrait,
any::models::AnyInnerType,
any::models::{AnyGuard, AnyInnerType},
common::{
gen::traits::{
gen_impl_trait_as_ref, gen_impl_trait_borrow, gen_impl_trait_default,
Expand Down Expand Up @@ -49,6 +51,9 @@ impl From<AnyDeriveTrait> for AnyGeneratableTrait {
AnyDeriveTrait::SerdeDeserialize => {
AnyGeneratableTrait::Irregular(AnyIrregularTrait::SerdeDeserialize)
}
AnyDeriveTrait::ArbitraryArbitrary => {
AnyGeneratableTrait::Irregular(AnyIrregularTrait::ArbitraryArbitrary)
}
}
}
}
Expand Down Expand Up @@ -97,6 +102,7 @@ enum AnyIrregularTrait {
Default,
SerdeSerialize,
SerdeDeserialize,
ArbitraryArbitrary,
}

pub fn gen_traits(
Expand All @@ -105,7 +111,8 @@ pub fn gen_traits(
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<AnyDeriveTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits {
guard: &AnyGuard,
) -> Result<GeneratedTraits, syn::Error> {
let GeneratableTraits {
transparent_traits,
irregular_traits,
Expand All @@ -123,12 +130,13 @@ pub fn gen_traits(
maybe_error_type_name,
irregular_traits,
maybe_default_value,
);
guard,
)?;

GeneratedTraits {
Ok(GeneratedTraits {
derive_transparent_traits,
implement_traits,
}
})
}

fn gen_implemented_traits(
Expand All @@ -137,23 +145,24 @@ fn gen_implemented_traits(
maybe_error_type_name: Option<ErrorTypeName>,
impl_traits: Vec<AnyIrregularTrait>,
maybe_default_value: Option<syn::Expr>,
) -> TokenStream {
guard: &AnyGuard,
) -> Result<TokenStream, syn::Error> {
impl_traits
.iter()
.map(|t| match t {
AnyIrregularTrait::AsRef => gen_impl_trait_as_ref(type_name, inner_type),
AnyIrregularTrait::From => gen_impl_trait_from(type_name, inner_type),
AnyIrregularTrait::Into => gen_impl_trait_into(type_name, inner_type.clone()),
AnyIrregularTrait::Display => gen_impl_trait_display(type_name),
AnyIrregularTrait::Deref => gen_impl_trait_deref(type_name, inner_type),
AnyIrregularTrait::Borrow => gen_impl_trait_borrow(type_name, inner_type),
AnyIrregularTrait::FromStr => {
AnyIrregularTrait::AsRef => Ok(gen_impl_trait_as_ref(type_name, inner_type)),
AnyIrregularTrait::From => Ok(gen_impl_trait_from(type_name, inner_type)),
AnyIrregularTrait::Into => Ok(gen_impl_trait_into(type_name, inner_type.clone())),
AnyIrregularTrait::Display => Ok(gen_impl_trait_display(type_name)),
AnyIrregularTrait::Deref => Ok(gen_impl_trait_deref(type_name, inner_type)),
AnyIrregularTrait::Borrow => Ok(gen_impl_trait_borrow(type_name, inner_type)),
AnyIrregularTrait::FromStr => Ok(
gen_impl_trait_from_str(type_name, inner_type, maybe_error_type_name.as_ref())
}
AnyIrregularTrait::TryFrom => {
),
AnyIrregularTrait::TryFrom => Ok(
gen_impl_trait_try_from(type_name, inner_type, maybe_error_type_name.as_ref())
}
AnyIrregularTrait::Default => {
),
AnyIrregularTrait::Default => Ok(
match maybe_default_value {
Some(ref default_value) => {
let has_validation = maybe_error_type_name.is_some();
Expand All @@ -165,13 +174,14 @@ fn gen_implemented_traits(
);
}
}
}
AnyIrregularTrait::SerdeSerialize => {
),
AnyIrregularTrait::SerdeSerialize => Ok(
gen_impl_trait_serde_serialize(type_name)
}
AnyIrregularTrait::SerdeDeserialize => {
),
AnyIrregularTrait::SerdeDeserialize => Ok(
gen_impl_trait_serde_deserialize(type_name, inner_type, maybe_error_type_name.as_ref())
}
),
AnyIrregularTrait::ArbitraryArbitrary => arbitrary::gen_impl_trait_arbitrary(type_name, inner_type, guard),
})
.collect()
}
1 change: 1 addition & 0 deletions nutype_macros/src/any/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub enum AnyDeriveTrait {
// External crates
SerdeSerialize,
SerdeDeserialize,
ArbitraryArbitrary,
}

impl TypeTrait for AnyDeriveTrait {
Expand Down
6 changes: 1 addition & 5 deletions nutype_macros/src/any/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ 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::ArbitraryArbitrary => Ok(AnyDeriveTrait::ArbitraryArbitrary),
DeriveTrait::SchemarsJsonSchema => {
let msg =
format!("Deriving of trait `{tr:?}` is not (yet) supported for an arbitrary type");
Expand Down

0 comments on commit 1bc35f9

Please sign in to comment.