Skip to main content

dynamic_config_macros/
lib.rs

1//! Procedural macro implementation for [`dynamic-config`](https://docs.rs/dynamic-config).
2//!
3//! Do not depend on this crate directly — it is an implementation detail of
4//! `dynamic-config`, which re-exports everything here and provides the runtime
5//! the generated code calls into. The two are released together and pinned to
6//! the same version.
7//!
8//! A procedural macro can only be defined in a crate with `proc-macro = true`,
9//! which is the sole reason this crate exists separately.
10
11#![forbid(unsafe_code)]
12#![deny(missing_docs)]
13
14mod args;
15mod expand;
16
17use proc_macro::TokenStream;
18use syn::ItemStruct;
19
20/// Turns a struct into a hot-reloadable configuration snapshot.
21///
22/// Documented on the re-export in `dynamic_config`, which is where callers
23/// should read it.
24#[proc_macro_attribute]
25pub fn dynamic_config(attr: TokenStream, item: TokenStream) -> TokenStream {
26    // Errors are emitted *alongside* the original item, never instead of it.
27    // Swallowing the struct turns one attribute mistake into a cascade of
28    // "cannot find type" errors at every use site, burying the message that
29    // actually matters.
30    let fallback = item.clone();
31
32    let with_original = |error: syn::Error| -> TokenStream {
33        let mut output: TokenStream = error.into_compile_error().into();
34        output.extend(fallback.clone());
35        output
36    };
37
38    // A named, spanned message for a non-struct: syn's raw "expected `struct`"
39    // does not say which attribute wanted one, or why.
40    let input: ItemStruct = match syn::parse(item.clone()) {
41        Ok(input) => input,
42        Err(_) => {
43            return with_original(syn::Error::new_spanned(
44                proc_macro2::TokenStream::from(item),
45                "#[dynamic_config] goes on a struct with named fields — the \
46                 fields are what the configuration deserializes into; an enum \
47                 or function has nowhere to put them",
48            ))
49        }
50    };
51
52    // The attribute takes no arguments; anything present gets the
53    // migration map, next to the untouched struct.
54    if let Err(error) = syn::parse::<args::Args>(attr) {
55        return with_original(error);
56    }
57
58    match expand::expand(input) {
59        Ok(tokens) => tokens.into(),
60        Err(error) => with_original(error),
61    }
62}