#![forbid(unsafe_code)]
use darling::FromDeriveInput;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Ident, Type};
mod codegen;
mod parse;
use codegen::{
generate_clap_impl, generate_defaults_impl, generate_load_impl, generate_migration_impl,
generate_modules_impl, generate_schema_impl, generate_validate_impl,
};
use darling::FromField;
use parse::{FieldAttrs, StructAttrs};
#[proc_macro_derive(Config, attributes(config))]
pub fn config_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match impl_config_derive(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_derive(ConfigSchema, attributes(config))]
pub fn config_schema_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match impl_config_schema_derive(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_derive(ConfigMigration, attributes(config))]
pub fn config_migration_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match impl_config_migration_derive(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_derive(ConfigModules, attributes(config))]
pub fn config_modules_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match impl_config_modules_derive(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_derive(ConfigClap, attributes(config))]
pub fn config_clap_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match impl_config_clap_derive(&input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
fn impl_config_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_attrs = StructAttrs::from_derive_input(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
struct_attrs
.validate(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
let struct_ident = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => {
return Err(syn::Error::new_spanned(
input,
"Config can only be derived for named structs",
))
}
};
let field_info: Vec<(&syn::Ident, &syn::Type, FieldAttrs)> = fields
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref()?;
let attrs = FieldAttrs::from_field(field).ok()?;
Some((ident, &field.ty, attrs))
})
.collect();
for (ident, _ty, attrs) in &field_info {
attrs
.validate(
fields
.iter()
.find(|f| f.ident.as_ref() == Some(ident))
.expect("field must exist in parsed fields"),
)
.map_err(|e| syn::Error::new_spanned(ident, e.to_string()))?;
}
let defaults_impl = generate_defaults_impl(struct_ident, &field_info);
let load_impl = generate_load_impl(struct_ident, &struct_attrs, fields);
let validate_impl = generate_validate_impl(&struct_attrs, &field_info);
let sensitive_paths = generate_sensitive_paths(struct_ident, &field_info);
Ok(quote! {
#defaults_impl
#load_impl
#validate_impl
#sensitive_paths
})
}
fn generate_sensitive_paths(
struct_ident: &Ident,
fields: &[(&Ident, &Type, FieldAttrs)],
) -> TokenStream2 {
use syn::LitStr;
let sensitive_paths: Vec<LitStr> = fields
.iter()
.filter(|(_, _, attrs)| attrs.sensitive || attrs.encrypt.is_some())
.map(|(ident, _, attrs)| {
let name = attrs.name.clone().unwrap_or_else(|| ident.to_string());
LitStr::new(&name, ident.span())
})
.collect();
if sensitive_paths.is_empty() {
return TokenStream2::new();
}
quote! {
impl #struct_ident {
fn sensitive_paths() -> &'static [&'static str] {
&[#(#sensitive_paths),*]
}
}
}
}
fn impl_config_schema_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_attrs = StructAttrs::from_derive_input(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
let struct_ident = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => {
return Err(syn::Error::new_spanned(
input,
"ConfigSchema can only be derived for named structs",
))
}
};
let schema_impl = generate_schema_impl(struct_ident, &struct_attrs, fields);
Ok(quote! {
#schema_impl
})
}
fn impl_config_migration_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_attrs = StructAttrs::from_derive_input(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
let struct_ident = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => {
return Err(syn::Error::new_spanned(
input,
"ConfigMigration can only be derived for named structs",
))
}
};
let migration_impl = generate_migration_impl(struct_ident, &struct_attrs, fields);
Ok(quote! {
#migration_impl
})
}
fn impl_config_modules_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_attrs = StructAttrs::from_derive_input(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
let struct_ident = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => {
return Err(syn::Error::new_spanned(
input,
"ConfigModules can only be derived for named structs",
))
}
};
let modules_impl = generate_modules_impl(struct_ident, &struct_attrs, fields);
Ok(quote! {
#modules_impl
})
}
fn impl_config_clap_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let struct_attrs = StructAttrs::from_derive_input(input)
.map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
let struct_ident = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => {
return Err(syn::Error::new_spanned(
input,
"ConfigClap can only be derived for named structs",
))
}
};
let clap_impl = generate_clap_impl(struct_ident, &struct_attrs, fields);
Ok(quote! {
#clap_impl
})
}