Skip to content

Commit

Permalink
Merge pull request #41 from greyblake/match_feature_macro
Browse files Browse the repository at this point in the history
Introduce and use match_feature macro
  • Loading branch information
greyblake authored Jun 24, 2023
2 parents ee8d8ee + 4937358 commit 7eb1ebd
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 74 deletions.
18 changes: 8 additions & 10 deletions nutype_macros/src/common/gen/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use std::collections::HashSet;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};

use crate::common::models::{ErrorTypeName, InnerType, TypeName};
use crate::{
common::models::{ErrorTypeName, InnerType, TypeName},
utils::match_feature,
};

use super::parse_error::{gen_def_parse_error, gen_parse_error_name};

Expand Down Expand Up @@ -253,21 +256,16 @@ pub fn gen_impl_trait_default(
// 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"))]
{
quote!(
match_feature!("nutype_test",
on => quote!(),
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!()
};
Expand Down
90 changes: 48 additions & 42 deletions nutype_macros/src/common/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use std::{any::type_name, fmt::Debug, str::FromStr};
use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use syn::spanned::Spanned;

use crate::common::models::{DeriveTrait, NormalDeriveTrait, RawGuard, SpannedDeriveTrait};
use crate::{
common::models::{DeriveTrait, NormalDeriveTrait, RawGuard, SpannedDeriveTrait},
utils::match_feature,
};

use super::models::{Attributes, NewUnchecked};

Expand Down Expand Up @@ -155,34 +158,34 @@ pub fn parse_nutype_attributes<S, V>(
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"))]
{
let msg = "To generate ::new_unchecked() function, the feature `new_unchecked` of crate `nutype` needs to be enabled.\nBut you ought to know: generally, THIS IS A BAD IDEA.\nUse it only when you really need it.";
return Err(syn::Error::new(ident.span(), msg));
}

// The feature `new_unchecked` is enabled
#[cfg(feature = "new_unchecked")]
{
// Try to look forward and return a good helpful error if `new_unchecked` is
// used incorrectly.
// Correct:
// new_unchecked
// Incorrect:
// new_unchecked()
// new_unchecked(foo = 13)
let maybe_next_token = iter.clone().next();
match maybe_next_token.map(try_unwrap_ident) {
None | Some(Ok(_)) => {
new_unchecked = NewUnchecked::On;
}
Some(Err(err)) => {
let msg = "new_unchecked does not support any options";
return Err(syn::Error::new(err.span(), msg));
match_feature!("new_unchecked",
// The feature is not enabled, so we return an error
on => {
// Try to look forward and return a good helpful error if `new_unchecked` is
// used incorrectly.
// Correct:
// new_unchecked
// Incorrect:
// new_unchecked()
// new_unchecked(foo = 13)
let maybe_next_token = iter.clone().next();
match maybe_next_token.map(try_unwrap_ident) {
None | Some(Ok(_)) => {
new_unchecked = NewUnchecked::On;
}
Some(Err(err)) => {
let msg = "new_unchecked does not support any options";
return Err(syn::Error::new(err.span(), msg));
}
}
},

// The feature `new_unchecked` is enabled
off => {
let msg = "To generate ::new_unchecked() function, the feature `new_unchecked` of crate `nutype` needs to be enabled.\nBut you ought to know: generally, THIS IS A BAD IDEA.\nUse it only when you really need it.";
return Err(syn::Error::new(ident.span(), msg));
}
}
)
}
unknown => {
let msg = format!("Unknown #[nutype] option: `{unknown}`");
Expand Down Expand Up @@ -349,25 +352,28 @@ fn parse_ident_into_derive_trait(ident: Ident) -> Result<SpannedDeriveTrait, syn
"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."));

#[cfg(feature = "serde")]
NormalDeriveTrait::SerdeSerialize
match_feature!("serde",
on => NormalDeriveTrait::SerdeSerialize,
off => {
return Err(syn::Error::new(ident.span(), "To derive Serialize, the feature `serde` of the crate `nutype` needs to be enabled."));
},
)
}
"Deserialize" => {
#[cfg(not(feature = "serde"))]
return Err(syn::Error::new(ident.span(), "To derive Deserialize, the feature `serde` of the crate `nutype` needs to be enabled."));

#[cfg(feature = "serde")]
NormalDeriveTrait::SerdeDeserialize
match_feature!("serde",
on => NormalDeriveTrait::SerdeDeserialize,
off => {
return Err(syn::Error::new(ident.span(), "To derive Deserialize, the feature `serde` of the crate `nutype` needs to be enabled."));
},
)
}
"JsonSchema" => {
#[cfg(not(feature = "schemars08"))]
return Err(syn::Error::new(ident.span(), "To derive JsonSchema, the feature `schemars08` of the crate `nutype` needs to be enabled."));

#[cfg(feature = "schemars08")]
NormalDeriveTrait::SchemarsJsonSchema
match_feature!("schemars08",
on => NormalDeriveTrait::SchemarsJsonSchema,
off => {
return Err(syn::Error::new(ident.span(), "To derive JsonSchema, the feature `schemars08` of the crate `nutype` needs to be enabled."));
}
)
}
_ => {
return Err(syn::Error::new(
Expand Down
1 change: 1 addition & 0 deletions nutype_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod common;
mod float;
mod integer;
mod string;
mod utils;

use std::{fmt::Debug, str::FromStr};

Expand Down
44 changes: 22 additions & 22 deletions nutype_macros/src/string/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::common::parse::{
use crate::string::models::StringGuard;
use crate::string::models::StringRawGuard;
use crate::string::models::{StringSanitizer, StringValidator};
use crate::utils::match_feature;
use proc_macro2::{Span, TokenStream, TokenTree};

use super::models::{RegexDef, SpannedStringSanitizer, SpannedStringValidator};
Expand Down Expand Up @@ -113,28 +114,27 @@ fn parse_validate_attr(tokens: Vec<TokenTree>) -> Result<SpannedStringValidator,
Ok(parsed_validator)
}
"regex" => {
#[cfg(not(feature = "regex"))]
{
let msg = concat!(
"To validate string types with regex, the feature `regex` of the crate `nutype` must be enabled.\n",
"IMPORTANT: Make sure that your crate EXPLICITLY depends on `regex` and `lazy_static` crates.\n",
"And... don't forget to take care of yourself and your beloved ones. That is even more important.",
);
return Err(syn::Error::new(ident.span(), msg));
}

#[cfg(feature = "regex")]
{
let rest_tokens: Vec<_> = token_iter.collect();
let stream = parse_with_token_stream(rest_tokens.iter(), ident.span())?;
let (regex_def, span) = parse_regex(stream, ident.span())?;
let validator = StringValidator::Regex(regex_def);
let parsed_validator = SpannedStringValidator {
item: validator,
span,
};
Ok(parsed_validator)
}
match_feature!("regex",
on => {
let rest_tokens: Vec<_> = token_iter.collect();
let stream = parse_with_token_stream(rest_tokens.iter(), ident.span())?;
let (regex_def, span) = parse_regex(stream, ident.span())?;
let validator = StringValidator::Regex(regex_def);
let parsed_validator = SpannedStringValidator {
item: validator,
span,
};
Ok(parsed_validator)
},
off => {
let msg = concat!(
"To validate string types with regex, the feature `regex` of the crate `nutype` must be enabled.\n",
"IMPORTANT: Make sure that your crate EXPLICITLY depends on `regex` and `lazy_static` crates.\n",
"And... don't forget to take care of yourself and your beloved ones. That is even more important.",
);
return Err(syn::Error::new(ident.span(), msg));
}
)
}
validator => {
let msg = format!("Unknown validation rule `{validator}`");
Expand Down
39 changes: 39 additions & 0 deletions nutype_macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Example:
//
// let msg = match_feature!("foo",
// on => "Foo is on!",
// off => "Foo is off",
// );
//
macro_rules! match_feature {
// Canonical
(
$feature_name:literal,
on => $on_code:expr,
off => $off_code:expr,
) => {{
#[cfg(feature = $feature_name)]
{
$on_code
}

#[cfg(not(feature = $feature_name))]
{
$off_code
}
}};

// Alternative: without trailing comma
(
$feature_name:literal,
on => $on_code:expr,
off => $off_code:expr
) => {{
match_feature!($feature_name,
on => $on_code,
off => $off_code,
)
}}
}

pub(crate) use match_feature;

0 comments on commit 7eb1ebd

Please sign in to comment.