Skip to main content

confide_impl/
lib.rs

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}