derive_environment_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use convert_case::{Case, Casing};
4use darling::{ast, FromDeriveInput, FromField};
5use proc_macro2::TokenStream;
6use quote::quote;
7use syn::*;
8
9#[derive(Debug, FromDeriveInput)]
10#[darling(attributes(env), supports(struct_named))]
11#[allow(dead_code)]
12struct EnvArgs {
13    ident: syn::Ident,
14    generics: syn::Generics,
15    data: ast::Data<(), EnvFieldArgs>,
16}
17
18#[derive(Debug, FromField)]
19#[darling(attributes(env))]
20#[allow(dead_code)]
21struct EnvFieldArgs {
22    ident: Option<syn::Ident>,
23    ty: syn::Type,
24
25    #[darling(default)]
26    ignore: bool,
27}
28
29/// Generates a `load_environment()` function that will populate each field from environment variables.
30///
31/// This uses `.parse()` so make sure all members implement `FromStr`.
32/// Note that `load_environment()` is not a constructor.
33#[proc_macro_derive(FromEnv, attributes(prefix, env))]
34pub fn environment(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
35    // Parse the input tokens into a syntax tree
36    let input = parse_macro_input!(input as DeriveInput);
37
38    let args = match EnvArgs::from_derive_input(&input) {
39        Ok(v) => v,
40        Err(e) => {
41            return e.write_errors().into();
42        }
43    };
44
45    let name = input.ident;
46    let fields = args.data.as_ref().take_struct().unwrap().fields;
47    let parseable_fields = env_from_parseable(&fields);
48
49    // Build the output, possibly using quasi-quotation
50    let expanded = quote! {
51        impl ::derive_environment::FromEnv for #name {
52            fn with_env(&mut self, prefix: &str) -> ::derive_environment::Result<bool> {
53                // Tracks whether or not a variable was found.
54                // Important for nested extendables.
55                let mut found_match = false;
56                #parseable_fields
57                ::derive_environment::Result::Ok(found_match)
58            }
59        }
60    };
61
62    // Hand the output tokens back to the compiler
63    expanded.into()
64}
65
66fn to_variable(field: &&EnvFieldArgs) -> String {
67    field
68        .ident
69        .clone()
70        .unwrap()
71        .to_string()
72        .to_case(Case::UpperSnake)
73}
74
75fn to_field(field: &&EnvFieldArgs) -> Ident {
76    field.ident.clone().unwrap()
77}
78
79fn env_from_parseable(fields: &[&EnvFieldArgs]) -> TokenStream {
80    let mut tokens = TokenStream::new();
81
82    for field in fields.iter().filter(|x| !x.ignore) {
83        let f = to_field(field);
84        let var = to_variable(field);
85
86        tokens.extend(quote! {
87            let name = ::std::format!("{prefix}_{}", #var);
88
89            if derive_environment::FromEnv::with_env(&mut self.#f, &name)? {
90                found_match = true;
91            }
92        });
93    }
94
95    tokens
96}