config_plus_macro/
lib.rs

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    // parse the input to the ValidationData struct defined above
14    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 generics = configuration.generics;
21    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 (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
27    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 #impl_generics #ident #ty_generics #where_clause {
33        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
50/// 添加文件源
51fn 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        let format = file.format;
58        match format { 
59            Some(format) => {
60                let format = format.to_string();
61                ts.push(quote! {
62                    .add_source(
63                        ::config_plus::get_file(#path, #format).required(#require),
64                    )
65                })
66            },
67            None => {
68                ts.push(quote! {
69                    .add_source(
70                        ::config_plus::File::with_name(#path).required(#require),
71                    )
72                })
73            }
74        }
75    }
76
77    for env in envs {
78        let name = env.name;
79        let require = env.require;
80        let format = env.format;
81        // 获取 HOME 环境变量
82        match env::var(name) {
83            Ok(path) => {
84                let path = path.trim();
85                match format {
86                    Some(format) => {
87                        let format = format.to_string();
88                        ts.push(quote! {
89                            .add_source(
90                                ::config_plus::get_file(#path, #format).required(#require),
91                            )
92                        })
93                    },
94                    None => {
95                        ts.push(quote! {
96                            .add_source(
97                                ::config_plus::File::with_name(#path).required(#require),
98                            )
99                        })
100                    }
101                }
102            },
103            Err(_) => {},
104        }
105    }
106    ts
107}
108
109/// 添加环境源
110fn environment_source(environment: bool) -> TokenStream2 {
111    let mut ts = quote! {};
112
113    if environment {
114        ts.extend(quote! {
115            .add_source(
116                ::config_plus::Environment::with_prefix("")
117                        .separator(".")
118                        .prefix_separator("")
119                        .list_separator(",")
120            )
121        })
122    }
123    ts
124}
125
126/// 添加http源
127fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
128    let mut ts = vec![];
129
130    for http in https {
131        let url = http.url;
132        let format = http.format.to_string();
133        let method = http.method.to_string();
134        ts.push(quote! {
135            .add_source(
136                ::config_plus::Http::with(#url, #format, #method)
137            )
138        })
139    }
140    ts
141}
142
143/// 处理前缀
144fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
145    let mut ts = quote! {};
146
147    if prefix.is_empty() {
148        return quote! {
149            .try_deserialize()
150            .unwrap()
151        };
152    }
153    let split = prefix.split(".");
154
155    // 元素个数
156    let count = split.clone().count();
157    for (index, str) in split.enumerate() {
158        if count == 1 {
159            ts.extend(quote! {
160                .get::<#ident>(#str)
161                .unwrap()
162            });
163            return ts;
164        }
165
166        if index == 0 {
167            ts.extend(quote! {
168                .get_table(#str)
169                .unwrap()
170            })
171        } else if count == index + 1 {
172            ts.extend(quote! {
173                .get(#str)
174                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
175                .unwrap()
176                .clone()
177                .try_deserialize()
178                .unwrap()
179            })
180        } else {
181            ts.extend(quote! {
182                .get(#str)
183                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
184                .unwrap()
185                .clone()
186                .into_table()
187                .unwrap()
188            })
189        }
190    }
191    ts
192}
193
194#[derive(Debug, FromDeriveInput)]
195#[darling(attributes(config), supports(struct_named))]
196struct ConfigurationData {
197    /// 结构体名
198    ident: syn::Ident,
199    // /// 泛型参数
200    // generics: syn::Generics,
201
202    /// 配置前缀
203    #[darling(default)]
204    prefix: String,
205
206    /// 配置文件路径
207    #[darling(multiple, default)]
208    file: Vec<FileData>,
209
210    /// 通过指定环境变量来指定文件路径
211    #[darling(multiple, default)]
212    env: Vec<EnvData>,
213
214    /// 配置http
215    #[darling(multiple, default)]
216    http: Vec<HttpData>,
217
218    /// 是否从环境变量中读取,默认为true
219    #[darling(default = env_default)]
220    environment: bool,
221}
222
223/// 文件
224#[derive(Debug, FromMeta)]
225struct FileData{
226    path: String,
227    #[darling(default)]
228    require: bool,
229    format: Option<Format>,
230}
231
232/// 环境变量
233#[derive(Debug, FromMeta)]
234struct EnvData{
235    name: String,
236    #[darling(default)]
237    require: bool,
238    format: Option<Format>,
239}
240
241/// http
242#[derive(Debug, FromMeta)]
243struct HttpData{
244    url: String,
245    #[darling(default)]
246    method: Method,
247    format: Format,
248}
249
250#[derive(Debug, FromMeta)]
251enum Method {
252    Get,
253    Post,
254}
255
256impl Default for Method {
257    fn default() -> Self {
258        Method::Get
259    }
260}
261
262impl Display for Method{
263    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
264        match self {
265            Method::Get =>  write!(f, "get"),
266            Method::Post =>  write!(f, "post"),
267        }
268    }
269}
270
271#[derive(Debug, FromMeta)]
272enum Format {
273    /// TOML (parsed with toml)
274    Toml,
275
276    /// JSON (parsed with `serde_json`)
277    Json,
278
279    /// YAML (parsed with `yaml_rust2`)
280    Yaml,
281
282    /// YAML (parsed with `yaml_rust2`)
283    Yml,
284
285    /// INI (parsed with `rust_ini`)
286    Ini,
287
288    /// RON (parsed with ron)
289    Ron,
290
291    /// JSON5 (parsed with json5)
292    Json5,
293}
294
295
296impl Display for Format{
297    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
298        match self {
299            Format::Toml =>  write!(f, "toml"),
300            Format::Json =>  write!(f, "json"),
301            Format::Yaml =>  write!(f, "yaml"),
302            Format::Yml =>  write!(f, "yaml"),
303            Format::Ini =>  write!(f, "ini"),
304            Format::Ron =>  write!(f, "ron"),
305            Format::Json5 =>  write!(f, "json5"),
306        }
307    }
308}
309
310fn env_default() -> bool {
311    true
312}