Repository: greyblake/nutype (language: rust)

Issue:
Move derive into nutype attributes
## Context

At the moment it's possible to derive traits by using the regular `#[derive]` macro. For example:
```rs
#[nutype(validate(not_empty))]
#[derive(Debug)]
pub struct Name(String);
```

The initial idea behind this was to give a "natural" syntax/feeling to Rust developers.
In reality however, `nutype` fully controls `#[derive()]`: it parses it, and generates code. It's not done by the standard `derive` macro.

Note also that the following will not compile:
```rs
#[derive(Debug)]
#[nutype(validate(not_empty))]
pub struct Name(String);
```

Because in this case, `nutype` has no notion about `derive` and the standard `derive` macro is applied on tuple struct with private inner value, which cannot be access directly.
However, people got confused sometimes, thinking that the 2 snippets above are equal. 

## Motivation

To eliminate confusion and make it clear  for the library users,  we want to move `derive` macro into set of `nutype` attributes, e.g.:
```rs
#[nutype(
    validate(not_empty),
    derive(Debug)
)]
pub struct Name(String);
```

## Spec

* Add a new field to `ParseableAttributes` : `derive_traits: Vec<SpannedItem<DeriveTrait>>`,
* Update `impl Parse for ParseableAttributes` to parse `derive` into `derive_traits`, for inspiration take the current implementation in `fn parse_ident_into_derive_trait`
* Adjust the tests (to run all possible tests run `make test`)
* When `#[derive(...)]` is used outside of `#[nutype(...)]` return a compilation error, explaining how to use derive correctly. Add a compiletests to test the error message.
* Update CHANGELOG.md with the information about the breaking change. Do not update README (will do it before the release).

## Acceptance Criteria

1. The following code should compile and `Name` should have `Debug` implemented:

```rs
#[nutype(
    validate(not_empty),
    derive(Debug)
)]
pub struct Name(String)
```

2. The following code should result into an error message with an example how to use `derive(..)` properly:

```rs
#[nutype(
    validate(not_empty)
)]
#[derive(Debug)]
pub struct Name(String)
```



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e93c8a..44995ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
 ### v0.4.0 - 2023-xx-xx
 * [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.
 
 ### v0.3.1 - 2023-06-30
 * Support deriving of `Deref`
diff --git a/dummy/src/main.rs b/dummy/src/main.rs
index bc9459b..02068de 100644
--- a/dummy/src/main.rs
+++ b/dummy/src/main.rs
@@ -1,8 +1,18 @@
 use nutype::nutype;
 
-#[nutype(validate(with = |x| x.is_empty() ))]
-// #[nutype(sanitize(with = trim_name ))]
-#[derive(Debug)]
-pub struct Name(String);
+#[nutype(
+    sanitize(trim, lowercase),
+    validate(not_empty),
+    derive(
+        TryFrom, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+    )
+)]
+pub struct Email(String);
 
-fn main() {}
+#[nutype(derive(Deref))]
+pub struct Number(i16);
+
+fn main() {
+    let magic = Number::new(42);
+    assert_eq!(*magic, 42);
+}
diff --git a/nutype/src/lib.rs b/nutype/src/lib.rs
index c622071..caa63ed 100644
--- a/nutype/src/lib.rs
+++ b/nutype/src/lib.rs
@@ -15,8 +15,8 @@
 //! #[nutype(
 //!     sanitize(trim, lowercase),
 //!     validate(not_empty, max_len = 20),
+//!     derive(Debug, PartialEq),
 //! )]
-//! #[derive(Debug, PartialEq)]
 //! pub struct Username(String);
 //!
 //! // Now we can create usernames:
@@ -222,8 +222,10 @@
 //! ```rust
 //! use nutype::nutype;
 //!
-//! #[nutype(validate(finite))]
-//! #[derive(PartialEq, Eq, PartialOrd, Ord)]
+//! #[nutype(
+//!     validate(finite),
+//!     derive(PartialEq, Eq, PartialOrd, Ord),
+//! )]
 //! struct Size(f64);
 //! ```
 //!
@@ -281,8 +283,10 @@
 //! ```
 //! use nutype::nutype;
 //!
-//! #[nutype(default = "Anonymous")]
-//! #[derive(Default)]
+//! #[nutype(
+//!     derive(Default),
+//!     default = "Anonymous",
+//! )]
 //! pub struct Name(String);
 //! ```
 //!
@@ -294,8 +298,10 @@
 //! ```
 //! use nutype::nutype;
 //!
-//! #[nutype(validate(finite))]
-//! #[derive(PartialEq, Eq, PartialOrd, Ord)]
+//! #[nutype(
+//!     validate(finite),
+//!     derive(PartialEq, Eq, PartialOrd, Ord),
+//! )]
 //! pub struct Weight(f64);
 //! ```
 //!
@@ -356,8 +362,13 @@ mod tests {
 
     #[test]
     fn test_email_example() {
-        #[nutype(sanitize(trim, lowercase), validate(not_empty))]
-        #[derive(TryFrom, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            sanitize(trim, lowercase),
+            validate(not_empty),
+            derive(
+                TryFrom, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         pub struct Email(String);
 
         let email = Email::new("  OH@my.example\n\n").unwrap();
@@ -368,8 +379,7 @@ mod tests {
 
     #[test]
     fn test_amount_example() {
-        #[nutype(validate(min = 100, max = 1_000))]
-        #[derive(Debug, PartialEq, TryFrom)]
+        #[nutype(validate(min = 100, max = 1_000), derive(Debug, PartialEq, TryFrom))]
         pub struct Amount(u32);
 
         assert_eq!(Amount::try_from(99), Err(AmountError::TooSmall));
diff --git a/nutype_macros/src/common/models.rs b/nutype_macros/src/common/models.rs
index d965443..3fcbb93 100644
--- a/nutype_macros/src/common/models.rs
+++ b/nutype_macros/src/common/models.rs
@@ -144,7 +144,6 @@ pub struct Meta {
     pub inner_type: InnerType,
     pub vis: syn::Visibility,
     pub doc_attrs: Vec<Attribute>,
-    pub derive_traits: Vec<SpannedDeriveTrait>,
 }
 
 impl Meta {
@@ -154,14 +153,12 @@ impl Meta {
             type_name,
             inner_type,
             vis,
-            derive_traits,
         } = self;
         let typed_meta = TypedMeta {
             doc_attrs,
             type_name,
             attrs,
             vis,
-            derive_traits,
         };
         (typed_meta, inner_type)
     }
@@ -178,7 +175,6 @@ pub struct TypedMeta {
 
     pub vis: syn::Visibility,
     pub doc_attrs: Vec<Attribute>,
-    pub derive_traits: Vec<SpannedDeriveTrait>,
 }
 
 /// Validated model, that represents precisely what needs to be generated.
@@ -195,7 +191,7 @@ pub enum Guard<Sanitizer, Validator> {
 
 /// Parsed attributes (`sanitize`, `validate`, `new_unchecked`).
 #[derive(Debug)]
-pub struct Attributes<G> {
+pub struct Attributes<G, DT> {
     /// Guard contains sanitizers and validators
     pub guard: G,
 
@@ -204,6 +200,8 @@ pub struct Attributes<G> {
 
     /// Value for Default trait. Provide with `default = `
     pub default: Option<syn::Expr>,
+
+    pub derive_traits: Vec<DT>,
 }
 
 impl<Sanitizer, Validator> Guard<Sanitizer, Validator> {
@@ -294,11 +292,11 @@ pub trait Newtype {
     #[allow(clippy::type_complexity)]
     fn parse_attributes(
         attrs: TokenStream,
-    ) -> Result<Attributes<Guard<Self::Sanitizer, Self::Validator>>, syn::Error>;
+    ) -> Result<Attributes<Guard<Self::Sanitizer, Self::Validator>, SpannedDeriveTrait>, syn::Error>;
 
     fn validate(
         guard: &Guard<Self::Sanitizer, Self::Validator>,
-        derive_traits: Vec<SpannedItem<DeriveTrait>>,
+        derive_traits: Vec<SpannedDeriveTrait>,
     ) -> Result<HashSet<Self::TypedTrait>, syn::Error>;
 
     #[allow(clippy::type_complexity)]
@@ -319,12 +317,12 @@ pub trait Newtype {
             type_name,
             attrs,
             vis,
-            derive_traits,
         } = typed_meta;
         let Attributes {
             guard,
             new_unchecked,
             default: maybe_default_value,
+            derive_traits,
         } = Self::parse_attributes(attrs)?;
         let traits = Self::validate(&guard, derive_traits)?;
         let generated_output = Self::generate(GenerateParams {
diff --git a/nutype_macros/src/common/parse/derive_trait.rs b/nutype_macros/src/common/parse/derive_trait.rs
new file mode 100644
index 0000000..2d97c5d
--- /dev/null
+++ b/nutype_macros/src/common/parse/derive_trait.rs
@@ -0,0 +1,69 @@
+use cfg_if::cfg_if;
+use proc_macro2::Ident;
+use syn::parse::{Parse, ParseStream};
+
+use crate::common::models::{DeriveTrait, SpannedDeriveTrait};
+
+impl Parse for SpannedDeriveTrait {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        let ident: Ident = input.parse()?;
+
+        let derive_trait = match ident.to_string().as_ref() {
+            "Debug" => DeriveTrait::Debug,
+            "Display" => DeriveTrait::Display,
+            "Clone" => DeriveTrait::Clone,
+            "Copy" => DeriveTrait::Copy,
+            "PartialEq" => DeriveTrait::PartialEq,
+            "Eq" => DeriveTrait::Eq,
+            "PartialOrd" => DeriveTrait::PartialOrd,
+            "Ord" => DeriveTrait::Ord,
+            "FromStr" => DeriveTrait::FromStr,
+            "AsRef" => DeriveTrait::AsRef,
+            "Deref" => DeriveTrait::Deref,
+            "TryFrom" => DeriveTrait::TryFrom,
+            "From" => DeriveTrait::From,
+            "Into" => DeriveTrait::Into,
+            "Hash" => DeriveTrait::Hash,
+            "Borrow" => DeriveTrait::Borrow,
+            "Default" => DeriveTrait::Default,
+            "Serialize" => {
+                cfg_if! {
+                    if #[cfg(feature = "serde")] {
+                        DeriveTrait::SerdeSerialize
+                    } else {
+                        return Err(syn::Error::new(ident.span(), "To derive Serialize, the feature `serde` of the crate `nutype` needs to be enabled."));
+                    }
+                }
+            }
+            "Deserialize" => {
+                cfg_if! {
+                    if #[cfg(feature = "serde")] {
+                        DeriveTrait::SerdeDeserialize
+                    } else {
+                        return Err(syn::Error::new(ident.span(), "To derive Deserialize, the feature `serde` of the crate `nutype` needs to be enabled."));
+                    }
+                }
+            }
+            "JsonSchema" => {
+                cfg_if! {
+                    if #[cfg(feature = "schemars08")] {
+                        DeriveTrait::SchemarsJsonSchema
+                    } else {
+                        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(
+                    ident.span(),
+                    format!("#[nutype] does not know how to derive `{ident}` trait."),
+                ));
+            }
+        };
+        let spanned_trait = SpannedDeriveTrait {
+            item: derive_trait,
+            span: ident.span(),
+        };
+        Ok(spanned_trait)
+    }
+}
diff --git a/nutype_macros/src/common/parse/meta.rs b/nutype_macros/src/common/parse/meta.rs
index fb50bb2..38bbc56 100644
--- a/nutype_macros/src/common/parse/meta.rs
+++ b/nutype_macros/src/common/parse/meta.rs
@@ -5,7 +5,7 @@ use syn::{spanned::Spanned, Attribute, DeriveInput, Visibility};
 use crate::{
     common::{
         models::{InnerType, Meta, TypeName},
-        parse::{is_derive_attribute, is_doc_attribute, parse_derive_traits},
+        parse::{intercept_derive_macro, is_derive_attribute, is_doc_attribute},
     },
     float::models::FloatInnerType,
     integer::models::IntegerInnerType,
@@ -28,7 +28,7 @@ pub fn parse_meta(token_stream: TokenStream) -> Result<Meta, syn::Error> {
 
     validate_supported_attrs(&attrs)?;
 
-    let derive_traits = parse_derive_traits(&attrs)?;
+    intercept_derive_macro(&attrs)?;
     let doc_attrs: Vec<Attribute> = attrs.into_iter().filter(is_doc_attribute).collect();
 
     let data_struct = match &data {
@@ -107,7 +107,6 @@ pub fn parse_meta(token_stream: TokenStream) -> Result<Meta, syn::Error> {
         type_name,
         inner_type,
         vis,
-        derive_traits,
     })
 }
 
diff --git a/nutype_macros/src/common/parse/mod.rs b/nutype_macros/src/common/parse/mod.rs
index 6309428..c36b3b6 100644
--- a/nutype_macros/src/common/parse/mod.rs
+++ b/nutype_macros/src/common/parse/mod.rs
@@ -1,9 +1,10 @@
+pub mod derive_trait;
 pub mod meta;
 
 use std::{any::type_name, fmt::Debug, str::FromStr};
 
 use cfg_if::cfg_if;
-use proc_macro2::{Ident, Span, TokenTree};
+use proc_macro2::{Ident, Span};
 use syn::{
     parenthesized,
     parse::{Parse, ParseStream},
@@ -12,7 +13,7 @@ use syn::{
     Expr, Lit, Token,
 };
 
-use crate::common::models::{DeriveTrait, SpannedDeriveTrait};
+use crate::common::models::SpannedDeriveTrait;
 
 use super::models::{CustomFunction, NewUnchecked, TypedCustomFunction};
 
@@ -30,135 +31,39 @@ pub fn is_derive_attribute(attribute: &syn::Attribute) -> bool {
     }
 }
 
-pub fn parse_derive_traits(
-    attributes: &[syn::Attribute],
-) -> Result<Vec<SpannedDeriveTrait>, syn::Error> {
-    let traits: Vec<Vec<SpannedDeriveTrait>> = attributes
-        .iter()
-        .filter(|a| is_derive_attribute(a))
-        .map(parse_derive_attr)
-        .collect::<Result<_, syn::Error>>()?;
-    Ok(traits.into_iter().flatten().collect())
-}
-
-fn parse_derive_attr(attr: &syn::Attribute) -> Result<Vec<SpannedDeriveTrait>, syn::Error> {
-    let meta = &attr.meta;
-    match meta {
-        syn::Meta::Path(path) => {
-            let msg = format!("Expected #[derive(...)], got: {path:?}");
-            Err(syn::Error::new(Span::call_site(), msg))
-        }
-        syn::Meta::NameValue(name_value) => {
-            let msg = format!("Expected #[derive(...)], got: {name_value:?}");
-            Err(syn::Error::new(Span::call_site(), msg))
-        }
-        syn::Meta::List(list) => {
-            let derive_traits: Vec<SpannedDeriveTrait> = list
-                .tokens
-                .clone()
-                .into_iter()
-                .map(parse_token_into_derive_trait)
-                .collect::<Result<Vec<Option<SpannedDeriveTrait>>, syn::Error>>()?
-                .into_iter()
-                .flatten()
-                .collect();
-            Ok(derive_traits)
-        }
-    }
-}
-
-fn parse_token_into_derive_trait(
-    token: TokenTree,
-) -> Result<Option<SpannedDeriveTrait>, syn::Error> {
-    match token {
-        TokenTree::Ident(ident) => {
-            let derive_trait = parse_ident_into_derive_trait(ident)?;
-            Ok(Some(derive_trait))
+pub fn intercept_derive_macro(attributes: &[syn::Attribute]) -> Result<(), syn::Error> {
+    // Return an error if attempt to use `#[derive(..)]` is detected.
+    for attr in attributes.iter() {
+        if is_derive_attribute(attr) {
+            let msg = concat!(
+                "#[derive(..)] macro is not allowed to be used with #[nutype]. ",
+                "If you want to derive traits use `derive(..) attribute within #[nutype] macro:\n\n",
+                "    #[nutype(\n",
+                "        derive(Debug, Clone, AsRef)\n",
+                "    )]\n\n",
+            );
+            return Err(syn::Error::new(attr.span(), msg));
         }
-        TokenTree::Punct(ref punct) => match punct.as_char() {
-            ',' => Ok(None),
-            '*' => Err(syn::Error::new(
-                token.span(),
-                "Asterisk derive is not longer supported",
-            )),
-            _ => Err(syn::Error::new(
-                token.span(),
-                format!("Unexpected `{token}`"),
-            )),
-        },
-        _ => Err(syn::Error::new(
-            token.span(),
-            format!("Unexpected `{token}`"),
-        )),
     }
-}
-
-fn parse_ident_into_derive_trait(ident: Ident) -> Result<SpannedDeriveTrait, syn::Error> {
-    let derive_trait = match ident.to_string().as_ref() {
-        "Debug" => DeriveTrait::Debug,
-        "Display" => DeriveTrait::Display,
-        "Clone" => DeriveTrait::Clone,
-        "Copy" => DeriveTrait::Copy,
-        "PartialEq" => DeriveTrait::PartialEq,
-        "Eq" => DeriveTrait::Eq,
-        "PartialOrd" => DeriveTrait::PartialOrd,
-        "Ord" => DeriveTrait::Ord,
-        "FromStr" => DeriveTrait::FromStr,
-        "AsRef" => DeriveTrait::AsRef,
-        "Deref" => DeriveTrait::Deref,
-        "TryFrom" => DeriveTrait::TryFrom,
-        "From" => DeriveTrait::From,
-        "Into" => DeriveTrait::Into,
-        "Hash" => DeriveTrait::Hash,
-        "Borrow" => DeriveTrait::Borrow,
-        "Default" => DeriveTrait::Default,
-        "Serialize" => {
-            cfg_if! {
-                if #[cfg(feature = "serde")] {
-                    DeriveTrait::SerdeSerialize
-                } else {
-                    return Err(syn::Error::new(ident.span(), "To derive Serialize, the feature `serde` of the crate `nutype` needs to be enabled."));
-                }
-            }
-        }
-        "Deserialize" => {
-            cfg_if! {
-                if #[cfg(feature = "serde")] {
-                    DeriveTrait::SerdeDeserialize
-                } else {
-                    return Err(syn::Error::new(ident.span(), "To derive Deserialize, the feature `serde` of the crate `nutype` needs to be enabled."));
-                }
-            }
-        }
-        "JsonSchema" => {
-            cfg_if! {
-                if #[cfg(feature = "schemars08")] {
-                    DeriveTrait::SchemarsJsonSchema
-                } else {
-                    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(
-                ident.span(),
-                format!("unsupported trait derive: {ident}"),
-            ));
-        }
-    };
-    let spanned_trait = SpannedDeriveTrait {
-        item: derive_trait,
-        span: ident.span(),
-    };
-    Ok(spanned_trait)
+    Ok(())
 }
 
 #[derive(Debug)]
 pub struct ParseableAttributes<Sanitizer, Validator> {
+    /// Parsed from `sanitize(...)` attribute
     pub sanitizers: Vec<Sanitizer>,
+
+    /// Parsed from `validate(...)` attribute
     pub validators: Vec<Validator>,
+
+    /// Parsed from `new_unchecked` attribute
     pub new_unchecked: NewUnchecked,
+
+    /// Parsed from `new_unchecked` attribute
     pub default: Option<Expr>,
+
+    /// Parsed from `derive(...)` attribute
+    pub derive_traits: Vec<SpannedDeriveTrait>,
 }
 
 // By some reason Default cannot be derived.
@@ -169,6 +74,7 @@ impl<Sanitizer, Validator> Default for ParseableAttributes<Sanitizer, Validator>
             validators: vec![],
             new_unchecked: NewUnchecked::Off,
             default: None,
+            derive_traits: vec![],
         }
     }
 }
@@ -207,6 +113,20 @@ impl<Sanitizer: Parse, Validator: Parse> Parse for ParseableAttributes<Sanitizer
                     );
                     return Err(syn::Error::new(ident.span(), msg));
                 }
+            } else if ident == "derive" {
+                if input.peek(Paren) {
+                    let content;
+                    parenthesized!(content in input);
+                    let items = content.parse_terminated(SpannedDeriveTrait::parse, Token![,])?;
+                    attrs.derive_traits = items.into_iter().collect();
+                } else {
+                    let msg = concat!(
+                        "`derive` must be used with parenthesis.\n",
+                        "For example:\n\n",
+                        "    derive(Debug, Clone, AsRef)\n\n"
+                    );
+                    return Err(syn::Error::new(ident.span(), msg));
+                }
             } else if ident == "default" {
                 let _eq: Token![=] = input.parse()?;
                 let default_expr: Expr = input.parse()?;
diff --git a/nutype_macros/src/float/mod.rs b/nutype_macros/src/float/mod.rs
index e234b17..212b8cf 100644
--- a/nutype_macros/src/float/mod.rs
+++ b/nutype_macros/src/float/mod.rs
@@ -10,7 +10,7 @@ use quote::ToTokens;
 
 use crate::common::{
     gen::GenerateNewtype,
-    models::{Attributes, DeriveTrait, GenerateParams, Guard, Newtype, SpannedItem},
+    models::{Attributes, GenerateParams, Guard, Newtype, SpannedDeriveTrait},
 };
 
 use self::{
@@ -37,13 +37,15 @@ where
     type TypedTrait = FloatDeriveTrait;
     type InnerType = FloatInnerType;
 
-    fn parse_attributes(attrs: TokenStream) -> Result<Attributes<FloatGuard<T>>, syn::Error> {
+    fn parse_attributes(
+        attrs: TokenStream,
+    ) -> Result<Attributes<FloatGuard<T>, SpannedDeriveTrait>, syn::Error> {
         parse::parse_attributes::<T>(attrs)
     }
 
     fn validate(
         guard: &Guard<Self::Sanitizer, Self::Validator>,
-        derive_traits: Vec<SpannedItem<DeriveTrait>>,
+        derive_traits: Vec<SpannedDeriveTrait>,
     ) -> Result<HashSet<Self::TypedTrait>, syn::Error> {
         validate_float_derive_traits(derive_traits, guard)
     }
diff --git a/nutype_macros/src/float/parse.rs b/nutype_macros/src/float/parse.rs
index 7fde46d..c46a025 100644
--- a/nutype_macros/src/float/parse.rs
+++ b/nutype_macros/src/float/parse.rs
@@ -4,7 +4,7 @@ use std::{
 };
 
 use crate::common::{
-    models::Attributes,
+    models::{Attributes, SpannedDeriveTrait},
     parse::{parse_number, parse_typed_custom_function, ParseableAttributes},
 };
 use proc_macro2::{Ident, TokenStream};
@@ -21,7 +21,9 @@ use super::{
     validate::validate_number_meta,
 };
 
-pub fn parse_attributes<T>(input: TokenStream) -> Result<Attributes<FloatGuard<T>>, syn::Error>
+pub fn parse_attributes<T>(
+    input: TokenStream,
+) -> Result<Attributes<FloatGuard<T>, SpannedDeriveTrait>, syn::Error>
 where
     T: FromStr + PartialOrd + Clone,
     <T as FromStr>::Err: Debug + Display,
@@ -34,6 +36,7 @@ where
         validators,
         new_unchecked,
         default,
+        derive_traits,
     } = attrs;
     let raw_guard = FloatRawGuard {
         sanitizers,
@@ -44,6 +47,7 @@ where
         new_unchecked,
         guard,
         default,
+        derive_traits,
     })
 }
 
diff --git a/nutype_macros/src/integer/mod.rs b/nutype_macros/src/integer/mod.rs
index 0de860a..314f286 100644
--- a/nutype_macros/src/integer/mod.rs
+++ b/nutype_macros/src/integer/mod.rs
@@ -10,7 +10,7 @@ use quote::ToTokens;
 
 use crate::common::{
     gen::GenerateNewtype,
-    models::{Attributes, DeriveTrait, GenerateParams, Guard, Newtype, SpannedItem},
+    models::{Attributes, GenerateParams, Guard, Newtype, SpannedDeriveTrait},
 };
 
 use self::{
@@ -38,13 +38,15 @@ where
     type TypedTrait = IntegerDeriveTrait;
     type InnerType = IntegerInnerType;
 
-    fn parse_attributes(attrs: TokenStream) -> Result<Attributes<IntegerGuard<T>>, syn::Error> {
+    fn parse_attributes(
+        attrs: TokenStream,
+    ) -> Result<Attributes<IntegerGuard<T>, SpannedDeriveTrait>, syn::Error> {
         parse::parse_attributes::<T>(attrs)
     }
 
     fn validate(
         guard: &Guard<Self::Sanitizer, Self::Validator>,
-        derive_traits: Vec<SpannedItem<DeriveTrait>>,
+        derive_traits: Vec<SpannedDeriveTrait>,
     ) -> Result<HashSet<Self::TypedTrait>, syn::Error> {
         let has_validation = guard.has_validation();
         validate_integer_derive_traits(derive_traits, has_validation)
diff --git a/nutype_macros/src/integer/parse.rs b/nutype_macros/src/integer/parse.rs
index 22b4db7..791af86 100644
--- a/nutype_macros/src/integer/parse.rs
+++ b/nutype_macros/src/integer/parse.rs
@@ -4,7 +4,7 @@ use std::{
 };
 
 use crate::common::{
-    models::Attributes,
+    models::{Attributes, SpannedDeriveTrait},
     parse::{parse_number, parse_typed_custom_function, ParseableAttributes},
 };
 use proc_macro2::{Ident, TokenStream};
@@ -21,7 +21,9 @@ use super::{
     validate::validate_number_meta,
 };
 
-pub fn parse_attributes<T>(input: TokenStream) -> Result<Attributes<IntegerGuard<T>>, syn::Error>
+pub fn parse_attributes<T>(
+    input: TokenStream,
+) -> Result<Attributes<IntegerGuard<T>, SpannedDeriveTrait>, syn::Error>
 where
     T: FromStr + PartialOrd + Clone,
     <T as FromStr>::Err: Debug + Display,
@@ -34,6 +36,7 @@ where
         validators,
         new_unchecked,
         default,
+        derive_traits,
     } = attrs;
     let raw_guard = IntegerRawGuard {
         sanitizers,
@@ -44,6 +47,7 @@ where
         new_unchecked,
         guard,
         default,
+        derive_traits,
     })
 }
 
diff --git a/nutype_macros/src/string/mod.rs b/nutype_macros/src/string/mod.rs
index efce450..a042596 100644
--- a/nutype_macros/src/string/mod.rs
+++ b/nutype_macros/src/string/mod.rs
@@ -7,7 +7,7 @@ use std::collections::HashSet;
 
 use crate::common::{
     gen::GenerateNewtype,
-    models::{Attributes, DeriveTrait, GenerateParams, Newtype, SpannedItem},
+    models::{Attributes, GenerateParams, Newtype, SpannedDeriveTrait},
 };
 
 use models::{StringDeriveTrait, StringSanitizer, StringValidator};
@@ -26,13 +26,15 @@ impl Newtype for StringNewtype {
     type TypedTrait = StringDeriveTrait;
     type InnerType = StringInnerType;
 
-    fn parse_attributes(attrs: TokenStream) -> Result<Attributes<StringGuard>, syn::Error> {
+    fn parse_attributes(
+        attrs: TokenStream,
+    ) -> Result<Attributes<StringGuard, SpannedDeriveTrait>, syn::Error> {
         parse::parse_attributes(attrs)
     }
 
     fn validate(
         guard: &StringGuard,
-        derive_traits: Vec<SpannedItem<DeriveTrait>>,
+        derive_traits: Vec<SpannedDeriveTrait>,
     ) -> Result<HashSet<Self::TypedTrait>, syn::Error> {
         validate_string_derive_traits(guard, derive_traits)
     }
diff --git a/nutype_macros/src/string/parse.rs b/nutype_macros/src/string/parse.rs
index 9e4bbb6..5bc8f17 100644
--- a/nutype_macros/src/string/parse.rs
+++ b/nutype_macros/src/string/parse.rs
@@ -1,6 +1,6 @@
 use crate::{
     common::{
-        models::{Attributes, SpannedItem},
+        models::{Attributes, SpannedDeriveTrait, SpannedItem},
         parse::{parse_number, parse_typed_custom_function_raw, ParseableAttributes},
     },
     string::models::{StringGuard, StringRawGuard, StringSanitizer, StringValidator},
@@ -18,7 +18,9 @@ use super::{
     validate::validate_string_meta,
 };
 
-pub fn parse_attributes(input: TokenStream) -> Result<Attributes<StringGuard>, syn::Error> {
+pub fn parse_attributes(
+    input: TokenStream,
+) -> Result<Attributes<StringGuard, SpannedDeriveTrait>, syn::Error> {
     let attrs: ParseableAttributes<SpannedStringSanitizer, SpannedStringValidator> =
         syn::parse2(input)?;
 
@@ -27,6 +29,7 @@ pub fn parse_attributes(input: TokenStream) -> Result<Attributes<StringGuard>, s
         validators,
         new_unchecked,
         default,
+        derive_traits,
     } = attrs;
     let raw_guard = StringRawGuard {
         sanitizers,
@@ -37,6 +40,7 @@ pub fn parse_attributes(input: TokenStream) -> Result<Attributes<StringGuard>, s
         new_unchecked,
         guard,
         default,
+        derive_traits,
     })
 }
 


Test patch:
diff --git a/test_suite/tests/float.rs b/test_suite/tests/float.rs
index 2fca1e2..5f32cdc 100644
--- a/test_suite/tests/float.rs
+++ b/test_suite/tests/float.rs
@@ -40,8 +40,7 @@ mod sanitizers {
 
     #[test]
     fn test_from_trait() {
-        #[nutype]
-        #[derive(From)]
+        #[nutype(derive(From))]
         struct Age(f64);
 
         assert_eq!(Age::from(17.0).into_inner(), 17.0);
@@ -54,8 +53,10 @@ mod validators {
 
     #[test]
     fn test_min() {
-        #[nutype(validate(min = 18.0))]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(min = 18.0),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         struct Age(f32);
 
         assert_eq!(Age::new(17.0).unwrap_err(), AgeError::TooSmall);
@@ -64,8 +65,10 @@ mod validators {
 
     #[test]
     fn test_max() {
-        #[nutype(validate(max = 99.0))]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(max = 99.0),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         struct Age(f32);
 
         assert_eq!(Age::new(100.0).unwrap_err(), AgeError::TooBig);
@@ -74,8 +77,10 @@ mod validators {
 
     #[test]
     fn test_min_and_max() {
-        #[nutype(validate(min = 18.0, max = 99.0))]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(min = 18.0, max = 99.0),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         struct Age(f32);
 
         assert_eq!(Age::new(17.9).unwrap_err(), AgeError::TooSmall);
@@ -85,8 +90,7 @@ mod validators {
 
     #[test]
     fn test_finite_f64() {
-        #[nutype(validate(finite))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(finite), derive(Debug, PartialEq))]
         struct Dist(f64);
 
         // invalid
@@ -104,8 +108,10 @@ mod validators {
 
     #[test]
     fn test_finite_f32() {
-        #[nutype(validate(finite))]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(finite),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         struct Dist(f32);
 
         // invalid
@@ -124,8 +130,7 @@ mod validators {
 
         #[test]
         fn test_with_closure_with_explicit_type() {
-            #[nutype(validate(with = |&c: &f32| (0.0..=100.0).contains(&c) ))]
-            #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+            #[nutype(validate(with = |&c: &f32| (0.0..=100.0).contains(&c) ), derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef))]
             pub struct Cent(f32);
 
             assert_eq!(Cent::new(-0.1), Err(CentError::Invalid));
@@ -136,8 +141,7 @@ mod validators {
 
         #[test]
         fn test_closure_with_no_type() {
-            #[nutype(validate(with = |&c| (0.0..=100.0).contains(&c) ))]
-            #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+            #[nutype(validate(with = |&c| (0.0..=100.0).contains(&c) ), derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef))]
             pub struct Cent(f32);
 
             assert_eq!(Cent::new(-0.1), Err(CentError::Invalid));
@@ -152,8 +156,7 @@ mod validators {
 
         #[test]
         fn test_with_function() {
-            #[nutype(validate(with = is_cent_valid))]
-            #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+            #[nutype(validate(with = is_cent_valid), derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef))]
             pub struct Cent(f32);
 
             assert_eq!(Cent::new(-0.1), Err(CentError::Invalid));
@@ -165,8 +168,10 @@ mod validators {
 
     #[test]
     fn test_try_from_trait() {
-        #[nutype(validate(min = 18.0))]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(min = 18.0),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         struct Age(f64);
 
         assert_eq!(Age::try_from(17.9).unwrap_err(), AgeError::TooSmall);
@@ -175,8 +180,7 @@ mod validators {
 
     #[test]
     fn test_try_from_trait_without_validation() {
-        #[nutype]
-        #[derive(Debug, PartialEq, TryFrom)]
+        #[nutype(derive(Debug, PartialEq, TryFrom))]
         struct Age(f64);
 
         assert_eq!(Age::try_from(78.8).unwrap().into_inner(), 78.8);
@@ -188,8 +192,10 @@ mod validators {
 
         #[test]
         fn test_error_display() {
-            #[nutype(validate(min = 0.0))]
-            #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
+            #[nutype(
+                validate(min = 0.0),
+                derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
+            )]
             struct Percentage(f64);
 
             let err = Percentage::try_from(-0.1).unwrap_err();
@@ -205,8 +211,7 @@ mod types {
 
     #[test]
     fn test_f32_validate() {
-        #[nutype(validate(min = 0.0, max = 100))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min = 0.0, max = 100), derive(Debug, PartialEq))]
         pub struct Width(f32);
 
         assert_eq!(Width::new(-0.0001), Err(WidthError::TooSmall));
@@ -217,8 +222,7 @@ mod types {
 
     #[test]
     fn test_f64_validate() {
-        #[nutype(validate(min = 0.0, max = 100))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min = 0.0, max = 100), derive(Debug, PartialEq))]
         pub struct Width(f64);
 
         assert_eq!(Width::new(-0.0001), Err(WidthError::TooSmall));
@@ -234,9 +238,9 @@ mod types {
     fn test_f64_negative() {
         #[nutype(
             sanitize(with = |n| n.clamp(-200.25, -5.0)),
-            validate(min = -100.25, max = -50.1)
+            validate(min = -100.25, max = -50.1),
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)
         )]
-        #[derive(TryFrom, Debug, Clone, Copy, PartialEq, PartialOrd, FromStr, AsRef)]
         pub struct Balance(f64);
 
         assert_eq!(Balance::new(-300.0), Err(BalanceError::TooSmall));
@@ -270,8 +274,7 @@ mod traits {
 
     #[test]
     fn test_without_validation() {
-        #[nutype]
-        #[derive(Debug, From, FromStr, Borrow, Clone, Copy)]
+        #[nutype(derive(Debug, From, FromStr, Borrow, Clone, Copy))]
         pub struct Dist(f32);
 
         should_implement_debug::<Dist>();
@@ -284,8 +287,10 @@ mod traits {
 
     #[test]
     fn test_with_validaiton() {
-        #[nutype(validate(max = 100.0))]
-        #[derive(Debug, TryFrom, FromStr, Borrow, Clone, Copy)]
+        #[nutype(
+            validate(max = 100.0),
+            derive(Debug, TryFrom, FromStr, Borrow, Clone, Copy)
+        )]
         pub struct Dist(f64);
 
         should_implement_debug::<Dist>();
@@ -298,8 +303,7 @@ mod traits {
 
     #[test]
     fn test_trait_into() {
-        #[nutype]
-        #[derive(Into)]
+        #[nutype(derive(Into))]
         pub struct Size(f64);
 
         let size = Size::new(35.7);
@@ -309,8 +313,7 @@ mod traits {
 
     #[test]
     fn test_trait_from() {
-        #[nutype]
-        #[derive(From)]
+        #[nutype(derive(From))]
         pub struct Size(f64);
 
         let size = Size::from(35.7);
@@ -319,8 +322,7 @@ mod traits {
 
     #[test]
     fn test_trait_as_ref() {
-        #[nutype]
-        #[derive(AsRef)]
+        #[nutype(derive(AsRef))]
         pub struct Weight(f32);
 
         let weight = Weight::new(72.650);
@@ -330,8 +332,7 @@ mod traits {
 
     #[test]
     fn test_trait_deref() {
-        #[nutype]
-        #[derive(Deref)]
+        #[nutype(derive(Deref))]
         pub struct Number(f64);
 
         let magic = Number::new(42.0);
@@ -342,8 +343,7 @@ mod traits {
     fn test_trait_borrow() {
         use std::borrow::Borrow;
 
-        #[nutype]
-        #[derive(Borrow)]
+        #[nutype(derive(Borrow))]
         pub struct Age(f64);
 
         let age = Age::new(32.0);
@@ -353,8 +353,7 @@ mod traits {
 
     #[test]
     fn test_trait_try_from() {
-        #[nutype(validate(max = 12.34))]
-        #[derive(Debug, TryFrom)]
+        #[nutype(validate(max = 12.34), derive(Debug, TryFrom))]
         pub struct Dist(f64);
 
         let dist = Dist::try_from(12.34).unwrap();
@@ -366,8 +365,7 @@ mod traits {
 
     #[test]
     fn test_trait_from_str_without_validation() {
-        #[nutype]
-        #[derive(Debug, FromStr)]
+        #[nutype(derive(Debug, FromStr))]
         pub struct Dist(f64);
 
         let dist: Dist = "33.4".parse().unwrap();
@@ -382,8 +380,7 @@ mod traits {
 
     #[test]
     fn test_trait_from_str_with_validation() {
-        #[nutype(validate(max = 12.34))]
-        #[derive(Debug, FromStr)]
+        #[nutype(validate(max = 12.34), derive(Debug, FromStr))]
         pub struct Dist(f64);
 
         // Happy path
@@ -403,8 +400,7 @@ mod traits {
 
     #[test]
     fn test_trait_display() {
-        #[nutype]
-        #[derive(Display)]
+        #[nutype(derive(Display))]
         pub struct Size(f64);
 
         let size = Size::new(35.7);
@@ -413,8 +409,7 @@ mod traits {
 
     #[test]
     fn test_trait_eq() {
-        #[nutype(validate(finite))]
-        #[derive(PartialEq, Eq, Debug)]
+        #[nutype(validate(finite), derive(PartialEq, Eq, Debug))]
         pub struct Size(f64);
 
         should_implement_eq::<Size>();
@@ -431,8 +426,7 @@ mod traits {
 
         #[test]
         fn test_trait_ord_f32() {
-            #[nutype(validate(finite))]
-            #[derive(PartialEq, Eq, PartialOrd, Ord)]
+            #[nutype(validate(finite), derive(PartialEq, Eq, PartialOrd, Ord))]
             pub struct Size(f32);
 
             let a: Size = Size::new(2.5).unwrap();
@@ -446,8 +440,7 @@ mod traits {
 
         #[test]
         fn test_trait_ord_f64() {
-            #[nutype(validate(finite))]
-            #[derive(PartialEq, Eq, PartialOrd, Ord)]
+            #[nutype(validate(finite), derive(PartialEq, Eq, PartialOrd, Ord))]
             pub struct Size(f64);
 
             let a: Size = Size::new(2.5).unwrap();
@@ -461,8 +454,7 @@ mod traits {
 
         #[test]
         fn test_sort() {
-            #[nutype(validate(finite))]
-            #[derive(PartialEq, Eq, PartialOrd, Ord)]
+            #[nutype(validate(finite), derive(PartialEq, Eq, PartialOrd, Ord))]
             pub struct Size(f64);
 
             let initial_raw_sizes = vec![5.5, 2.0, 15.0, 44.5, 3.5];
@@ -482,8 +474,7 @@ mod traits {
 
             #[test]
             fn cmp_never_panics_f32() {
-                #[nutype(validate(finite))]
-                #[derive(PartialEq, Eq, PartialOrd, Ord)]
+                #[nutype(validate(finite), derive(PartialEq, Eq, PartialOrd, Ord))]
                 pub struct Size(f32);
 
                 fn prop(u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
@@ -501,8 +492,7 @@ mod traits {
 
             #[test]
             fn cmp_never_panics_f64() {
-                #[nutype(validate(finite))]
-                #[derive(PartialEq, Eq, PartialOrd, Ord)]
+                #[nutype(validate(finite), derive(PartialEq, Eq, PartialOrd, Ord))]
                 pub struct Size(f64);
 
                 fn prop(u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
@@ -523,8 +513,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_serialize() {
-        #[nutype]
-        #[derive(Serialize)]
+        #[nutype(derive(Serialize))]
         pub struct Offset(f64);
 
         let offset = Offset::new(-33.5);
@@ -535,8 +524,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_deserialize_without_validation() {
-        #[nutype]
-        #[derive(Deserialize)]
+        #[nutype(derive(Deserialize))]
         pub struct Offset(f64);
 
         {
@@ -553,8 +541,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_deserialize_with_validation() {
-        #[nutype(validate(min = 13.3))]
-        #[derive(Deserialize)]
+        #[nutype(validate(min = 13.3), derive(Deserialize))]
         pub struct Offset(f32);
 
         {
@@ -579,8 +566,7 @@ mod traits {
 
         #[test]
         fn test_default_without_validation() {
-            #[nutype(default = 13.0)]
-            #[derive(Default)]
+            #[nutype(default = 13.0, derive(Default))]
             pub struct Number(f32);
 
             assert_eq!(Number::default().into_inner(), 13.0);
@@ -588,8 +574,7 @@ mod traits {
 
         #[test]
         fn test_default_with_validation_when_valid() {
-            #[nutype(validate(max = 20.0), default = 13.0)]
-            #[derive(Default)]
+            #[nutype(validate(max = 20.0), default = 13.0, derive(Default))]
             pub struct Number(f64);
 
             assert_eq!(Number::default().into_inner(), 13.0);
@@ -598,8 +583,7 @@ mod traits {
         #[test]
         #[should_panic(expected = "Default value for type Number is invalid")]
         fn test_default_with_validation_when_invalid() {
-            #[nutype(validate(max = 20.0), default = 20.1)]
-            #[derive(Default)]
+            #[nutype(validate(max = 20.0), default = 20.1, derive(Default))]
             pub struct Number(f64);
 
             Number::default();
@@ -630,8 +614,7 @@ mod derive_schemars_json_schema {
 
     #[test]
     fn test_json_schema_derive() {
-        #[nutype]
-        #[derive(JsonSchema)]
+        #[nutype(derive(JsonSchema))]
         pub struct ProductWeight(f64);
 
         assert_eq!(ProductWeight::schema_name(), "ProductWeight");
diff --git a/test_suite/tests/integer.rs b/test_suite/tests/integer.rs
index 2c7d19c..e61ca35 100644
--- a/test_suite/tests/integer.rs
+++ b/test_suite/tests/integer.rs
@@ -40,9 +40,9 @@ mod sanitizers {
 
     #[test]
     fn test_from_trait() {
-        #[nutype(sanitize(with = |a| a.clamp(18, 99)))]
-        #[derive(
-            From, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash,
+        #[nutype(
+            sanitize(with = |a| a.clamp(18, 99)),
+            derive(From, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)
         )]
         struct Age(u8);
 
@@ -56,8 +56,12 @@ mod validators {
 
     #[test]
     fn test_min() {
-        #[nutype(validate(min = 18))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 18),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Age(u8);
 
         assert_eq!(Age::new(17).unwrap_err(), AgeError::TooSmall);
@@ -66,8 +70,12 @@ mod validators {
 
     #[test]
     fn test_max() {
-        #[nutype(validate(max = 99))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(max = 99),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Age(u8);
 
         assert_eq!(Age::new(100).unwrap_err(), AgeError::TooBig);
@@ -76,8 +84,12 @@ mod validators {
 
     #[test]
     fn test_min_and_max() {
-        #[nutype(validate(min = 18, max = 99))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 18, max = 99),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Age(u8);
 
         assert_eq!(Age::new(17).unwrap_err(), AgeError::TooSmall);
@@ -91,8 +103,7 @@ mod validators {
 
         #[test]
         fn test_with_closure_with_explicit_type() {
-            #[nutype(validate(with = |c: &i32| (0..=100).contains(c) ))]
-            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+            #[nutype(validate(with = |c: &i32| (0..=100).contains(c) ), derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash))]
             pub struct Cent(i32);
 
             assert_eq!(Cent::new(-10), Err(CentError::Invalid));
@@ -102,8 +113,7 @@ mod validators {
 
         #[test]
         fn test_closure_with_no_type() {
-            #[nutype(validate(with = |c| (0..=100).contains(c) ))]
-            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+            #[nutype(validate(with = |c| (0..=100).contains(c) ), derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash))]
             pub struct Cent(i32);
 
             assert_eq!(Cent::new(-10), Err(CentError::Invalid));
@@ -117,9 +127,9 @@ mod validators {
 
         #[test]
         fn test_with_function() {
-            #[nutype(validate(with = is_cent_valid))]
-            #[derive(
-                TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash,
+            #[nutype(
+                validate(with = is_cent_valid),
+                derive(TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash),
             )]
             pub struct Cent(i32);
 
@@ -131,9 +141,11 @@ mod validators {
 
     #[test]
     fn test_try_from_trait() {
-        #[nutype(validate(min = 18))]
-        #[derive(
-            TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash,
+        #[nutype(
+            validate(min = 18),
+            derive(
+                TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
         )]
         struct Age(u8);
 
@@ -143,8 +155,7 @@ mod validators {
 
     #[test]
     fn test_try_from_trait_without_validation() {
-        #[nutype]
-        #[derive(Debug, PartialEq, TryFrom)]
+        #[nutype(derive(Debug, PartialEq, TryFrom))]
         struct Age(u8);
 
         assert_eq!(Age::try_from(78).unwrap().into_inner(), 78);
@@ -156,9 +167,12 @@ mod validators {
 
         #[test]
         fn test_error_display() {
-            #[nutype(validate(min = 18))]
-            #[derive(
-                TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash,
+            #[nutype(
+                validate(min = 18),
+                derive(
+                    TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef,
+                    Hash
+                )
             )]
             struct Age(u8);
 
@@ -178,8 +192,8 @@ mod types {
         #[nutype(
             sanitize(with = |n| n.clamp(0, 200)),
             validate(min = 18, max = 99),
+            derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)
         )]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
         struct Age(u8);
 
         assert_eq!(Age::new(17), Err(AgeError::TooSmall));
@@ -189,8 +203,7 @@ mod types {
 
     #[test]
     fn test_u8_sanitize() {
-        #[nutype(sanitize(with = |n| n.clamp(10, 100)))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(sanitize(with = |n| n.clamp(10, 100)), derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash))]
         struct Percentage(u8);
 
         assert_eq!(Percentage::new(101), Percentage::new(100));
@@ -199,8 +212,12 @@ mod types {
 
     #[test]
     fn test_u16() {
-        #[nutype(validate(min = 18, max = 65000))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 18, max = 65000),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Age(u16);
 
         assert_eq!(Age::new(17), Err(AgeError::TooSmall));
@@ -210,8 +227,12 @@ mod types {
 
     #[test]
     fn test_u32() {
-        #[nutype(validate(min = 1000, max = 100_000))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 1000, max = 100_000),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Amount(u32);
 
         assert_eq!(Amount::new(17), Err(AmountError::TooSmall));
@@ -221,8 +242,12 @@ mod types {
 
     #[test]
     fn test_u64() {
-        #[nutype(validate(min = 1000, max = 18446744073709551000))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 1000, max = 18446744073709551000),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Amount(u64);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -232,8 +257,12 @@ mod types {
 
     #[test]
     fn test_u128() {
-        #[nutype(validate(min = 1000, max = 170141183460469231731687303715884105828))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 1000, max = 170141183460469231731687303715884105828),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Amount(u128);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -247,8 +276,7 @@ mod types {
 
     #[test]
     fn test_i8_sanitize() {
-        #[nutype(sanitize(with = |n| n.clamp(0, 100)))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(sanitize(with = |n| n.clamp(0, 100)), derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash))]
         struct Percentage(i8);
 
         assert_eq!(Percentage::new(101), Percentage::new(100));
@@ -257,8 +285,7 @@ mod types {
 
     #[test]
     fn test_i8_validate() {
-        #[nutype(validate(min = -20, max = 100))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(validate(min = -20, max = 100), derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash))]
         struct Offset(i8);
 
         assert_eq!(Offset::new(-21), Err(OffsetError::TooSmall));
@@ -270,8 +297,12 @@ mod types {
 
     #[test]
     fn test_i16_validate() {
-        #[nutype(validate(min = 1000, max = 32_000))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 1000, max = 32_000),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
         struct Amount(i16);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -282,8 +313,12 @@ mod types {
 
     #[test]
     fn test_i32_validate() {
-        #[nutype(validate(min = 1000, max = 320_000))]
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash)]
+        #[nutype(
+            validate(min = 1000, max = 320_000),
+            derive(
+                Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash
+            )
+        )]
 
         struct Amount(i32);
 
@@ -301,9 +336,7 @@ mod types {
         #[nutype(
             sanitize(with = |n| n.clamp(-200, -5)),
             validate(min = -100, max = -50),
-        )]
-        #[derive(
-            TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash,
+            derive(TryFrom, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromStr, AsRef, Hash),
         )]
         pub struct Balance(i32);
 
@@ -316,8 +349,10 @@ mod types {
 
     #[test]
     fn test_i64_validate() {
-        #[nutype(validate(min = 1000, max = 8446744073709551000))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(
+            validate(min = 1000, max = 8446744073709551000),
+            derive(Debug, PartialEq)
+        )]
         struct Amount(i64);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -328,8 +363,10 @@ mod types {
 
     #[test]
     fn test_i128_validate() {
-        #[nutype(validate(min = 1000, max = 70141183460469231731687303715884105000))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(
+            validate(min = 1000, max = 70141183460469231731687303715884105000),
+            derive(Debug, PartialEq)
+        )]
         struct Amount(i128);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -343,8 +380,7 @@ mod types {
 
     #[test]
     fn test_usize_validate() {
-        #[nutype(validate(min = 1000, max = 2000))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min = 1000, max = 2000), derive(Debug, PartialEq))]
         struct Amount(usize);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -355,8 +391,7 @@ mod types {
 
     #[test]
     fn test_isize_validate() {
-        #[nutype(validate(min = 1000, max = 2000))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min = 1000, max = 2000), derive(Debug, PartialEq))]
         struct Amount(isize);
 
         assert_eq!(Amount::new(999), Err(AmountError::TooSmall));
@@ -389,8 +424,7 @@ mod traits {
 
     #[test]
     fn test_without_validation() {
-        #[nutype]
-        #[derive(Debug, From, FromStr, Borrow, Clone, Copy)]
+        #[nutype(derive(Debug, From, FromStr, Borrow, Clone, Copy))]
         pub struct Number(i8);
 
         should_implement_debug::<Number>();
@@ -403,8 +437,10 @@ mod traits {
 
     #[test]
     fn test_with_validaiton() {
-        #[nutype(validate(max = 1000))]
-        #[derive(Debug, TryFrom, FromStr, Borrow, Clone, Copy)]
+        #[nutype(
+            validate(max = 1000),
+            derive(Debug, TryFrom, FromStr, Borrow, Clone, Copy)
+        )]
         pub struct Number(u128);
 
         should_implement_debug::<Number>();
@@ -417,8 +453,7 @@ mod traits {
 
     #[test]
     fn test_trait_into() {
-        #[nutype]
-        #[derive(Into)]
+        #[nutype(derive(Into))]
         pub struct Age(u8);
 
         let age = Age::new(32);
@@ -428,8 +463,7 @@ mod traits {
 
     #[test]
     fn test_trait_from() {
-        #[nutype]
-        #[derive(From)]
+        #[nutype(derive(From))]
         pub struct Amount(u32);
 
         let amount = Amount::from(350);
@@ -438,8 +472,7 @@ mod traits {
 
     #[test]
     fn test_trait_as_ref() {
-        #[nutype]
-        #[derive(AsRef)]
+        #[nutype(derive(AsRef))]
         pub struct Age(u8);
 
         let age = Age::new(32);
@@ -449,8 +482,7 @@ mod traits {
 
     #[test]
     fn test_trait_deref() {
-        #[nutype]
-        #[derive(Deref)]
+        #[nutype(derive(Deref))]
         pub struct Number(i16);
 
         let magic = Number::new(42);
@@ -461,8 +493,7 @@ mod traits {
     fn test_trait_borrow() {
         use std::borrow::Borrow;
 
-        #[nutype]
-        #[derive(Borrow)]
+        #[nutype(derive(Borrow))]
         pub struct Age(u8);
 
         let age = Age::new(32);
@@ -472,8 +503,7 @@ mod traits {
 
     #[test]
     fn test_trait_try_from() {
-        #[nutype(validate(max = 1000))]
-        #[derive(Debug, TryFrom)]
+        #[nutype(validate(max = 1000), derive(Debug, TryFrom))]
         pub struct Amount(i64);
 
         let amount = Amount::try_from(1000).unwrap();
@@ -485,8 +515,7 @@ mod traits {
 
     #[test]
     fn test_trait_from_str_without_validation() {
-        #[nutype]
-        #[derive(Debug, FromStr)]
+        #[nutype(derive(Debug, FromStr))]
         pub struct Age(i16);
 
         let age: Age = "33".parse().unwrap();
@@ -501,8 +530,7 @@ mod traits {
 
     #[test]
     fn test_trait_from_str_with_validation() {
-        #[nutype(validate(max = 99))]
-        #[derive(Debug, FromStr)]
+        #[nutype(validate(max = 99), derive(Debug, FromStr))]
         pub struct Age(isize);
 
         // Happy path
@@ -522,8 +550,7 @@ mod traits {
 
     #[test]
     fn test_trait_display() {
-        #[nutype]
-        #[derive(Display)]
+        #[nutype(derive(Display))]
         pub struct Age(i64);
 
         let age = Age::new(35);
@@ -533,8 +560,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_serialize() {
-        #[nutype]
-        #[derive(Serialize)]
+        #[nutype(derive(Serialize))]
         pub struct Offset(i64);
 
         let offset = Offset::new(-280);
@@ -545,8 +571,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_deserialize_without_validation() {
-        #[nutype]
-        #[derive(Deserialize)]
+        #[nutype(derive(Deserialize))]
         pub struct Offset(i64);
 
         {
@@ -563,8 +588,7 @@ mod traits {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_deserialize_with_validation() {
-        #[nutype(validate(min = 13))]
-        #[derive(Deserialize)]
+        #[nutype(validate(min = 13), derive(Deserialize))]
         pub struct Offset(i64);
 
         {
@@ -589,8 +613,7 @@ mod traits {
 
         #[test]
         fn test_default_without_validation() {
-            #[nutype(default = 13)]
-            #[derive(Default)]
+            #[nutype(default = 13, derive(Default))]
             pub struct Number(i8);
 
             assert_eq!(Number::default().into_inner(), 13);
@@ -598,8 +621,7 @@ mod traits {
 
         #[test]
         fn test_default_with_validation_when_valid() {
-            #[nutype(validate(max = 20), default = 13)]
-            #[derive(Default)]
+            #[nutype(validate(max = 20), default = 13, derive(Default))]
             pub struct Number(i8);
 
             assert_eq!(Number::default().into_inner(), 13);
@@ -608,8 +630,7 @@ mod traits {
         #[test]
         #[should_panic(expected = "Default value for type Number is invalid")]
         fn test_default_with_validation_when_invalid() {
-            #[nutype(validate(max = 20), default = 21)]
-            #[derive(Default)]
+            #[nutype(validate(max = 20), default = 21, derive(Default))]
             pub struct Number(i16);
 
             Number::default();
@@ -640,8 +661,7 @@ mod derive_schemars_json_schema {
 
     #[test]
     fn test_json_schema_derive() {
-        #[nutype]
-        #[derive(JsonSchema)]
+        #[nutype(derive(JsonSchema))]
         pub struct CustomerId(i128);
 
         assert_eq!(CustomerId::schema_name(), "CustomerId");
diff --git a/test_suite/tests/string.rs b/test_suite/tests/string.rs
index c9896a9..d3506f3 100644
--- a/test_suite/tests/string.rs
+++ b/test_suite/tests/string.rs
@@ -76,8 +76,7 @@ mod sanitizers {
 
     #[test]
     fn test_from_trait() {
-        #[nutype(sanitize(trim, lowercase))]
-        #[derive(Debug, PartialEq, From)]
+        #[nutype(sanitize(trim, lowercase), derive(Debug, PartialEq, From))]
         pub struct Email(String);
 
         assert_eq!(
@@ -93,8 +92,10 @@ mod validators {
 
     #[test]
     fn test_max_len() {
-        #[nutype(validate(max_len = 5))]
-        #[derive(TryFrom, Debug, Clone, PartialEq, PartialOrd, FromStr, AsRef)]
+        #[nutype(
+            validate(max_len = 5),
+            derive(TryFrom, Debug, Clone, PartialEq, PartialOrd, FromStr, AsRef)
+        )]
         pub struct Name(String);
 
         assert_eq!(Name::new("Anton").unwrap().into_inner(), "Anton");
@@ -106,8 +107,7 @@ mod validators {
 
     #[test]
     fn test_min_len() {
-        #[nutype(validate(min_len = 6))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min_len = 6), derive(Debug, PartialEq))]
         pub struct Name(String);
 
         assert_eq!(Name::new("Anton"), Err(NameError::TooShort));
@@ -119,8 +119,7 @@ mod validators {
 
     #[test]
     fn test_not_empty() {
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(not_empty), derive(Debug, PartialEq))]
         pub struct Name(String);
 
         assert_eq!(Name::new(""), Err(NameError::Empty));
@@ -130,8 +129,7 @@ mod validators {
 
     #[test]
     fn test_many_validators() {
-        #[nutype(validate(min_len = 3, max_len = 6))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(min_len = 3, max_len = 6), derive(Debug, PartialEq))]
         pub struct Name(String);
 
         assert_eq!(Name::new("Jo"), Err(NameError::TooShort));
@@ -145,8 +143,7 @@ mod validators {
 
         #[test]
         fn test_with_closure_with_explicit_type() {
-            #[nutype(validate(with = |e: &str| e.contains('@')))]
-            #[derive(Debug, PartialEq)]
+            #[nutype(validate(with = |e: &str| e.contains('@')), derive(Debug, PartialEq))]
             pub struct Email(String);
 
             assert_eq!(Email::new("foo.bar.example"), Err(EmailError::Invalid));
@@ -158,8 +155,7 @@ mod validators {
 
         #[test]
         fn test_closure_with_no_type() {
-            #[nutype(validate(with = |e| e.contains('@')))]
-            #[derive(Debug, PartialEq)]
+            #[nutype(validate(with = |e| e.contains('@')), derive(Debug, PartialEq))]
             pub struct Email(String);
 
             assert_eq!(Email::new("foo.bar.example"), Err(EmailError::Invalid));
@@ -175,8 +171,7 @@ mod validators {
 
         #[test]
         fn test_with_function() {
-            #[nutype(validate(with = validate_email))]
-            #[derive(Debug, PartialEq)]
+            #[nutype(validate(with = validate_email), derive(Debug, PartialEq))]
             pub struct Email(String);
 
             assert_eq!(Email::new("foo.bar.example"), Err(EmailError::Invalid));
@@ -189,8 +184,7 @@ mod validators {
 
     #[test]
     fn test_try_from_trait() {
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, PartialEq, TryFrom)]
+        #[nutype(validate(not_empty), derive(Debug, PartialEq, TryFrom))]
         pub struct Name(String);
 
         assert_eq!(Name::try_from(""), Err(NameError::Empty));
@@ -199,8 +193,7 @@ mod validators {
 
     #[test]
     fn test_try_from_trait_without_validation() {
-        #[nutype]
-        #[derive(Debug, PartialEq, TryFrom)]
+        #[nutype(derive(Debug, PartialEq, TryFrom))]
         pub struct Name(String);
 
         assert_eq!(Name::try_from("Tom").unwrap().into_inner(), "Tom");
@@ -210,8 +203,7 @@ mod validators {
     fn test_error() {
         fn ensure_type_implements_error<T: std::error::Error>() {}
 
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(not_empty), derive(Debug, PartialEq))]
         pub struct Email(String);
 
         ensure_type_implements_error::<EmailError>();
@@ -237,8 +229,8 @@ mod complex {
         #[nutype(
             sanitize(trim, with = |s| s.to_uppercase()),
             validate(not_empty, max_len = 6),
+            derive(Debug, PartialEq)
         )]
-        #[derive(Debug, PartialEq)]
         pub struct Name(String);
 
         assert_eq!(Name::new("    "), Err(NameError::Empty));
@@ -270,8 +262,7 @@ mod derives {
 
     #[test]
     fn test_without_validation() {
-        #[nutype]
-        #[derive(Debug, Hash, From, FromStr, Borrow, Clone)]
+        #[nutype(derive(Debug, Hash, From, FromStr, Borrow, Clone))]
         pub struct Name(String);
 
         should_implement_hash::<Name>();
@@ -286,8 +277,10 @@ mod derives {
 
     #[test]
     fn test_with_validaiton() {
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, Hash, TryFrom, FromStr, Borrow, Clone)]
+        #[nutype(
+            validate(not_empty),
+            derive(Debug, Hash, TryFrom, FromStr, Borrow, Clone)
+        )]
         pub struct Name(String);
 
         should_implement_hash::<Name>();
@@ -302,8 +295,7 @@ mod derives {
 
     #[test]
     fn test_trait_into() {
-        #[nutype(sanitize(trim))]
-        #[derive(Into)]
+        #[nutype(sanitize(trim), derive(Into))]
         pub struct Name(String);
 
         let name = Name::new("  Anna");
@@ -313,8 +305,7 @@ mod derives {
 
     #[test]
     fn test_trait_from_str() {
-        #[nutype]
-        #[derive(From)]
+        #[nutype(derive(From))]
         pub struct Name(String);
 
         let name = Name::from("Anna");
@@ -323,8 +314,7 @@ mod derives {
 
     #[test]
     fn test_trait_from_string() {
-        #[nutype]
-        #[derive(From)]
+        #[nutype(derive(From))]
         pub struct Name(String);
 
         let name = Name::from("Anna".to_string());
@@ -333,8 +323,7 @@ mod derives {
 
     #[test]
     fn test_trait_as_ref() {
-        #[nutype]
-        #[derive(AsRef)]
+        #[nutype(derive(AsRef))]
         pub struct Name(String);
 
         let name = Name::new("Anna");
@@ -344,8 +333,7 @@ mod derives {
 
     #[test]
     fn test_trait_deref() {
-        #[nutype]
-        #[derive(Deref)]
+        #[nutype(derive(Deref))]
         pub struct Name(String);
 
         let name = Name::new("Anna");
@@ -359,8 +347,7 @@ mod derives {
     fn test_trait_borrow_str() {
         use std::borrow::Borrow;
 
-        #[nutype]
-        #[derive(Borrow)]
+        #[nutype(derive(Borrow))]
         pub struct Name(String);
 
         let name = Name::new("Anna");
@@ -372,8 +359,7 @@ mod derives {
     fn test_trait_borrow_string() {
         use std::borrow::Borrow;
 
-        #[nutype]
-        #[derive(Borrow)]
+        #[nutype(derive(Borrow))]
         pub struct Name(String);
 
         let name = Name::new("Anna");
@@ -383,8 +369,7 @@ mod derives {
 
     #[test]
     fn test_trait_try_from_str() {
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, TryFrom)]
+        #[nutype(validate(not_empty), derive(Debug, TryFrom))]
         pub struct Name(String);
 
         let name = Name::try_from("Anna").unwrap();
@@ -396,8 +381,7 @@ mod derives {
 
     #[test]
     fn test_trait_try_from_string() {
-        #[nutype(validate(not_empty))]
-        #[derive(Debug, TryFrom)]
+        #[nutype(validate(not_empty), derive(Debug, TryFrom))]
         pub struct Name(String);
 
         let name = Name::try_from("Anna".to_string()).unwrap();
@@ -409,8 +393,7 @@ mod derives {
 
     #[test]
     fn test_trait_display() {
-        #[nutype]
-        #[derive(Display)]
+        #[nutype(derive(Display))]
         pub struct Name(String);
 
         let name = Name::new("Serhii");
@@ -423,8 +406,7 @@ mod derives {
 
         #[test]
         fn test_default_without_validation() {
-            #[nutype(default = "Anonymous")]
-            #[derive(Default)]
+            #[nutype(default = "Anonymous", derive(Default))]
             pub struct Name(String);
 
             assert_eq!(Name::default().into_inner(), "Anonymous");
@@ -432,8 +414,7 @@ mod derives {
 
         #[test]
         fn test_default_with_validation_when_valid() {
-            #[nutype(validate(min_len = 5), default = "Anonymous")]
-            #[derive(Default)]
+            #[nutype(validate(min_len = 5), default = "Anonymous", derive(Default))]
             pub struct Name(String);
 
             assert_eq!(Name::default().into_inner(), "Anonymous");
@@ -442,8 +423,7 @@ mod derives {
         #[test]
         #[should_panic(expected = "Default value for type Name is invalid")]
         fn test_default_with_validation_when_invalid() {
-            #[nutype(validate(min_len = 5), default = "Nope")]
-            #[derive(Default)]
+            #[nutype(validate(min_len = 5), default = "Nope", derive(Default))]
             pub struct Name(String);
 
             Name::default();
@@ -453,8 +433,7 @@ mod derives {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_serialize() {
-        #[nutype]
-        #[derive(Serialize)]
+        #[nutype(derive(Serialize))]
         pub struct Email(String);
 
         let email = Email::new("my@example.com");
@@ -465,8 +444,7 @@ mod derives {
     #[cfg(feature = "serde")]
     #[test]
     fn test_trait_deserialize_without_validation() {
-        #[nutype]
-        #[derive(Deserialize)]
+        #[nutype(derive(Deserialize))]
         pub struct NaiveEmail(String);
 
         {
@@ -479,9 +457,9 @@ mod derives {
     #[test]
     fn test_trait_deserialize_with_validation() {
         #[nutype(
-            validate(with = |address| address.contains('@') )
+            validate(with = |address| address.contains('@') ),
+            derive(Deserialize),
         )]
-        #[derive(Deserialize)]
         pub struct NaiveEmail(String);
 
         {
@@ -519,8 +497,7 @@ mod derive_schemars_json_schema {
 
     #[test]
     fn test_json_schema_derive() {
-        #[nutype]
-        #[derive(JsonSchema)]
+        #[nutype(derive(JsonSchema))]
         pub struct CustomerIdentifier(String);
 
         assert_eq!(CustomerIdentifier::schema_name(), "CustomerIdentifier");
@@ -546,8 +523,7 @@ mod validation_with_regex {
 
     #[test]
     fn test_regex_as_string() {
-        #[nutype(validate(regex = "^[0-9]{3}-[0-9]{3}$"))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(regex = "^[0-9]{3}-[0-9]{3}$"), derive(Debug, PartialEq))]
         pub struct PhoneNumber(String);
 
         // Invalid
@@ -563,8 +539,7 @@ mod validation_with_regex {
 
     #[test]
     fn test_regex_with_lazy_static() {
-        #[nutype(validate(regex = PHONE_REGEX_LAZY_STATIC))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(regex = PHONE_REGEX_LAZY_STATIC), derive(Debug, PartialEq))]
         pub struct PhoneNumber(String);
 
         // Invalid
@@ -580,8 +555,7 @@ mod validation_with_regex {
 
     #[test]
     fn test_regex_with_once_cell_lazy() {
-        #[nutype(validate(regex = PHONE_REGEX_ONCE_CELL))]
-        #[derive(Debug, PartialEq)]
+        #[nutype(validate(regex = PHONE_REGEX_ONCE_CELL), derive(Debug, PartialEq))]
         pub struct PhoneNumber(String);
 
         // Invalid
diff --git a/test_suite/tests/ui/common/schemars08.rs b/test_suite/tests/ui/common/schemars08.rs
index 9f56c1e..74ce1c2 100644
--- a/test_suite/tests/ui/common/schemars08.rs
+++ b/test_suite/tests/ui/common/schemars08.rs
@@ -1,7 +1,6 @@
 use nutype::nutype;
 
-#[nutype]
-#[derive(JsonSchema)]
+#[nutype(derive(JsonSchema))]
 pub struct Username(String);
 
 fn main() {}
diff --git a/test_suite/tests/ui/common/schemars08.stderr b/test_suite/tests/ui/common/schemars08.stderr
index ad2135b..5cd3ee1 100644
--- a/test_suite/tests/ui/common/schemars08.stderr
+++ b/test_suite/tests/ui/common/schemars08.stderr
@@ -1,5 +1,5 @@
 error: To derive JsonSchema, the feature `schemars08` of the crate `nutype` needs to be enabled.
- --> tests/ui/common/schemars08.rs:4:10
+ --> tests/ui/common/schemars08.rs:3:17
   |
-4 | #[derive(JsonSchema)]
-  |          ^^^^^^^^^^
+3 | #[nutype(derive(JsonSchema))]
+  |                 ^^^^^^^^^^
diff --git a/test_suite/tests/ui/float/derive/eq_without_finite.rs b/test_suite/tests/ui/float/derive/eq_without_finite.rs
index dd81cf6..fb9bbbe 100644
--- a/test_suite/tests/ui/float/derive/eq_without_finite.rs
+++ b/test_suite/tests/ui/float/derive/eq_without_finite.rs
@@ -1,7 +1,6 @@
 use nutype::nutype;
 
-#[nutype]
-#[derive(PartialEq, Eq)]
+#[nutype(derive(PartialEq, Eq))]
 pub struct Size(f32);
 
 fn main() {}
diff --git a/test_suite/tests/ui/float/derive/eq_without_finite.stderr b/test_suite/tests/ui/float/derive/eq_without_finite.stderr
index 926f0c8..63ca6a7 100644
--- a/test_suite/tests/ui/float/derive/eq_without_finite.stderr
+++ b/test_suite/tests/ui/float/derive/eq_without_finite.stderr
@@ -1,7 +1,7 @@
 error: To derive Eq trait on float-based type there must be validation that proves that inner value is not NaN.
        Consider adding:
            validate(finite)
- --> tests/ui/float/derive/eq_without_finite.rs:4:21
+ --> tests/ui/float/derive/eq_without_finite.rs:3:28
   |
-4 | #[derive(PartialEq, Eq)]
-  |                     ^^
+3 | #[nutype(derive(PartialEq, Eq))]
+  |                            ^^
diff --git a/test_suite/tests/ui/float/derive/eq_without_partial_eq.rs b/test_suite/tests/ui/float/derive/eq_without_partial_eq.rs
index c5e9361..dc4da32 100644
--- a/test_suite/tests/ui/float/derive/eq_without_partial_eq.rs
+++ b/test_suite/tests/ui/float/derive/eq_without_partial_eq.rs
@@ -1,7 +1,6 @@
 use nutype::nutype;
 
-#[nutype(validate(finite))]
-#[derive(Eq)]
+#[nutype(validate(finite), derive(Eq))]
 pub struct Size(f32);
 
 fn main() {}
diff --git a/test_suite/tests/ui/float/derive/eq_without_partial_eq.stderr b/test_suite/tests/ui/float/derive/eq_without_partial_eq.stderr
index 511e6e5..08c8a7f 100644
--- a/test_suite/tests/ui/float/derive/eq_without_partial_eq.stderr
+++ b/test_suite/tests/ui/float/derive/eq_without_partial_eq.stderr
@@ -1,6 +1,6 @@
 error: Trait Eq requires PartialEq.
        Every expert was once a beginner.
- --> tests/ui/float/derive/eq_without_partial_eq.rs:4:10
+ --> tests/ui/float/derive/eq_without_partial_eq.rs:3:35
   |
-4 | #[derive(Eq)]
-  |          ^^
+3 | #[nutype(validate(finite), derive(Eq))]
+  |                                   ^^
diff --git a/test_suite/tests/ui/float/derive/ord_without_eq.rs b/test_suite/tests/ui/float/derive/ord_without_eq.rs
index d0f56d8..0755cf4 100644
--- a/test_suite/tests/ui/float/derive/ord_without_eq.rs
+++ b/test_suite/tests/ui/float/derive/ord_without_eq.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(validate(finite))]
-#[derive(PartialEq, PartialOrd, Ord)]
+#[nutype(
+    validate(finite),
+    derive(PartialEq, PartialOrd, Ord)
+)]
 pub struct Size(f64);
 
 fn main() {}
diff --git a/test_suite/tests/ui/float/derive/ord_without_eq.stderr b/test_suite/tests/ui/float/derive/ord_without_eq.stderr
index c484245..9a1752d 100644
--- a/test_suite/tests/ui/float/derive/ord_without_eq.stderr
+++ b/test_suite/tests/ui/float/derive/ord_without_eq.stderr
@@ -1,6 +1,6 @@
 error: Trait Ord requires Eq.
        Festina lente.
- --> tests/ui/float/derive/ord_without_eq.rs:4:33
+ --> tests/ui/float/derive/ord_without_eq.rs:5:35
   |
-4 | #[derive(PartialEq, PartialOrd, Ord)]
-  |                                 ^^^
+5 |     derive(PartialEq, PartialOrd, Ord)
+  |                                   ^^^
diff --git a/test_suite/tests/ui/float/derive/ord_without_finite.rs b/test_suite/tests/ui/float/derive/ord_without_finite.rs
index 29e88bc..15b23fe 100644
--- a/test_suite/tests/ui/float/derive/ord_without_finite.rs
+++ b/test_suite/tests/ui/float/derive/ord_without_finite.rs
@@ -1,7 +1,6 @@
 use nutype::nutype;
 
-#[nutype]
-#[derive(Ord)]
+#[nutype(derive(Ord))]
 pub struct Size(f32);
 
 fn main() {}
diff --git a/test_suite/tests/ui/float/derive/ord_without_finite.stderr b/test_suite/tests/ui/float/derive/ord_without_finite.stderr
index 5fe190d..ab2ffed 100644
--- a/test_suite/tests/ui/float/derive/ord_without_finite.stderr
+++ b/test_suite/tests/ui/float/derive/ord_without_finite.stderr
@@ -1,7 +1,7 @@
 error: To derive Ord trait on float-based type there must be validation that proves that inner value is not NaN.
        Consider adding:
            validate(finite)
- --> tests/ui/float/derive/ord_without_finite.rs:4:10
+ --> tests/ui/float/derive/ord_without_finite.rs:3:17
   |
-4 | #[derive(Ord)]
-  |          ^^^
+3 | #[nutype(derive(Ord))]
+  |                 ^^^
diff --git a/test_suite/tests/ui/float/derive/ord_without_partial_ord.rs b/test_suite/tests/ui/float/derive/ord_without_partial_ord.rs
index 51cbf55..3423397 100644
--- a/test_suite/tests/ui/float/derive/ord_without_partial_ord.rs
+++ b/test_suite/tests/ui/float/derive/ord_without_partial_ord.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(validate(finite))]
-#[derive(PartialEq, Eq, Ord)]
+#[nutype(
+    validate(finite),
+    derive(PartialEq, Eq, Ord)
+)]
 pub struct Size(f64);
 
 fn main() {}
diff --git a/test_suite/tests/ui/float/derive/ord_without_partial_ord.stderr b/test_suite/tests/ui/float/derive/ord_without_partial_ord.stderr
index 6493a03..3310fdc 100644
--- a/test_suite/tests/ui/float/derive/ord_without_partial_ord.stderr
+++ b/test_suite/tests/ui/float/derive/ord_without_partial_ord.stderr
@@ -1,6 +1,6 @@
 error: Trait Ord requires PartialOrd.
        Übung macht den Meister.
- --> tests/ui/float/derive/ord_without_partial_ord.rs:4:25
+ --> tests/ui/float/derive/ord_without_partial_ord.rs:5:27
   |
-4 | #[derive(PartialEq, Eq, Ord)]
-  |                         ^^^
+5 |     derive(PartialEq, Eq, Ord)
+  |                           ^^^
diff --git a/test_suite/tests/ui/integer/derive/default.rs b/test_suite/tests/ui/integer/derive/default.rs
index 5d69d93..cba7b0f 100644
--- a/test_suite/tests/ui/integer/derive/default.rs
+++ b/test_suite/tests/ui/integer/derive/default.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(validate(max = 1024))]
-#[derive(Default)]
+#[nutype(
+    validate(max = 1024),
+    derive(Default)
+)]
 pub struct Count(i32);
 
 fn main() {}
diff --git a/test_suite/tests/ui/integer/derive/default.stderr b/test_suite/tests/ui/integer/derive/default.stderr
index 9465bb6..bf2bdc4 100644
--- a/test_suite/tests/ui/integer/derive/default.stderr
+++ b/test_suite/tests/ui/integer/derive/default.stderr
@@ -1,7 +1,10 @@
 error: custom attribute panicked
  --> tests/ui/integer/derive/default.rs:3:1
   |
-3 | #[nutype(validate(max = 1024))]
-  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+3 | / #[nutype(
+4 | |     validate(max = 1024),
+5 | |     derive(Default)
+6 | | )]
+  | |__^
   |
   = help: message: Default trait is derived for type Count, but `default = ` parameter is missing in #[nutype] macro
diff --git a/test_suite/tests/ui/string/derive/from.rs b/test_suite/tests/ui/string/derive/from.rs
index 22fd423..acd7747 100644
--- a/test_suite/tests/ui/string/derive/from.rs
+++ b/test_suite/tests/ui/string/derive/from.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(validate(not_empty))]
-#[derive(From)]
+#[nutype(
+    validate(not_empty),
+    derive(From),
+)]
 struct Name(String);
 
 fn main() {}
diff --git a/test_suite/tests/ui/string/derive/from.stderr b/test_suite/tests/ui/string/derive/from.stderr
index 27c03da..1c27793 100644
--- a/test_suite/tests/ui/string/derive/from.stderr
+++ b/test_suite/tests/ui/string/derive/from.stderr
@@ -1,5 +1,5 @@
 error: #[nutype] cannot derive `From` trait, because there is validation defined. Use `TryFrom` instead.
- --> tests/ui/string/derive/from.rs:4:10
+ --> tests/ui/string/derive/from.rs:5:12
   |
-4 | #[derive(From)]
-  |          ^^^^
+5 |     derive(From),
+  |            ^^^^
diff --git a/test_suite/tests/ui/string/derive/invalid_char.rs b/test_suite/tests/ui/string/derive/invalid_char.rs
index 0d70245..3016735 100644
--- a/test_suite/tests/ui/string/derive/invalid_char.rs
+++ b/test_suite/tests/ui/string/derive/invalid_char.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(sanitize(trim))]
-#[derive(Debug, !)]
+#[nutype(
+    sanitize(trim),
+    derive(Debug, !),
+)]
 struct Name(String);
 
 fn main() {}
diff --git a/test_suite/tests/ui/string/derive/invalid_char.stderr b/test_suite/tests/ui/string/derive/invalid_char.stderr
index 4530f0c..ce7f3cc 100644
--- a/test_suite/tests/ui/string/derive/invalid_char.stderr
+++ b/test_suite/tests/ui/string/derive/invalid_char.stderr
@@ -1,5 +1,5 @@
-error: Unexpected `!`
- --> tests/ui/string/derive/invalid_char.rs:4:17
+error: expected identifier
+ --> tests/ui/string/derive/invalid_char.rs:5:19
   |
-4 | #[derive(Debug, !)]
-  |                 ^
+5 |     derive(Debug, !),
+  |                   ^
diff --git a/test_suite/tests/ui/string/derive/unknown_trait.rs b/test_suite/tests/ui/string/derive/unknown_trait.rs
index 74b52dc..e75716a 100644
--- a/test_suite/tests/ui/string/derive/unknown_trait.rs
+++ b/test_suite/tests/ui/string/derive/unknown_trait.rs
@@ -1,7 +1,9 @@
 use nutype::nutype;
 
-#[nutype(sanitize(trim))]
-#[derive(Debug, Clone, Bingo, PartialEq)]
+#[nutype(
+    sanitize(trim),
+    derive(Debug, Clone, Bingo, PartialEq),
+)]
 struct Name(String);
 
 fn main() {}
diff --git a/test_suite/tests/ui/string/derive/unknown_trait.stderr b/test_suite/tests/ui/string/derive/unknown_trait.stderr
index dacbffd..068e6e1 100644
--- a/test_suite/tests/ui/string/derive/unknown_trait.stderr
+++ b/test_suite/tests/ui/string/derive/unknown_trait.stderr
@@ -1,5 +1,5 @@
-error: unsupported trait derive: Bingo
- --> tests/ui/string/derive/unknown_trait.rs:4:24
+error: #[nutype] does not know how to derive `Bingo` trait.
+ --> tests/ui/string/derive/unknown_trait.rs:5:26
   |
-4 | #[derive(Debug, Clone, Bingo, PartialEq)]
-  |                        ^^^^^
+5 |     derive(Debug, Clone, Bingo, PartialEq),
+  |                          ^^^^^