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 .into_table()
142 .unwrap()
143 })
144 }
145 }
146 ts
147}
148
149#[derive(Debug, FromDeriveInput)]
150#[darling(attributes(properties), supports(struct_named))]
151struct ConfigurationData {
152 ident: syn::Ident,
154 #[darling(default)]
159 prefix: String,
160
161 #[darling(multiple, default)]
163 file: Vec<FileData>,
164
165 #[darling(multiple, default)]
167 http: Vec<HttpData>,
168
169 #[darling(default = env_default)]
171 environment: bool,
172}
173
174#[derive(Debug, FromMeta)]
175struct FileData{
176 path: String,
177 #[darling(default)]
178 require: bool,
179}
180
181#[derive(Debug, FromMeta)]
182struct HttpData{
183 url: String,
184 #[darling(default)]
185 method: Method,
186 format: Format,
187}
188
189#[derive(Debug, FromMeta)]
190enum Method {
191 Get,
192 Post,
193}
194
195impl Default for Method {
196 fn default() -> Self {
197 Method::Get
198 }
199}
200
201impl Display for Method{
202 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
203 match self {
204 Method::Get => write!(f, "get"),
205 Method::Post => write!(f, "post"),
206 }
207 }
208}
209
210#[derive(Debug, FromMeta)]
211enum Format {
212 Toml,
214
215 Json,
217
218 Yaml,
220
221 Yml,
223
224 Ini,
226
227 Ron,
229
230 Json5,
232}
233
234
235impl Display for Format{
236 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
237 match self {
238 Format::Toml => write!(f, "toml"),
239 Format::Json => write!(f, "json"),
240 Format::Yaml => write!(f, "yaml"),
241 Format::Yml => write!(f, "yaml"),
242 Format::Ini => write!(f, "ini"),
243 Format::Ron => write!(f, "ron"),
244 Format::Json5 => write!(f, "json5"),
245 }
246 }
247}
248
249fn env_default() -> bool {
250 true
251}