1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::spanned::Spanned;
5
6#[proc_macro_derive(Config, attributes(config))]
9pub fn derive_config(input: TokenStream) -> TokenStream {
10 let input = syn::parse_macro_input!(input as syn::DeriveInput);
11
12 match config_impl(input) {
13 Ok(output) => output,
14 Err(err) => err.to_compile_error().into(),
15 }
16}
17
18#[proc_macro_attribute]
21pub fn __erase(_attr: TokenStream, _item: TokenStream) -> TokenStream {
22 TokenStream::new()
23}
24
25fn config_impl(mut input: syn::DeriveInput) -> syn::Result<TokenStream> {
28 let mut default_functions = Vec::new();
29
30 #[cfg(feature = "derive-debug")]
31 input.attrs.push(syn::parse_quote! {
32 #[derive(Debug)]
33 });
34
35 #[cfg(feature = "derive-eq")]
36 input.attrs.push(syn::parse_quote! {
37 #[derive(PartialEq, Eq)]
38 });
39
40 #[cfg(feature = "derive-deserialize")]
41 {
42 input.attrs.push(syn::parse_quote! {
43 #[derive(serde::Deserialize)]
44 });
45 input.attrs.push(syn::parse_quote! {
46 #[serde(deny_unknown_fields, rename_all = "camelCase")]
47 });
48 }
49
50 #[cfg(feature = "derive-serialize")]
51 input.attrs.push(syn::parse_quote! {
52 #[derive(serde::Serialize)]
53 });
54
55 #[cfg(feature = "derive-jsonschema")]
56 input.attrs.push(syn::parse_quote! {
57 #[derive(schemars::JsonSchema)]
58 });
59
60 match &mut input.data {
61 syn::Data::Struct(item) => {
62 for field in &mut item.fields {
63 let opts = ConfigFieldOpts::extract_from(&mut field.attrs)?;
64
65 if !opts.required.unwrap_or_default() {
66 let new_default_attr = if let Some(expr) = opts.default {
67 let fname =
68 quote::format_ident!("__default_{}", field.ident.as_ref().unwrap());
69 let path_str = syn::Lit::Str(syn::LitStr::new(
70 &format!("{}::{}", input.ident, fname),
71 opts.span,
72 ));
73
74 default_functions.push(quote! {
75 fn #fname() -> String {
76 #expr.into()
77 }
78 });
79
80 syn::parse_quote! {
81 #[serde(default = #path_str)]
82 }
83 } else {
84 syn::parse_quote!(#[serde(default)])
85 };
86
87 field.attrs.push(new_default_attr);
88 }
89 }
90 }
91
92 syn::Data::Enum(item) => {
93 let unit_enum = item
94 .variants
95 .iter()
96 .all(|v| matches!(v.fields, syn::Fields::Unit));
97
98 if !unit_enum {
99 input.attrs.push(syn::parse_quote! {
100 #[serde(tag = "kind")]
101 });
102 }
103 }
104
105 _ => {
106 return Err(syn::Error::new_spanned(
107 input,
108 "#[derive(Config)] can only be applied to structs and enums",
109 ));
110 }
111 }
112
113 input.attrs.push(syn::parse_quote! { #[::setty::__erase] });
118
119 let item_name = &input.ident;
120
121 Ok(TokenStream::from(quote! {
122 #input
123
124 impl #item_name {
125 #(#default_functions)*
126 }
127 }))
128}
129
130struct ConfigFieldOpts {
133 required: Option<bool>,
134 default: Option<syn::Expr>,
135 span: Span,
136}
137
138impl ConfigFieldOpts {
139 fn new(span: Span) -> Self {
140 Self {
141 required: None,
142 default: None,
143 span,
144 }
145 }
146
147 fn merge(&mut self, other: Self) -> syn::Result<()> {
148 self.span = other.span;
149
150 if other.required.is_some() {
151 if self.required.is_some() {
152 return Err(syn::Error::new(
153 other.span,
154 "`required` specified more than once",
155 ));
156 }
157 self.required = other.required;
158 }
159
160 if other.default.is_some() {
161 if self.default.is_some() {
162 return Err(syn::Error::new(
163 other.span,
164 "`default` specified more than once",
165 ));
166 }
167 self.default = other.default;
168 }
169
170 if self.required == Some(true) && self.default.is_some() {
171 return Err(syn::Error::new(
172 other.span,
173 "can't be both `required` and specify a `default`",
174 ));
175 }
176
177 Ok(())
178 }
179
180 fn extract_from(attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConfigFieldOpts> {
181 let mut opts = ConfigFieldOpts::new(proc_macro2::Span::call_site());
182
183 for attr in attrs.iter() {
184 if attr.path().is_ident("config") {
185 let more_opts = Self::parse_from(attr)?;
186 opts.merge(more_opts)?;
187 }
188 }
189
190 attrs.retain(|attr| !attr.path().is_ident("config"));
191
192 Ok(opts)
193 }
194
195 fn parse_from(attr: &syn::Attribute) -> syn::Result<ConfigFieldOpts> {
196 let mut opts = ConfigFieldOpts::new(attr.span());
197
198 attr.parse_args_with(|input: syn::parse::ParseStream| {
199 while !input.is_empty() {
200 let ident: syn::Ident = input.parse()?;
201
202 match ident.to_string().as_str() {
203 "required" => {
204 if opts.required.is_some() {
205 return Err(syn::Error::new(
206 attr.span(),
207 "`required` specified more than once",
208 ));
209 }
210 opts.required = Some(true);
211 }
212
213 "default" => {
214 if opts.required.is_some() {
215 return Err(syn::Error::new(
216 attr.span(),
217 "`default` specified more than once",
218 ));
219 }
220 input.parse::<syn::Token![=]>()?;
221 let expr: syn::Expr = input.parse()?;
222 opts.default = Some(expr);
223 }
224
225 _ => {
226 return Err(syn::Error::new(
227 attr.span(),
228 format!("unknown config option `{}`", ident),
229 ));
230 }
231 }
232
233 let _ = input.parse::<syn::Token![,]>();
235 }
236
237 Ok(())
238 })?;
239
240 Ok(opts)
241 }
242}
243
244