1use proc_macro::TokenStream;
2use std::fmt::{Display, Formatter};
3use darling::{FromDeriveInput, FromMeta};
4use proc_macro2::TokenStream as TokenStream2;
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput};
7
8#[proc_macro_derive(Configuration, attributes(properties))]
9pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
10 let input: DeriveInput = parse_macro_input!(input);
11
12 let configuration = ConfigurationData::from_derive_input(&input).unwrap();
14 expand(configuration).into()
15}
16
17fn expand(configuration: ConfigurationData) -> TokenStream2 {
18 let ident = configuration.ident;
19 let prefix = configuration.prefix;
21 let files = configuration.file;
22 let environment = configuration.environment;
23 let https = configuration.http;
24
25 let file_sources = file_source(files);
27 let environment_source = environment_source(environment);
28 let http_sources = http_source(https);
29 let get_result = process_prefix(prefix, &ident);
30 quote! {
31 impl #ident {
33 pub fn config() -> &'static Self {
34 static CONFIG: ::std::sync::LazyLock<#ident> = ::std::sync::LazyLock::new(|| {
35 ::config_plus::Config::builder()
36 #(#file_sources)*
37 #environment_source
38 #(#http_sources)*
39 .build()
40 .unwrap()
41 #get_result
42 });
43 &CONFIG
44 }
45 }
46 }
47}
48
49fn file_source(files: Vec<String>) -> Vec<TokenStream2> {
51 let mut ts = vec![];
52
53 for file in files {
54 ts.push(quote! {
55 .add_source(
56 ::config_plus::File::with_name(#file)
57 )
58 })
59 }
60 ts
61}
62
63fn environment_source(environment: bool) -> TokenStream2 {
65 let mut ts = quote! {};
66
67 if environment {
68 ts.extend(quote! {
69 .add_source(
70 ::config_plus::Environment::with_prefix("")
71 .separator(".")
72 .prefix_separator("")
73 .list_separator(",")
74 )
75 })
76 }
77 ts
78}
79
80fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
82 let mut ts = vec![];
83
84 for http in https {
85 let url = http.url;
86 let format = http.format.to_string();
87 let method = http.method.to_string();
88 ts.push(quote! {
89 .add_source(
90 ::config_plus::Http::with(#url, #format, #method)
91 )
92 })
93 }
94 ts
95}
96
97fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
99 let mut ts = quote! {};
100
101 if prefix.is_empty() {
102 return quote! {
103 .try_deserialize()
104 .unwrap()
105 };
106 }
107 let split = prefix.split(".");
108
109 let count = split.clone().count();
111 for (index, str) in split.enumerate() {
112 if count == 1 {
113 ts.extend(quote! {
114 .get::<#ident>(#str)
115 .unwrap()
116 });
117 return ts;
118 }
119
120 if index == 0 {
121 ts.extend(quote! {
122 .get_table(#str)
123 .unwrap()
124 })
125 } else if count == index + 1 {
126 ts.extend(quote! {
127 .get(#str)
128 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
129 .unwrap()
130 .clone()
131 .try_deserialize()
132 .unwrap()
133 })
134 } else {
135 ts.extend(quote! {
136 .get(#str)
137 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
138 .unwrap()
139 .into_table()
140 .unwrap()
141 })
142 }
143 }
144 ts
145}
146
147#[derive(Debug, FromDeriveInput)]
148#[darling(attributes(properties), supports(struct_named))]
149struct ConfigurationData {
150 ident: syn::Ident,
152 #[darling(default)]
157 prefix: String,
158
159 #[darling(multiple, default)]
161 file: Vec<String>,
162
163 #[darling(multiple, default)]
165 http: Vec<HttpData>,
166
167 #[darling(default = env_default)]
169 environment: bool,
170}
171
172#[derive(Debug, FromMeta)]
173struct HttpData{
174 url: String,
175 #[darling(default)]
176 method: Method,
177 format: Format,
178}
179
180#[derive(Debug, FromMeta)]
181enum Method {
182 Get,
183 Post,
184}
185
186impl Default for Method {
187 fn default() -> Self {
188 Method::Get
189 }
190}
191
192impl Display for Method{
193 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194 match self {
195 Method::Get => write!(f, "get"),
196 Method::Post => write!(f, "post"),
197 }
198 }
199}
200
201#[derive(Debug, FromMeta)]
202enum Format {
203 Toml,
205
206 Json,
208
209 Yaml,
211
212 Yml,
214
215 Ini,
217
218 Ron,
220
221 Json5,
223}
224
225
226impl Display for Format{
227 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
228 match self {
229 Format::Toml => write!(f, "toml"),
230 Format::Json => write!(f, "json"),
231 Format::Yaml => write!(f, "yaml"),
232 Format::Yml => write!(f, "yaml"),
233 Format::Ini => write!(f, "ini"),
234 Format::Ron => write!(f, "ron"),
235 Format::Json5 => write!(f, "json5"),
236 }
237 }
238}
239
240fn env_default() -> bool {
241 true
242}