use darling::FromField;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Fields, Ident};
use crate::parse::{FieldAttrs, StructAttrs};
pub fn generate_load_impl(
struct_ident: &Ident,
attrs: &StructAttrs,
fields: &syn::Fields,
) -> TokenStream {
let env_prefix = attrs.effective_env_prefix();
let named_fields = match fields {
Fields::Named(named) => &named.named,
_ => return quote! {},
};
let field_info: Vec<(&syn::Ident, &syn::Type, FieldAttrs)> = named_fields
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref()?;
let attrs = FieldAttrs::from_field(field).ok()?;
Some((ident, &field.ty, attrs))
})
.collect();
let load_impl = generate_load_method(struct_ident, attrs, &field_info);
let load_sync_impl = generate_load_sync_method(struct_ident, attrs, &field_info);
let load_file_impl = generate_load_file_method(struct_ident, attrs, &field_info);
let env_mapping_impl = generate_env_mapping(struct_ident, env_prefix, &field_info);
quote! {
#load_impl
#load_sync_impl
#load_file_impl
#env_mapping_impl
}
}
fn generate_load_method(
struct_ident: &Ident,
attrs: &StructAttrs,
fields: &[(&syn::Ident, &syn::Type, FieldAttrs)],
) -> TokenStream {
let env_prefix = attrs.effective_env_prefix();
let default_calls: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip && f.default.is_some())
.map(|(_, _, f)| {
let config_key = f.effective_name();
let default_expr = f.default.as_ref().unwrap();
quote! {
builder = builder.default(#config_key.to_string(), {
let val: confers::ConfigValue = (#default_expr).into();
val
});
}
})
.collect();
let env_calls: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip)
.map(|(_, _, f)| {
let env_name = f.effective_env_name(env_prefix);
let config_key = f.effective_name();
if f.is_sensitive_effective() {
let file_env_name = format!("{}_FILE", env_name);
quote! {
if let Ok(file_path) = std::env::var(#file_env_name) {
let validator = confers::security::PathValidator::new();
match validator.validate_and_resolve(&file_path) {
Ok(validated_path) => {
if let Ok(content) = std::fs::read_to_string(&validated_path) {
let val = content.trim().to_string();
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
Err(_) => {
}
}
} else if let Ok(val) = std::env::var(#env_name) {
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
} else {
quote! {
if let Ok(val) = std::env::var(#env_name) {
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
}
})
.collect();
quote! {
impl #struct_ident {
pub fn load() -> impl std::future::Future<Output = confers::ConfigResult<Self>> {
async {
Self::load_sync()
}
}
pub fn load_sync() -> confers::ConfigResult<Self> {
let mut builder = confers::ConfigBuilder::<Self>::new();
#(#default_calls)*
let mut env_map = std::collections::HashMap::new();
#(#env_calls)*
if !env_map.is_empty() {
builder = builder.memory(env_map);
}
builder.build()
}
}
}
}
fn generate_load_sync_method(
struct_ident: &Ident,
attrs: &StructAttrs,
fields: &[(&syn::Ident, &syn::Type, FieldAttrs)],
) -> TokenStream {
let env_prefix = attrs.effective_env_prefix();
let default_calls: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip && f.default.is_some())
.map(|(_ident, _, f)| {
let config_key = f.effective_name();
let default_expr = f.default.as_ref().unwrap();
quote! {
builder = builder.default(#config_key.to_string(), {
let val: confers::ConfigValue = (#default_expr).into();
val
});
}
})
.collect();
let env_calls: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip)
.map(|(_ident, _ty, f)| {
let env_name = f.effective_env_name(env_prefix);
let config_key = f.effective_name();
if f.is_sensitive_effective() {
let file_env_name = format!("{}_FILE", env_name);
quote! {
if let Ok(file_path) = std::env::var(#file_env_name) {
let validator = confers::security::PathValidator::new();
match validator.validate_and_resolve(&file_path) {
Ok(validated_path) => {
if let Ok(content) = std::fs::read_to_string(&validated_path) {
let val = content.trim().to_string();
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
Err(_) => {
}
}
} else if let Ok(val) = std::env::var(#env_name) {
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
} else {
quote! {
if let Ok(val) = std::env::var(#env_name) {
env_map.insert(#config_key.to_string(), confers::ConfigValue::string(val));
}
}
}
})
.collect();
quote! {
impl #struct_ident {
pub fn build_config() -> confers::ConfigResult<Self> {
let mut builder = confers::ConfigBuilder::<Self>::new();
#(#default_calls)*
let mut env_map = std::collections::HashMap::new();
#(#env_calls)*
if !env_map.is_empty() {
builder = builder.memory(env_map);
}
builder.build()
}
}
}
}
fn generate_load_file_method(
struct_ident: &Ident,
_attrs: &StructAttrs,
_fields: &[(&syn::Ident, &syn::Type, FieldAttrs)],
) -> TokenStream {
quote! {
impl #struct_ident {
pub fn load_file(path: impl AsRef<std::path::Path>) -> confers::ConfigResult<Self> {
let builder = confers::ConfigBuilder::<Self>::new()
.file(path.as_ref());
builder.build()
}
pub fn load_file_with_env(path: impl AsRef<std::path::Path>) -> confers::ConfigResult<Self> {
let builder = confers::ConfigBuilder::<Self>::new()
.file(path.as_ref())
.env();
builder.build()
}
}
}
}
fn generate_env_mapping(
struct_ident: &Ident,
env_prefix: &str,
fields: &[(&syn::Ident, &syn::Type, FieldAttrs)],
) -> TokenStream {
let mappings: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip)
.map(|(ident, _, f)| {
let config_key = f.effective_name();
let env_name = f.effective_env_name(env_prefix);
let field_name = ident.to_string();
quote! {
(#field_name.to_string(), #config_key.to_string(), #env_name.to_string())
}
})
.collect();
quote! {
impl #struct_ident {
pub fn env_mapping() -> Vec<(String, String, String)> {
vec![
#(#mappings),*
]
}
}
}
}
#[allow(dead_code)]
pub fn generate_typed_keys(
struct_ident: &Ident,
fields: &[(&syn::Ident, &syn::Type, FieldAttrs)],
) -> TokenStream {
let key_defs: Vec<TokenStream> = fields
.iter()
.filter(|(_, _, f)| !f.skip)
.map(|(ident, ty, f)| {
let config_key = f.effective_name();
let fn_name = format_ident!("key_{}", ident);
quote! {
pub fn #fn_name() -> confers::TypedConfigKey<#ty> {
confers::TypedConfigKey::new(#config_key)
}
}
})
.collect();
quote! {
impl #struct_ident {
#(#key_defs)*
}
}
}