config_plus_macro/
lib.rs

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    // parse the input to the ValidationData struct defined above
13    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 generics = configuration.generics;
20    let prefix = configuration.prefix;
21    let files = configuration.file;
22    let environment = configuration.environment;
23    let https = configuration.http;
24
25    // let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
26    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 #impl_generics #ident #ty_generics #where_clause {
32        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
49/// 添加文件源
50fn file_source(files: Vec<String>) -> Vec<TokenStream2> {
51    let mut ts = vec![];
52
53    for file in files {
54        ts.push(quote! {
55            .add_source(
56                ::config_plus::File::with_name(#file)
57            )
58        })
59    }
60    ts
61}
62
63/// 添加环境源
64fn environment_source(environment: bool) -> TokenStream2 {
65    let mut ts = quote! {};
66
67    if environment {
68        ts.extend(quote! {
69            .add_source(
70                ::config_plus::Environment::with_prefix("")
71                        .separator(".")
72                        .prefix_separator("")
73                        .list_separator(",")
74            )
75        })
76    }
77    ts
78}
79
80/// 添加http源
81fn http_source(https: Vec<HttpData>) -> Vec<TokenStream2> {
82    let mut ts = vec![];
83
84    for http in https {
85        let url = http.url;
86        let format = http.format.to_string();
87        let method = http.method.to_string();
88        ts.push(quote! {
89            .add_source(
90                ::config_plus::Http::with(#url, #format, #method)
91            )
92        })
93    }
94    ts
95}
96
97/// 处理前缀
98fn process_prefix(prefix: String, ident: &syn::Ident) -> TokenStream2 {
99    let mut ts = quote! {};
100
101    if prefix.is_empty() {
102        return quote! {
103            .try_deserialize()
104            .unwrap()
105        };
106    }
107    let split = prefix.split(".");
108
109    // 元素个数
110    let count = split.clone().count();
111    for (index, str) in split.enumerate() {
112        if count == 1 {
113            ts.extend(quote! {
114                .get::<#ident>(#str)
115                .unwrap()
116            });
117            return ts;
118        }
119
120        if index == 0 {
121            ts.extend(quote! {
122                .get_table(#str)
123                .unwrap()
124            })
125        } else if count == index + 1 {
126            ts.extend(quote! {
127                .get(#str)
128                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
129                .unwrap()
130                .clone()
131                .try_deserialize()
132                .unwrap()
133            })
134        } else {
135            ts.extend(quote! {
136                .get(#str)
137                .ok_or_else(|| ::config_plus::ConfigError::NotFound(#str.into()))
138                .unwrap()
139                .into_table()
140                .unwrap()
141            })
142        }
143    }
144    ts
145}
146
147#[derive(Debug, FromDeriveInput)]
148#[darling(attributes(properties), supports(struct_named))]
149struct ConfigurationData {
150    /// 结构体名
151    ident: syn::Ident,
152    // /// 泛型参数
153    // generics: syn::Generics,
154
155    /// 配置前缀
156    #[darling(default)]
157    prefix: String,
158
159    /// 配置文件路径
160    #[darling(multiple, default)]
161    file: Vec<String>,
162
163    /// 配置http
164    #[darling(multiple, default)]
165    http: Vec<HttpData>,
166
167    /// 是否从环境变量中读取,默认为true
168    #[darling(default = env_default)]
169    environment: bool,
170}
171
172#[derive(Debug, FromMeta)]
173struct HttpData{
174    url: String,
175    #[darling(default)]
176    method: Method,
177    format: Format,
178}
179
180#[derive(Debug, FromMeta)]
181enum Method {
182    Get,
183    Post,
184}
185
186impl Default for Method {
187    fn default() -> Self {
188        Method::Get
189    }
190}
191
192impl Display for Method{
193    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
194        match self {
195            Method::Get =>  write!(f, "get"),
196            Method::Post =>  write!(f, "post"),
197        }
198    }
199}
200
201#[derive(Debug, FromMeta)]
202enum Format {
203    /// TOML (parsed with toml)
204    Toml,
205
206    /// JSON (parsed with `serde_json`)
207    Json,
208
209    /// YAML (parsed with `yaml_rust2`)
210    Yaml,
211
212    /// YAML (parsed with `yaml_rust2`)
213    Yml,
214
215    /// INI (parsed with `rust_ini`)
216    Ini,
217
218    /// RON (parsed with ron)
219    Ron,
220
221    /// JSON5 (parsed with json5)
222    Json5,
223}
224
225
226impl Display for Format{
227    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
228        match self {
229            Format::Toml =>  write!(f, "toml"),
230            Format::Json =>  write!(f, "json"),
231            Format::Yaml =>  write!(f, "yaml"),
232            Format::Yml =>  write!(f, "yaml"),
233            Format::Ini =>  write!(f, "ini"),
234            Format::Ron =>  write!(f, "ron"),
235            Format::Json5 =>  write!(f, "json5"),
236        }
237    }
238}
239
240fn env_default() -> bool {
241    true
242}