diff --git a/CHANGELOG.md b/CHANGELOG.md index af00beb..e3f61c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Add `#[config(partial_attr(...))]` struct attribute to specify attributes for + the partial type. ## [0.2.4] - 2023-07-02 - Fixed enum deserialization from env values diff --git a/examples/simple.rs b/examples/simple.rs index f73c6a4..6309a1a 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -15,6 +15,7 @@ struct Conf { /// Configuring the HTTP server of our app. #[derive(Debug, Config)] +#[config(partial_attr(derive(Clone)))] struct Http { /// The port the server will listen on. #[config(env = "PORT")] diff --git a/macro/src/gen/mod.rs b/macro/src/gen/mod.rs index c8bedcd..e063574 100644 --- a/macro/src/gen/mod.rs +++ b/macro/src/gen/mod.rs @@ -246,6 +246,8 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { input.name, ); + let partial_attrs = &input.partial_attrs; + quote! { #[doc = #module_doc] #visibility mod #mod_name { @@ -253,6 +255,7 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { use super::*; #[derive(confique::serde::Deserialize)] + #( #[ #partial_attrs ])* #struct_visibility struct #struct_name { #( #struct_fields, )* } diff --git a/macro/src/ir.rs b/macro/src/ir.rs index 5694450..8fb45f8 100644 --- a/macro/src/ir.rs +++ b/macro/src/ir.rs @@ -1,10 +1,13 @@ //! Definition of the intermediate representation. +use proc_macro2::TokenStream; + /// The parsed input to the `gen_config` macro. pub(crate) struct Input { pub(crate) doc: Vec, pub(crate) visibility: syn::Visibility, + pub(crate) partial_attrs: Vec, pub(crate) name: syn::Ident, pub(crate) fields: Vec, } diff --git a/macro/src/parse.rs b/macro/src/parse.rs index d115d75..529a999 100644 --- a/macro/src/parse.rs +++ b/macro/src/parse.rs @@ -1,3 +1,4 @@ +use proc_macro2::{TokenStream, Group, Delimiter, Ident}; use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated}; use crate::{ @@ -17,6 +18,7 @@ impl Input { }; let doc = extract_doc(&mut input.attrs); + let partial_attrs = extract_struct_attrs(input.attrs)?; let fields = fields.named.into_iter() .map(Field::from_ast) .collect::, _>>()?; @@ -25,12 +27,54 @@ impl Input { Ok(Self { doc, visibility: input.vis, + partial_attrs, name: input.ident, fields, }) } } +fn extract_struct_attrs(attrs: Vec) -> Result, Error> { + #[derive(Debug)] + enum StructAttr { + InternalAttr(TokenStream), + } + + impl Parse for StructAttr { + fn parse(input: ParseStream) -> syn::Result { + let content; + syn::parenthesized!(content in input); + assert_empty_or_comma(input)?; + + let name: Ident = content.parse()?; + match &*name.to_string() { + "partial_attr" => { + let g: Group = content.parse()?; + if g.delimiter() != Delimiter::Parenthesis { + return Err(Error::new_spanned(g, + "expected `(...)` but found different delimiter")); + } + assert_empty_or_comma(&content)?; + Ok(Self::InternalAttr(g.stream())) + } + _ => Err(Error::new_spanned(name, "unknown attribute")), + } + } + } + + let mut partial_attrs = Vec::new(); + for attr in attrs { + if !attr.path.is_ident("config") { + continue; + } + match syn::parse2::(attr.tokens)? { + StructAttr::InternalAttr(tokens) => partial_attrs.push(tokens), + } + } + + Ok(partial_attrs) +} + impl Field { fn from_ast(mut field: syn::Field) -> Result { let doc = extract_doc(&mut field.attrs); diff --git a/src/lib.rs b/src/lib.rs index b36abcd..f642358 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -326,6 +326,13 @@ pub use crate::{ /// the field.. Can only be present if the `env` attribute is present. Also /// see [`env::parse`]. /// +/// There are also the following attributes on the struct itself: +/// +/// - **`#[config(partial_attr(...))]`: specify attributes that should be +/// attached to the partial struct definition. For example, +/// `#[config(partial_attr(derive(Clone)))]` can be used to make the partial +/// type implement `Clone`. +/// /// [serde-deser]: https://serde.rs/field-attrs.html#deserialize_with /// /// ## Special types for leaf fields