1use proc_macro::TokenStream;
2use std::env;
3use std::fmt::{Display, Formatter};
4use darling::{FromDeriveInput, FromMeta};
5use proc_macro2::TokenStream as TokenStream2;
6use quote::quote;
7use syn::{parse_macro_input, DeriveInput};
8
9#[proc_macro_derive(Configuration, attributes(config))]
10pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
11 let input: DeriveInput = parse_macro_input!(input);
12
13 let configuration = ConfigurationData::from_derive_input(&input).unwrap();
15 expand(configuration).into()
16}
17
18fn expand(configuration: ConfigurationData) -> TokenStream2 {
19 let ident = configuration.ident;
20 let prefix = configuration.prefix;
22 let files = configuration.file;
23 let envs = configuration.env;
24 let environment = configuration.environment;
25 let https = configuration.http;
26 let file_sources = file_source(envs, files);
28 let environment_source = environment_source(environment);
29 let http_sources = http_source(https);
30 let get_result = process_prefix(prefix, &ident);
31 quote! {
32 impl #ident {
34 pub fn config() -> &'static Self {
35 static CONFIG: ::std::sync::LazyLock<#ident> = ::std::sync::LazyLock::new(|| {
36 ::config_plus::Config::builder()
37 #(#file_sources)*
38 #environment_source
39 #(#http_sources)*
40 .build()
41 .unwrap()
42 #get_result
43 });
44 &CONFIG
45 }
46 }
47 }
48}
49
50fn file_source(envs:Vec<EnvData>, files: Vec<FileData>) -> Vec<TokenStream2> {
52 let mut ts = vec![];
53
54 for file in files {
55 let path = file.path;
56 let require = file.require;
57 ts.push(quote! {
58 .add_source(
59 ::config_plus::File::with_name(#path).required(#require),
60 )
61 })
62 }
63
64 for env in envs {
65 let name = env.name;
66 let require = env.require;
67 match env::var(name) {
69 Ok(path) => {
70 let path = path.trim();
71 ts.push(quote! {
72 .add_source(
73 ::config_plus::File::with_name(#path).required(#require),
74 )
75 })
76 },
77 Err(_) => {},
78 }
79 }
80 ts
81}
82
83fn environment_source(environment: bool) -> TokenStream2 {
85 let mut ts = quote! {};
86
87 if environment {
88 ts.extend(quote! {
89 .add_source(
90 ::config_plus::Environment::with_prefix("")
91 .separator(".")
92 .prefix_separator("")
93 .list_separator(",")
94 )
95 })
96 }
97 ts
98}
99
100fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
102 let mut ts = vec![];
103
104 for http in https {
105 let url = http.url;
106 let format = http.format.to_string();
107 let method = http.method.to_string();
108 ts.push(quote! {
109 .add_source(
110 ::config_plus::Http::with(#url, #format, #method)
111 )
112 })
113 }
114 ts
115}
116
117fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
119 let mut ts = quote! {};
120
121 if prefix.is_empty() {
122 return quote! {
123 .try_deserialize()
124 .unwrap()
125 };
126 }
127 let split = prefix.split(".");
128
129 let count = split.clone().count();
131 for (index, str) in split.enumerate() {
132 if count == 1 {
133 ts.extend(quote! {
134 .get::<#ident>(#str)
135 .unwrap()
136 });
137 return ts;
138 }
139
140 if index == 0 {
141 ts.extend(quote! {
142 .get_table(#str)
143 .unwrap()
144 })
145 } else if count == index + 1 {
146 ts.extend(quote! {
147 .get(#str)
148 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
149 .unwrap()
150 .clone()
151 .try_deserialize()
152 .unwrap()
153 })
154 } else {
155 ts.extend(quote! {
156 .get(#str)
157 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
158 .unwrap()
159 .clone()
160 .into_table()
161 .unwrap()
162 })
163 }
164 }
165 ts
166}
167
168#[derive(Debug, FromDeriveInput)]
169#[darling(attributes(config), supports(struct_named))]
170struct ConfigurationData {
171 ident: syn::Ident,
173 #[darling(default)]
178 prefix: String,
179
180 #[darling(multiple, default)]
182 file: Vec<FileData>,
183
184 #[darling(multiple, default)]
186 env: Vec<EnvData>,
187
188 #[darling(multiple, default)]
190 http: Vec<HttpData>,
191
192 #[darling(default = env_default)]
194 environment: bool,
195}
196
197#[derive(Debug, FromMeta)]
199struct FileData{
200 path: String,
201 #[darling(default)]
202 require: bool,
203}
204
205#[derive(Debug, FromMeta)]
207struct EnvData{
208 name: String,
209 #[darling(default)]
210 require: bool,
211}
212
213#[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}