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        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        // 获取 HOME 环境变量
68        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
83/// 添加环境源
84fn 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
100/// 添加http源
101fn 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
117/// 处理前缀
118fn 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    // 元素个数
130    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    /// 结构体名
172    ident: syn::Ident,
173    // /// 泛型参数
174    // generics: syn::Generics,
175
176    /// 配置前缀
177    #[darling(default)]
178    prefix: String,
179
180    /// 配置文件路径
181    #[darling(multiple, default)]
182    file: Vec<FileData>,
183
184    /// 通过指定环境变量来指定文件路径
185    #[darling(multiple, default)]
186    env: Vec<EnvData>,
187
188    /// 配置http
189    #[darling(multiple, default)]
190    http: Vec<HttpData>,
191
192    /// 是否从环境变量中读取,默认为true
193    #[darling(default = env_default)]
194    environment: bool,
195}
196
197/// 文件
198#[derive(Debug, FromMeta)]
199struct FileData{
200    path: String,
201    #[darling(default)]
202    require: bool,
203}
204
205/// 环境变量
206#[derive(Debug, FromMeta)]
207struct EnvData{
208    name: String,
209    #[darling(default)]
210    require: bool,
211}
212
213/// http
214#[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 (parsed with toml)
246    Toml,
247
248    /// JSON (parsed with `serde_json`)
249    Json,
250
251    /// YAML (parsed with `yaml_rust2`)
252    Yaml,
253
254    /// YAML (parsed with `yaml_rust2`)
255    Yml,
256
257    /// INI (parsed with `rust_ini`)
258    Ini,
259
260    /// RON (parsed with ron)
261    Ron,
262
263    /// JSON5 (parsed with json5)
264    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}