1use darling::{FromDeriveInput, FromMeta};
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use std::fmt::{Display, Formatter};
9use syn::{parse_macro_input, DeriveInput};
10
11#[proc_macro_derive(Configuration, attributes(properties))]
51pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
52 let input: DeriveInput = parse_macro_input!(input);
53
54 let configuration = ConfigurationData::from_derive_input(&input).unwrap();
56 expand(configuration).into()
57}
58
59fn expand(configuration: ConfigurationData) -> TokenStream2 {
60 let ident = configuration.ident;
61 let prefix = configuration.prefix;
63 let files = configuration.file;
64 let environment = configuration.environment;
65 let https = configuration.http;
66
67 let file_sources = file_source(files);
69 let environment_source = environment_source(environment);
70 let http_sources = http_source(https);
71 let get_result = process_prefix(prefix, &ident);
72 quote! {
73 impl #ident {
75 pub fn config() -> &'static Self {
76 static CONFIG: ::std::sync::LazyLock<#ident> = ::std::sync::LazyLock::new(|| {
77 ::config::Config::builder()
78 #(#file_sources)*
79 #environment_source
80 #(#http_sources)*
81 .build()
82 .unwrap()
83 #get_result
84 });
85 &CONFIG
86 }
87 }
88 }
89}
90
91fn file_source(files: Vec<String>) -> Vec<TokenStream2> {
93 let mut ts = vec![];
94
95 for file in files {
96 ts.push(quote! {
97 .add_source(
98 ::config::File::with_name(#file)
99 )
100 })
101 }
102 ts
103}
104
105fn environment_source(environment: bool) -> TokenStream2 {
107 let mut ts = quote! {};
108
109 if environment {
110 ts.extend(quote! {
111 .add_source(
112 ::config::Environment::with_prefix("")
113 .separator(".")
114 .prefix_separator("")
115 .list_separator(",")
116 )
117 })
118 }
119 ts
120}
121
122fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
124 let mut ts = vec![];
125
126 for http in https {
127 let url = http.url;
128 let format = http.format.to_string();
129 let method = http.method.to_string();
130 ts.push(quote! {
131 .add_source(
132 ::config_plus::Http::with(#url, #format, #method)
133 )
134 })
135 }
136 ts
137}
138
139fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
141 let mut ts = quote! {};
142
143 if prefix.is_empty() {
144 return quote! {
145 .try_deserialize()
146 .unwrap()
147 };
148 }
149 let split = prefix.split(".");
150
151 let count = split.clone().count();
153 for (index, str) in split.enumerate() {
154 if count == 1 {
155 ts.extend(quote! {
156 .get::<#ident>(#str)
157 .unwrap()
158 });
159 return ts;
160 }
161
162 if index == 0 {
163 ts.extend(quote! {
164 .get_table(#str)
165 .unwrap()
166 })
167 } else if count == index + 1 {
168 ts.extend(quote! {
169 .get(#str)
170 .ok_or_else(|| ::config::ConfigError::NotFound(#str.into()))
171 .unwrap()
172 .clone()
173 .try_deserialize()
174 .unwrap()
175 })
176 } else {
177 ts.extend(quote! {
178 .get(#str)
179 .ok_or_else(|| ::config::ConfigError::NotFound(#str.into()))
180 .unwrap()
181 .into_table()
182 .unwrap()
183 })
184 }
185 }
186 ts
187}
188
189#[derive(Debug, FromDeriveInput)]
190#[darling(attributes(properties), supports(struct_named))]
191struct ConfigurationData {
192 ident: syn::Ident,
194 #[darling(default)]
199 prefix: String,
200
201 #[darling(multiple, default)]
203 file: Vec<String>,
204
205 #[darling(multiple, default)]
207 http: Vec<HttpData>,
208
209 #[darling(default = env_default)]
211 environment: bool,
212}
213
214#[derive(Debug, FromMeta)]
215struct HttpData{
216 url: String,
217 #[darling(default)]
218 method: Method,
219 format: Format,
220}
221
222#[derive(Debug, FromMeta)]
223enum Method {
224 Get,
225 Post,
226}
227
228impl Default for Method {
229 fn default() -> Self {
230 Method::Get
231 }
232}
233
234impl Display for Method{
235 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236 match self {
237 Method::Get => write!(f, "get"),
238 Method::Post => write!(f, "post"),
239 }
240 }
241}
242
243#[derive(Debug, FromMeta)]
244enum Format {
245 Toml,
247
248 Json,
250
251 Yaml,
253
254 Yml,
256
257 Ini,
259
260 Ron,
262
263 Json5,
265}
266
267
268impl Display for Format{
269 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
270 match self {
271 Format::Toml => write!(f, "toml"),
272 Format::Json => write!(f, "json"),
273 Format::Yaml => write!(f, "yaml"),
274 Format::Yml => write!(f, "yaml"),
275 Format::Ini => write!(f, "ini"),
276 Format::Ron => write!(f, "ron"),
277 Format::Json5 => write!(f, "json5"),
278 }
279 }
280}
281
282fn env_default() -> bool {
283 true
284}