1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4 Attribute, Expr, ItemStruct, Meta, parse::Parse, parse::ParseStream, parse_macro_input,
5 spanned::Spanned,
6};
7
8struct ConfideArgs {
9 no_default: bool,
10 no_debug: bool,
11}
12
13impl Parse for ConfideArgs {
14 fn parse(input: ParseStream) -> syn::Result<Self> {
15 let mut no_default = false;
16 let mut no_debug = false;
17 let vars = input.parse_terminated(syn::Ident::parse, syn::Token![,])?;
18 for var in vars {
19 if var == "no_default" {
20 no_default = true;
21 } else if var == "no_debug" {
22 no_debug = true;
23 } else {
24 return Err(syn::Error::new(
25 var.span(),
26 format!("unknown confide argument: `{var}`"),
27 ));
28 }
29 }
30 Ok(ConfideArgs {
31 no_default,
32 no_debug,
33 })
34 }
35}
36
37impl Default for ConfideArgs {
38 fn default() -> Self {
39 ConfideArgs {
40 no_default: false,
41 no_debug: false,
42 }
43 }
44}
45
46enum FieldAnnotation {
47 DefaultExpr(Expr),
48 DefaultDefault,
49 DurationExpr(String),
50 BytesExpr(String),
51}
52
53struct FieldAttr {
54 annotation: Option<FieldAnnotation>,
55 secret: bool,
56}
57
58fn is_expr_path(expr: &Expr) -> bool {
59 matches!(expr, Expr::Path(_))
60}
61
62fn extract_field_attr(attrs: &[Attribute]) -> syn::Result<FieldAttr> {
63 for attr in attrs {
64 if attr.path().is_ident("confide") {
65 match &attr.meta {
66 Meta::List(list) => {
67 let args: FieldConfideArgs = syn::parse2(list.tokens.clone())?;
68 let annotation = match args.default {
69 Some(FieldDefault::Expr(expr)) => Some(FieldAnnotation::DefaultExpr(expr)),
70 Some(FieldDefault::Bare) => Some(FieldAnnotation::DefaultDefault),
71 Some(FieldDefault::Duration(s)) => Some(FieldAnnotation::DurationExpr(s)),
72 Some(FieldDefault::Bytes(s)) => Some(FieldAnnotation::BytesExpr(s)),
73 None => None,
74 };
75 return Ok(FieldAttr {
76 annotation,
77 secret: args.secret,
78 });
79 }
80 Meta::Path(_) => {
81 return Err(syn::Error::new_spanned(
82 &attr.meta,
83 "bare `#[confide]` on a field is not supported; did you mean `#[confide(default)]`?",
84 ));
85 }
86 Meta::NameValue(_) => {
87 return Err(syn::Error::new_spanned(
88 &attr.meta,
89 "`#[confide = ...]` on a field is not supported; did you mean `#[confide(default = ...)]`?",
90 ));
91 }
92 }
93 }
94 }
95 Ok(FieldAttr {
96 annotation: None,
97 secret: false,
98 })
99}
100
101enum FieldDefault {
102 Expr(Expr),
103 Bare,
104 Duration(String),
105 Bytes(String),
106}
107
108struct FieldConfideArgs {
109 default: Option<FieldDefault>,
110 secret: bool,
111}
112
113impl Parse for FieldConfideArgs {
114 fn parse(input: ParseStream) -> syn::Result<Self> {
115 let mut default = None;
116 let mut secret = false;
117 let metas = input.parse_terminated(Meta::parse, syn::Token![,])?;
118 for meta in metas {
119 match &meta {
120 Meta::Path(p) if p.is_ident("default") => {
121 default = Some(FieldDefault::Bare);
122 }
123 Meta::Path(p) if p.is_ident("secret") => {
124 secret = true;
125 }
126 Meta::NameValue(nv) if nv.path.is_ident("default") => {
127 default = Some(FieldDefault::Expr(nv.value.clone()));
128 }
129 Meta::NameValue(nv) if nv.path.is_ident("default_duration") => {
130 if let Expr::Lit(lit) = &nv.value {
131 if let syn::Lit::Str(s) = &lit.lit {
132 default = Some(FieldDefault::Duration(s.value()));
133 continue;
134 }
135 }
136 return Err(syn::Error::new_spanned(
137 &nv.value,
138 "expected a string literal, e.g. default_duration = \"10s\"",
139 ));
140 }
141 Meta::NameValue(nv) if nv.path.is_ident("default_bytes") => {
142 if let Expr::Lit(lit) = &nv.value {
143 if let syn::Lit::Str(s) = &lit.lit {
144 default = Some(FieldDefault::Bytes(s.value()));
145 continue;
146 }
147 }
148 return Err(syn::Error::new_spanned(
149 &nv.value,
150 "expected a string literal, e.g. default_bytes = \"1MiB\"",
151 ));
152 }
153 other => {
154 return Err(syn::Error::new_spanned(
155 other,
156 "unexpected confide field argument",
157 ));
158 }
159 }
160 }
161 Ok(FieldConfideArgs { default, secret })
162 }
163}
164
165#[proc_macro_attribute]
166pub fn confide(attr: TokenStream, item: TokenStream) -> TokenStream {
167 let args = if attr.is_empty() {
168 ConfideArgs::default()
169 } else {
170 match syn::parse::<ConfideArgs>(attr) {
171 Ok(args) => args,
172 Err(e) => return e.to_compile_error().into(),
173 }
174 };
175 let struct_input = parse_macro_input!(item as ItemStruct);
176 let struct_name = &struct_input.ident;
177 let struct_visibility = &struct_input.vis;
178 let (impl_generics, type_generics, where_clause) = struct_input.generics.split_for_impl();
179 let struct_outer_attrs: Vec<_> = struct_input
180 .attrs
181 .iter()
182 .filter(|a| !a.path().is_ident("confide"))
183 .collect();
184
185 let fields = match &struct_input.fields {
186 syn::Fields::Named(fields_name) => &fields_name.named,
187 _ => {
188 return syn::Error::new(
189 struct_input.span(),
190 "confide only supports structs with named fields",
191 )
192 .to_compile_error()
193 .into();
194 }
195 };
196
197 let mut default_fns = Vec::new();
198 let mut field_outputs = Vec::new();
199 let mut default_fields = Vec::new();
200 let mut debug_fields: Vec<(syn::Ident, bool)> = Vec::new();
201
202 for field in fields.iter() {
203 let field_name = field.ident.as_ref().expect("named field");
204 let field_visibility = &field.vis;
205 let field_type = &field.ty;
206 let field_attr = match extract_field_attr(&field.attrs) {
207 Ok(a) => a,
208 Err(e) => return e.to_compile_error().into(),
209 };
210 let annotation = field_attr.annotation;
211 let is_secret = field_attr.secret;
212
213 let mut attrs_out: Vec<Attribute> = field
214 .attrs
215 .iter()
216 .filter(|a| !a.path().is_ident("confide"))
217 .cloned()
218 .collect();
219
220 match annotation {
221 Some(FieldAnnotation::DefaultExpr(expr)) => {
222 if is_expr_path(&expr) {
223 let path_str = quote!(#expr).to_string();
224
225 attrs_out.push(syn::parse_quote! {
226 #[serde(default = #path_str)]
227 });
228
229 default_fields.push(quote! { #field_name: #expr(), });
230 } else {
231 let fn_name = format_ident!("__confide_default_{}", field_name);
232 let fn_path = format!("{}::{}", struct_name, fn_name);
233
234 attrs_out.push(syn::parse_quote! {
235 #[serde(default = #fn_path)]
236 });
237
238 default_fns.push(quote! {
239 #[allow(non_snake_case)]
240 fn #fn_name() -> #field_type {
241 #expr
242 }
243 });
244
245 default_fields.push(quote! { #field_name: Self::#fn_name(), });
246 }
247 }
248 Some(FieldAnnotation::DefaultDefault) => {
249 attrs_out.push(syn::parse_quote! {
250 #[serde(default)]
251 });
252
253 default_fields.push(quote! { #field_name: ::core::default::Default::default(), });
254 }
255 Some(FieldAnnotation::DurationExpr(s)) => {
256 let duration = match humantime::parse_duration(&s) {
257 Ok(d) => d,
258 Err(e) => {
259 return syn::Error::new(
260 field_name.span(),
261 format!("invalid duration: {e}"),
262 )
263 .to_compile_error()
264 .into();
265 }
266 };
267 let secs = duration.as_secs();
268 let nanos = duration.subsec_nanos();
269 let fn_name = format_ident!("__confide_default_{}", field_name);
270 let fn_path = format!("{}::{}", struct_name, fn_name);
271
272 attrs_out.push(syn::parse_quote! {
273 #[serde(with = "confide::humantime_serde")]
274 });
275 attrs_out.push(syn::parse_quote! {
276 #[serde(default = #fn_path)]
277 });
278
279 default_fns.push(quote! {
280 #[allow(non_snake_case)]
281 fn #fn_name() -> #field_type {
282 ::core::time::Duration::new(#secs, #nanos)
283 }
284 });
285
286 default_fields.push(quote! { #field_name: Self::#fn_name(), });
287 }
288 Some(FieldAnnotation::BytesExpr(s)) => {
289 let bs: bytesize::ByteSize = match s.parse() {
290 Ok(v) => v,
291 Err(e) => {
292 return syn::Error::new(
293 field_name.span(),
294 format!("invalid byte size: {e}"),
295 )
296 .to_compile_error()
297 .into();
298 }
299 };
300 let bytes = bs.as_u64();
301 let fn_name = format_ident!("__confide_default_{}", field_name);
302 let fn_path = format!("{}::{}", struct_name, fn_name);
303
304 let ser_fn_name = format_ident!("__confide_serialize_bytes_{}", field_name);
305 let de_fn_name = format_ident!("__confide_deserialize_bytes_{}", field_name);
306 let ser_path = format!("{}::{}", struct_name, ser_fn_name);
307 let de_path = format!("{}::{}", struct_name, de_fn_name);
308
309 attrs_out.push(syn::parse_quote! {
310 #[serde(
311 serialize_with = #ser_path,
312 deserialize_with = #de_path
313 )]
314 });
315 attrs_out.push(syn::parse_quote! {
316 #[serde(default = #fn_path)]
317 });
318
319 default_fns.push(quote! {
320 #[allow(non_snake_case)]
321 fn #fn_name() -> #field_type {
322 ::core::convert::TryInto::<#field_type>::try_into(#bytes)
323 .expect("default_bytes value out of range for field type")
324 }
325 });
326 default_fns.push(quote! {
327 #[allow(non_snake_case, unused)]
328 fn #ser_fn_name<__S: ::serde::Serializer>(
329 val: &#field_type,
330 s: __S,
331 ) -> ::core::result::Result<__S::Ok, __S::Error> {
332 let bytes: u64 = ::core::convert::TryInto::<u64>::try_into(*val)
333 .map_err(|_| {
334 ::serde::ser::Error::custom(
335 ::core::format_args!(
336 "byte size field value out of range for u64"
337 ),
338 )
339 })?;
340 ::serde::Serialize::serialize(
341 &::confide::bytesize::ByteSize::b(bytes)
342 .display()
343 .iec()
344 .to_string(),
345 s,
346 )
347 }
348 });
349 default_fns.push(quote! {
350 #[allow(non_snake_case, unused)]
351 fn #de_fn_name<'__de, __D: ::serde::Deserializer<'__de>>(
352 d: __D,
353 ) -> ::core::result::Result<#field_type, __D::Error> {
354 struct __ConfideBytesVisitor;
355 impl<'__de2> ::serde::de::Visitor<'__de2> for __ConfideBytesVisitor {
356 type Value = u64;
357 fn expecting(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
358 f.write_str("a byte size string like '1MiB' or an integer")
359 }
360 fn visit_u64<__E: ::serde::de::Error>(
361 self,
362 v: u64,
363 ) -> ::core::result::Result<u64, __E> {
364 Ok(v)
365 }
366 fn visit_i64<__E: ::serde::de::Error>(
367 self,
368 v: i64,
369 ) -> ::core::result::Result<u64, __E> {
370 u64::try_from(v).map_err(|_| {
371 __E::invalid_value(
372 ::serde::de::Unexpected::Signed(v),
373 &"non-negative integer",
374 )
375 })
376 }
377 fn visit_str<__E: ::serde::de::Error>(
378 self,
379 v: &str,
380 ) -> ::core::result::Result<u64, __E> {
381 v.parse::<::confide::bytesize::ByteSize>()
382 .map(|bs| bs.as_u64())
383 .map_err(|_| {
384 __E::invalid_value(::serde::de::Unexpected::Str(v), &self)
385 })
386 }
387 }
388 let val: u64 = if d.is_human_readable() {
389 d.deserialize_any(__ConfideBytesVisitor)?
390 } else {
391 d.deserialize_u64(__ConfideBytesVisitor)?
392 };
393 #field_type::try_from(val).map_err(|_| {
394 ::serde::de::Error::invalid_value(
395 ::serde::de::Unexpected::Unsigned(val),
396 &concat!(
397 "a byte size value fitting in ",
398 ::core::stringify!(#field_type),
399 ),
400 )
401 })
402 }
403 });
404
405 default_fields.push(quote! { #field_name: Self::#fn_name(), });
406 }
407 None => {}
408 }
409
410 debug_fields.push((field_name.clone(), is_secret));
411
412 field_outputs.push(quote! {
413 #(#attrs_out)*
414 #field_visibility #field_name: #field_type,
415 });
416 }
417
418 let default_impl = if args.no_default {
419 quote! {}
420 } else {
421 quote! {
422 impl #impl_generics Default for #struct_name #type_generics #where_clause {
423 fn default() -> Self {
424 Self {
425 #(#default_fields)*
426 }
427 }
428 }
429 }
430 };
431
432 let debug_impl = if args.no_debug {
433 quote! {}
434 } else {
435 let field_entries = debug_fields.iter().map(|(name, secret)| {
436 let name_str = name.to_string();
437 if *secret {
438 quote! { .field(#name_str, &"***") }
439 } else {
440 quote! { .field(#name_str, &self.#name) }
441 }
442 });
443 quote! {
444 impl #impl_generics ::core::fmt::Debug for #struct_name #type_generics #where_clause {
445 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
446 f.debug_struct(stringify!(#struct_name))
447 #(#field_entries)*
448 .finish()
449 }
450 }
451 }
452 };
453
454 let expanded = quote! {
455 #(#struct_outer_attrs)*
456 #struct_visibility struct #struct_name #type_generics #where_clause {
457 #(#field_outputs)*
458 }
459
460 impl #impl_generics #struct_name #type_generics #where_clause {
461 #(#default_fns)*
462 }
463
464 #default_impl
465 #debug_impl
466 };
467 expanded.into()
468}