Add #[config(partial_attr(...))] to add attributes for partial type

CC #17
This commit is contained in:
Lukas Kalbertodt
2023-12-08 18:15:50 +01:00
parent 830ca775cb
commit fa79a463b8
6 changed files with 60 additions and 0 deletions

View File

@@ -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

View File

@@ -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")]

View File

@@ -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, )*
}

View File

@@ -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<String>,
pub(crate) visibility: syn::Visibility,
pub(crate) partial_attrs: Vec<TokenStream>,
pub(crate) name: syn::Ident,
pub(crate) fields: Vec<Field>,
}

View File

@@ -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::<Result<Vec<_>, _>>()?;
@@ -25,12 +27,54 @@ impl Input {
Ok(Self {
doc,
visibility: input.vis,
partial_attrs,
name: input.ident,
fields,
})
}
}
fn extract_struct_attrs(attrs: Vec<syn::Attribute>) -> Result<Vec<TokenStream>, Error> {
#[derive(Debug)]
enum StructAttr {
InternalAttr(TokenStream),
}
impl Parse for StructAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
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::<StructAttr>(attr.tokens)? {
StructAttr::InternalAttr(tokens) => partial_attrs.push(tokens),
}
}
Ok(partial_attrs)
}
impl Field {
fn from_ast(mut field: syn::Field) -> Result<Self, Error> {
let doc = extract_doc(&mut field.attrs);

View File

@@ -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