Skip to content

Commit

Permalink
Merge pull request #88 from greyblake/any-type
Browse files Browse the repository at this point in the history
Any type
  • Loading branch information
greyblake authored Aug 27, 2023
2 parents 51fb4fa + dc51ea4 commit 4b80fa4
Show file tree
Hide file tree
Showing 17 changed files with 1,173 additions and 47 deletions.
50 changes: 50 additions & 0 deletions Cargo.lock

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

30 changes: 15 additions & 15 deletions dummy/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use nutype::nutype;

#[nutype(
sanitize(trim, lowercase),
validate(not_empty, char_len_max = 255, char_len_min = 6),
derive(
TryFrom, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
)
)]
pub struct Email(String);
#[derive(Debug)]
pub struct Point {
x: i32,
y: i32,
}

#[nutype(
derive(Deref, FromStr),
validate(greater_or_equal = 10, less_or_equal = 1000)
derive(Debug),
sanitize(with = |p| {
Point {
x: p.x.clamp(0, 100),
y: p.y.clamp(0, 100),
}
}),
validate(predicate = |p| p.x > p.y),
)]
pub struct Number(i16);
pub struct Pos(Point);

fn main() {
let magic = Number::new(42).unwrap();
assert_eq!(*magic, 42);
}
fn main() {}
63 changes: 63 additions & 0 deletions nutype_macros/src/any/gen/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use proc_macro2::TokenStream;
use quote::quote;

use crate::{
any::models::AnyValidator,
common::{
gen::error::{gen_error_type_name, gen_impl_error_trait},
models::{ErrorTypeName, TypeName},
},
};

pub fn gen_validation_error_type(type_name: &TypeName, validators: &[AnyValidator]) -> TokenStream {
let error_type_name = gen_error_type_name(type_name);
let definition = gen_definition(&error_type_name, validators);
let impl_display_trait = gen_impl_display_trait(&error_type_name, validators);
let impl_error_trait = gen_impl_error_trait(&error_type_name);

quote! {
#[derive(Debug, Clone, PartialEq, Eq)]
#definition

#impl_display_trait
#impl_error_trait
}
}

fn gen_definition(error_type_name: &ErrorTypeName, validators: &[AnyValidator]) -> TokenStream {
let error_variants: TokenStream = validators
.iter()
.map(|validator| match validator {
AnyValidator::Predicate(_) => {
quote!(PredicateViolated,)
}
})
.collect();

quote! {
pub enum #error_type_name {
#error_variants
}
}
}

fn gen_impl_display_trait(
error_type_name: &ErrorTypeName,
validators: &[AnyValidator],
) -> TokenStream {
let match_arms = validators.iter().map(|validator| match validator {
AnyValidator::Predicate(_) => quote! {
#error_type_name::PredicateViolated => write!(f, "invalid")
},
});

quote! {
impl ::core::fmt::Display for #error_type_name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#(#match_arms,)*
}
}
}
}
}
117 changes: 117 additions & 0 deletions nutype_macros/src/any/gen/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
mod error;
mod traits;

use std::collections::HashSet;

use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote;

use crate::common::{
gen::{error::gen_error_type_name, traits::GeneratedTraits, GenerateNewtype},
models::{ErrorTypeName, TypeName, TypedCustomFunction},
};

use self::error::gen_validation_error_type;

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

use traits::gen_traits;

impl GenerateNewtype for AnyNewtype {
type Sanitizer = AnySanitizer;
type Validator = AnyValidator;
type InnerType = AnyInnerType;
type TypedTrait = AnyDeriveTrait;

fn gen_fn_sanitize(
inner_type: &Self::InnerType,
sanitizers: &[Self::Sanitizer],
) -> TokenStream {
let transformations: TokenStream = sanitizers
.iter()
.map(|san| match san {
AnySanitizer::With(custom_sanitizer) => {
let inner_type_ref: syn::Type = parse_quote!(
#inner_type
);
let typed_sanitizer: TypedCustomFunction = custom_sanitizer
.clone()
.try_into_typed(&inner_type_ref)
.expect("Failed to convert `with` sanitizer into a typed closure");
quote!(
value = (#typed_sanitizer)(value);
)
}
})
.collect();

quote!(
fn sanitize(mut value: #inner_type) -> #inner_type {
#transformations
value
}
)
}

fn gen_fn_validate(
inner_type: &Self::InnerType,
type_name: &TypeName,
validators: &[Self::Validator],
) -> TokenStream {
let error_name = gen_error_type_name(type_name);

let validations: TokenStream = validators
.iter()
.map(|validator| match validator {
AnyValidator::Predicate(predicate) => {
let inner_type_ref: syn::Type = parse_quote!(
&'a #inner_type
);
let typed_predicate: TypedCustomFunction = predicate
.clone()
.try_into_typed(&inner_type_ref)
.expect("Failed to convert predicate into a typed closure");
quote!(
if !(#typed_predicate)(val) {
return Err(#error_name::PredicateViolated);
}
)
}
})
.collect();

quote!(
fn validate<'a>(val: &'a #inner_type) -> ::core::result::Result<(), #error_name> {
#validations
Ok(())
}
)
}

fn gen_validation_error_type(
type_name: &TypeName,
validators: &[Self::Validator],
) -> TokenStream {
gen_validation_error_type(type_name, validators)
}

fn gen_traits(
type_name: &TypeName,
inner_type: &Self::InnerType,
maybe_error_type_name: Option<ErrorTypeName>,
traits: HashSet<Self::TypedTrait>,
maybe_default_value: Option<syn::Expr>,
) -> GeneratedTraits {
gen_traits(
type_name,
inner_type,
maybe_error_type_name,
traits,
maybe_default_value,
)
}
}
Loading

0 comments on commit 4b80fa4

Please sign in to comment.