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 let args = match syn::parse(attr) {
53 Ok(args) => args,
54 Err(error) => return with_original(error),
55 };
56
57 match expand::expand(args, input) {
58 Ok(tokens) => tokens.into(),
59 Err(error) => with_original(error),
60 }
61}