config_gen_macro_impl/
lib.rs1mod env_attrs;
2mod utils;
3
4use proc_macro2::{Ident, TokenStream};
5use quote::{format_ident, quote};
6use syn::{parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, Field};
7
8use crate::{
9 env_attrs::derive_env_loader,
10 utils::{get_fields, inner_type, type_is_vec},
11};
12
13#[proc_macro_derive(ConfigGenerator, attributes(env_key))]
14pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 let ast = parse_macro_input!(input as DeriveInput);
16
17 let old_struct_impls = derive_old_struct_impls(&ast);
18 let new_struct_ident = derive_optional_struct_name(&ast);
19 let fields = derive_optional_fields(&ast);
20 let new_struct_impls = derive_new_struct_impls(&ast);
21
22 let macro_output = quote! {
23 #old_struct_impls
24
25 #[derive(::serde::Deserialize)]
26 struct #new_struct_ident {
27 #fields
28 }
29
30 #new_struct_impls
31 };
32
33 macro_output.into()
34}
35
36fn derive_optional_struct_name(ast: &DeriveInput) -> Ident {
37 let original_struct_name = ast.ident.to_string();
38 format_ident!("Optional{}", original_struct_name)
39}
40
41fn derive_optional_fields(ast: &DeriveInput) -> TokenStream {
42 let fields = get_fields(ast);
43
44 let optional_fields = fields.iter().map(|field| {
45 let name = &field.ident;
46 let ty = &field.ty;
47
48 if inner_type("Option", ty).is_some() {
49 quote! { #name: ::std::option::#ty }
50 } else if type_is_vec(ty) {
51 quote! { #name: ::std::vec::#ty }
52 } else {
53 quote! { #name: ::std::option::Option<#ty> }
54 }
55 });
56
57 quote! {
59 #(#optional_fields),*
60 }
61}
62
63fn derive_old_struct_impls(ast: &DeriveInput) -> TokenStream {
64 let old_struct_ident = &ast.ident;
65 let new_struct_ident = derive_optional_struct_name(ast);
66
67 let env_loaders: Vec<TokenStream> = get_fields(ast)
73 .iter()
74 .map(|field| derive_env_loader(field))
75 .collect();
76 let with_env_fn = quote! {
78 #[doc="Load values from enviroment variables configured with the `env_key` attribute and overwrite current values with them."]
79 pub fn with_env(mut self) -> Self {
80 let mut env_configs = #new_struct_ident::new();
81
82 #(#env_loaders)*
83
84 env_configs.apply_to(self)
86 }
87 };
88
89 #[cfg(not(feature = "load_toml"))]
92 let with_toml_fn = quote! {};
93 #[cfg(feature = "load_toml")]
94 let with_toml_fn = quote! {
95 #[doc="Load the toml file at `path` and overwrite current values with them."]
96 pub fn with_toml<P: ::core::convert::AsRef<::std::path::Path>>(mut self, path: &P) -> Self {
97 let file_contents = ::std::fs::read_to_string(path).unwrap();
98 let optional_config = ::toml::from_str::<#new_struct_ident>(file_contents.as_str()).unwrap();
99
100 optional_config.apply_to(self)
102 }
103 };
104
105 quote! {
106 impl #old_struct_ident {
107 #with_env_fn
108 #with_toml_fn
109 }
110 }
111}
112
113fn derive_new_struct_impls(ast: &DeriveInput) -> TokenStream {
114 let old_struct_ident = &ast.ident;
115 let new_struct_ident = derive_optional_struct_name(ast);
116 let fields = get_fields(ast);
117
118 let new_settings = fields.iter().map(|field| {
119 let name = &field.ident;
120 let ty = &field.ty;
121
122 if type_is_vec(ty) {
123 quote! { #name: ::std::vec::Vec::new() }
124 } else {
125 quote! { #name: ::std::option::Option::None }
126 }
127 });
128
129 let apply_to_fn = gen_apply_to_fn(old_struct_ident, &fields);
130
131 quote! {
132 impl #new_struct_ident {
133 pub fn new() -> Self {
134 Self {
135 #(#new_settings),*
136 }
137 }
138
139 #apply_to_fn
140 }
141 }
142}
143
144fn gen_apply_to_fn(old_struct_ident: &Ident, fields: &Punctuated<Field, Comma>) -> TokenStream {
145 let field_applyors: Vec<TokenStream> = fields
147 .iter()
148 .map(|field| {
149 let name = &field.ident;
150 let ty = &field.ty;
151 if inner_type("Option", ty).is_some() {
152 quote! {
153 if self.#name.is_some() {
154 old.#name = self.#name.clone();
155 }
156 }
157 } else if type_is_vec(ty) {
158 quote! {
159 for item in &self.#name {
160 if !old.#name.contains(item) {
161 old.#name.push(item.clone());
162 }
163 }
164 }
165 } else {
166 quote! {
167 if let ::std::option::Option::Some(val) = &self.#name {
168 old.#name = val.clone();
169 }
170 }
171 }
172 })
173 .collect();
174
175 quote! {
176 pub fn apply_to(&self, mut old: #old_struct_ident) -> #old_struct_ident {
177 #(#field_applyors)*
178
179 old
180 }
181 }
182}