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<FileData>) -> Vec<TokenStream2> {
51 let mut ts = vec![];
52
53 for file in files {
54 let path = file.path;
55 let require = file.require;
56 ts.push(quote! {
57 .add_source(
58 ::config_plus::File::with_name(#path).required(#require),
59 )
60 })
61 }
62 ts
63}
64
65fn environment_source(environment: bool) -> TokenStream2 {
67 let mut ts = quote! {};
68
69 if environment {
70 ts.extend(quote! {
71 .add_source(
72 ::config_plus::Environment::with_prefix("")
73 .separator(".")
74 .prefix_separator("")
75 .list_separator(",")
76 )
77 })
78 }
79 ts
80}
81
82fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
84 let mut ts = vec![];
85
86 for http in https {
87 let url = http.url;
88 let format = http.format.to_string();
89 let method = http.method.to_string();
90 ts.push(quote! {
91 .add_source(
92 ::config_plus::Http::with(#url, #format, #method)
93 )
94 })
95 }
96 ts
97}
98
99fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
101 let mut ts = quote! {};
102
103 if prefix.is_empty() {
104 return quote! {
105 .try_deserialize()
106 .unwrap()
107 };
108 }
109 let split = prefix.split(".");
110
111 let count = split.clone().count();
113 for (index, str) in split.enumerate() {
114 if count == 1 {
115 ts.extend(quote! {
116 .get::<#ident>(#str)
117 .unwrap()
118 });
119 return ts;
120 }
121
122 if index == 0 {
123 ts.extend(quote! {
124 .get_table(#str)
125 .unwrap()
126 })
127 } else if count == index + 1 {
128 ts.extend(quote! {
129 .get(#str)
130 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
131 .unwrap()
132 .clone()
133 .try_deserialize()
134 .unwrap()
135 })
136 } else {
137 ts.extend(quote! {
138 .get(#str)
139 .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
140 .unwrap()
141 .clone()
142 .into_table()
143 .unwrap()
144 })
145 }
146 }
147 ts
148}
149
150#[derive(Debug, FromDeriveInput)]
151#[darling(attributes(properties), supports(struct_named))]
152struct ConfigurationData {
153 ident: syn::Ident,
155 #[darling(default)]
160 prefix: String,
161
162 #[darling(multiple, default)]
164 file: Vec<FileData>,
165
166 #[darling(multiple, default)]
168 http: Vec<HttpData>,
169
170 #[darling(default = env_default)]
172 environment: bool,
173}
174
175#[derive(Debug, FromMeta)]
176struct FileData{
177 path: String,
178 #[darling(default)]
179 require: bool,
180}
181
182#[derive(Debug, FromMeta)]
183struct HttpData{
184 url: String,
185 #[darling(default)]
186 method: Method,
187 format: Format,
188}
189
190#[derive(Debug, FromMeta)]
191enum Method {
192 Get,
193 Post,
194}
195
196impl Default for Method {
197 fn default() -> Self {
198 Method::Get
199 }
200}
201
202impl Display for Method{
203 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204 match self {
205 Method::Get => write!(f, "get"),
206 Method::Post => write!(f, "post"),
207 }
208 }
209}
210
211#[derive(Debug, FromMeta)]
212enum Format {
213 Toml,
215
216 Json,
218
219 Yaml,
221
222 Yml,
224
225 Ini,
227
228 Ron,
230
231 Json5,
233}
234
235
236impl Display for Format{
237 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238 match self {
239 Format::Toml => write!(f, "toml"),
240 Format::Json => write!(f, "json"),
241 Format::Yaml => write!(f, "yaml"),
242 Format::Yml => write!(f, "yaml"),
243 Format::Ini => write!(f, "ini"),
244 Format::Ron => write!(f, "ron"),
245 Format::Json5 => write!(f, "json5"),
246 }
247 }
248}
249
250fn env_default() -> bool {
251 true
252}