config_plus_macro/
lib.rs

1//! 通过`#[derive(Deserialize, Configuration)`] 为结构体(必须实现[Deserialize]特征)生成一个`config`方法,
2//! 调用该方法可以读取配置文件中的属性
3
4use darling::{FromDeriveInput, FromMeta};
5use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8use std::fmt::{Display, Formatter};
9use syn::{parse_macro_input, DeriveInput};
10
11/// 为结构体生成一个`config`方法,调用该方法可以读取配置文件中的属性
12///
13/// # Example
14/// `application.yml`文件的内容为
15/// ```
16/// "
17/// push:
18///   file:
19///     enable: 1
20///     appId:  4ULDFh55cx9uM
21///     appKey: MPRaknd2Q49hgE1
22///     masterSecret: esEDaOfvvi5aD
23///     packageName:  www.baidu.com
24/// "
25/// ```
26/// rust代码
27/// ```
28/// r#"
29/// use config_macro::Configuration;
30/// use serde::Deserialize;
31///
32/// #[derive(Debug, Deserialize, Configuration)]
33/// #[properties(prefix = "push.file", file = "application.yml")]
34/// #[serde(rename_all(deserialize = "camelCase"))]
35/// struct PushFile {
36///     enable: u8,
37///     app_id: String,
38///     app_key: String,
39///     master_secret: String,
40///     package_name: String,
41/// }
42///
43/// let config = PushFile::config();
44/// "#
45/// ```
46/// `#[properties]`可以配置的属性
47/// - prefix: 读取配置文件的前缀
48/// - file: 配置文件的路径(可以配置多个file属性)
49/// - environment: 是否从环境变量中读取,默认为true
50#[proc_macro_derive(Configuration, attributes(properties))]
51pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
52    let input: DeriveInput = parse_macro_input!(input);
53
54    // parse the input to the ValidationData struct defined above
55    let configuration = ConfigurationData::from_derive_input(&input).unwrap();
56    expand(configuration).into()
57}
58
59fn expand(configuration: ConfigurationData) -> TokenStream2 {
60    let ident = configuration.ident;
61    // let generics = configuration.generics;
62    let prefix = configuration.prefix;
63    let files = configuration.file;
64    let environment = configuration.environment;
65    let https = configuration.http;
66
67    // let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
68    let file_sources = file_source(files);
69    let environment_source = environment_source(environment);
70    let http_sources = http_source(https);
71    let get_result = process_prefix(prefix, &ident);
72    quote! {
73        // impl #impl_generics #ident #ty_generics #where_clause {
74        impl #ident {
75            pub fn config()  -> &'static Self {
76                static CONFIG: ::std::sync::LazyLock<#ident>  = ::std::sync::LazyLock::new(|| {
77                    ::config::Config::builder()
78                        #(#file_sources)* 
79                        #environment_source
80                        #(#http_sources)* 
81                        .build()
82                        .unwrap()
83                        #get_result
84                });
85                &CONFIG
86            }
87        }
88    }
89}
90
91/// 添加文件源
92fn file_source(files: Vec<String>) -> Vec<TokenStream2> {
93    let mut ts = vec![];
94
95    for file in files {
96        ts.push(quote! {
97            .add_source(
98                ::config::File::with_name(#file)
99            )
100        })
101    }
102    ts
103}
104
105/// 添加环境源
106fn environment_source(environment: bool) -> TokenStream2 {
107    let mut ts = quote! {};
108
109    if environment {
110        ts.extend(quote! {
111            .add_source(
112                ::config::Environment::with_prefix("")
113                        .separator(".")
114                        .prefix_separator("")
115                        .list_separator(",")
116            )
117        })
118    }
119    ts
120}
121
122/// 添加http源
123fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
124    let mut ts = vec![];
125    
126    for http in https {
127        let url = http.url;
128        let format = http.format.to_string();
129        let method = http.method.to_string();
130        ts.push(quote! {
131            .add_source(
132                ::config_plus::Http::with(#url, #format, #method)
133            )
134        })
135    }
136    ts
137}
138
139/// 处理前缀
140fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
141    let mut ts = quote! {};
142
143    if prefix.is_empty() {
144        return quote! {
145            .try_deserialize()
146            .unwrap()
147        };
148    }
149    let split = prefix.split(".");
150
151    // 元素个数
152    let count = split.clone().count();
153    for (index, str) in split.enumerate() {
154        if count == 1 {
155            ts.extend(quote! {
156                .get::<#ident>(#str)
157                .unwrap()
158            });
159            return ts;
160        }
161
162        if index == 0 {
163            ts.extend(quote! {
164                .get_table(#str)
165                .unwrap()
166            })
167        } else if count == index + 1 {
168            ts.extend(quote! {
169                .get(#str)
170                .ok_or_else(|| ::config::ConfigError::NotFound(#str.into()))
171                .unwrap()
172                .clone()
173                .try_deserialize()
174                .unwrap()
175            })
176        } else {
177            ts.extend(quote! {
178                .get(#str)
179                .ok_or_else(|| ::config::ConfigError::NotFound(#str.into()))
180                .unwrap()
181                .into_table()
182                .unwrap()
183            })
184        }
185    }
186    ts
187}
188
189#[derive(Debug, FromDeriveInput)]
190#[darling(attributes(properties), supports(struct_named))]
191struct ConfigurationData {
192    /// 结构体名
193    ident: syn::Ident,
194    // /// 泛型参数
195    // generics: syn::Generics,
196
197    /// 配置前缀
198    #[darling(default)]
199    prefix: String,
200
201    /// 配置文件路径
202    #[darling(multiple, default)]
203    file: Vec<String>,
204
205    /// 配置http
206    #[darling(multiple, default)]
207    http: Vec<HttpData>,
208
209    /// 是否从环境变量中读取,默认为true
210    #[darling(default = env_default)]
211    environment: bool,
212}
213
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}