confine_macro/
lib.rs

1mod config;
2
3use darling::ast::NestedMeta;
4use darling::{Error, FromMeta};
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::ItemStruct;
8
9#[derive(thiserror::Error, Debug)]
10enum ConfineError {
11    #[error("Error parsing attribute arguments: {0}")]
12    ParseError(#[from] Error),
13
14    #[error("Error parsing macro input: {0}")]
15    ParseMacroInput(#[from] syn::Error),
16
17    #[error("The variable to configure the environment ('{0}') was not set.")]
18    EnvVarNotSet(String),
19
20    #[error("Unsupported type: {0}. Currently only supports String, i64, f64, and bool.")]
21    UnsupportedType(String),
22
23    #[error("Config file error: {0}")]
24    ConfigBuilderError(#[from] confine_builder::ConfineBuilderError),
25}
26
27impl From<ConfineError> for TokenStream {
28    fn from(value: ConfineError) -> Self {
29        let error = Error::custom(value);
30        TokenStream::from(error.write_errors())
31    }
32}
33
34#[derive(Debug, FromMeta)]
35struct ConfineArgs {
36    pub default_env: Option<String>,
37    env_var: Option<String>,
38    path: Option<String>,
39    prefix: Option<String>,
40}
41
42impl ConfineArgs {
43    fn env_var(&self) -> String {
44        self.env_var.clone().unwrap_or("CONFINE_ENV".to_string())
45    }
46
47    fn path(&self) -> String {
48        self.path.clone().unwrap_or("config".into())
49    }
50
51    fn prefix(&self) -> String {
52        self.prefix.clone().unwrap_or("application".to_string())
53    }
54}
55
56#[proc_macro_attribute]
57pub fn confine(args: TokenStream, input: TokenStream) -> TokenStream {
58    process_confine(args, input).unwrap_or_else(|e| e.into())
59}
60
61fn process_confine(args: TokenStream, input: TokenStream) -> Result<TokenStream, ConfineError> {
62    let attr_args = NestedMeta::parse_meta_list(args.into())?;
63    let args = ConfineArgs::from_list(&attr_args)?;
64    let input = syn::parse::<ItemStruct>(input)?;
65    let struct_name = &input.ident;
66
67    let path = args.path();
68    let env_var = args.env_var();
69    let prefix = args.prefix();
70
71    Ok(quote!(
72        #input
73
74        impl #struct_name {
75            pub fn try_load() -> Result<Self, confine::ConfineBuilderError> {
76                let config = confine::ConfineConfigBuilder::default()
77                    .config_path(#path.into())
78                    .env_var(#env_var.into())
79                    .prefix(#prefix.into())
80                    .try_load::<#struct_name>()?;
81                Ok(config)
82            }
83        }
84    )
85    .into())
86}