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                .clone()
142                .into_table()
143                .unwrap()
144            })
145        }
146    }
147    ts
148}
149
150#[derive(Debug, FromDeriveInput)]
151#[darling(attributes(properties), supports(struct_named))]
152struct ConfigurationData {
153    /// 结构体名
154    ident: syn::Ident,
155    // /// 泛型参数
156    // generics: syn::Generics,
157
158    /// 配置前缀
159    #[darling(default)]
160    prefix: String,
161
162    /// 配置文件路径
163    #[darling(multiple, default)]
164    file: Vec<FileData>,
165
166    /// 配置http
167    #[darling(multiple, default)]
168    http: Vec<HttpData>,
169
170    /// 是否从环境变量中读取,默认为true
171    #[darling(default = env_default)]
172    environment: bool,
173}
174
175#[derive(Debug, FromMeta)]
176struct FileData{
177    path: String,
178    #[darling(default)]
179    require: bool,
180}
181
182#[derive(Debug, FromMeta)]
183struct HttpData{
184    url: String,
185    #[darling(default)]
186    method: Method,
187    format: Format,
188}
189
190#[derive(Debug, FromMeta)]
191enum Method {
192    Get,
193    Post,
194}
195
196impl Default for Method {
197    fn default() -> Self {
198        Method::Get
199    }
200}
201
202impl Display for Method{
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204        match self {
205            Method::Get =>  write!(f, "get"),
206            Method::Post =>  write!(f, "post"),
207        }
208    }
209}
210
211#[derive(Debug, FromMeta)]
212enum Format {
213    /// TOML (parsed with toml)
214    Toml,
215
216    /// JSON (parsed with `serde_json`)
217    Json,
218
219    /// YAML (parsed with `yaml_rust2`)
220    Yaml,
221
222    /// YAML (parsed with `yaml_rust2`)
223    Yml,
224
225    /// INI (parsed with `rust_ini`)
226    Ini,
227
228    /// RON (parsed with ron)
229    Ron,
230
231    /// JSON5 (parsed with json5)
232    Json5,
233}
234
235
236impl Display for Format{
237    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
238        match self {
239            Format::Toml =>  write!(f, "toml"),
240            Format::Json =>  write!(f, "json"),
241            Format::Yaml =>  write!(f, "yaml"),
242            Format::Yml =>  write!(f, "yaml"),
243            Format::Ini =>  write!(f, "ini"),
244            Format::Ron =>  write!(f, "ron"),
245            Format::Json5 =>  write!(f, "json5"),
246        }
247    }
248}
249
250fn env_default() -> bool {
251    true
252}