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}