-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from greyblake/any-type
Any type
- Loading branch information
Showing
17 changed files
with
1,173 additions
and
47 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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,)* | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} | ||
} |
Oops, something went wrong.