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<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
65/// 添加环境源
66fn 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
82/// 添加http源
83fn 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
99/// 处理前缀
100fn 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    // 元素个数
112    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    /// 结构体名
153    ident: syn::Ident,
154    // /// 泛型参数
155    // generics: syn::Generics,
156
157    /// 配置前缀
158    #[darling(default)]
159    prefix: String,
160
161    /// 配置文件路径
162    #[darling(multiple, default)]
163    file: Vec<FileData>,
164
165    /// 配置http
166    #[darling(multiple, default)]
167    http: Vec<HttpData>,
168
169    /// 是否从环境变量中读取,默认为true
170    #[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 (parsed with toml)
213    Toml,
214
215    /// JSON (parsed with `serde_json`)
216    Json,
217
218    /// YAML (parsed with `yaml_rust2`)
219    Yaml,
220
221    /// YAML (parsed with `yaml_rust2`)
222    Yml,
223
224    /// INI (parsed with `rust_ini`)
225    Ini,
226
227    /// RON (parsed with ron)
228    Ron,
229
230    /// JSON5 (parsed with json5)
231    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}