setty_derive/lib.rs
1mod attrs;
2mod combine;
3mod config;
4mod default;
5mod derive;
6
7/////////////////////////////////////////////////////////////////////////////////////////
8
9/// Our main workhorse. Derives the attributes based on the set of enabled crate features.
10///
11/// ## Field Attributes
12/// These arguments can be specified in `#[config(...)]` field attribute:
13/// * `default` - Use `Default::default` value if field is not present
14/// * `default = $expr` - Specifies expression used to initialize the value when it's not present in config
15/// * `default_str = "$str"` - Shorthand for`default = "$str".parse().unwrap()`
16/// * `combine(keep | replace | merge)` - Allows overriding how values are combined across different config files
17/// * Possible values:
18/// * `keep` - keeps first seen value
19/// * `replace` - fully replaces with the new value
20/// * `merge` - merges object keys and concatenates arrays, merge is smart and will not merge values across different enums
21/// * Default behavior:
22/// * `replace` for all known value types
23/// * `merge` for unknown types
24/// * You will need to implement `setty::combine::Combine` for it to work for custom types
25/// * `Config` derive macro automatically implements it for you
26/// * If you don't want any merging - simply override to use `combine(replace)`
27///
28/// ## Interaction with other attributes
29/// * `#[deprecated(since = "..", reason = "..")]` attribute (and its other forms):
30/// * Will be propagated
31/// * A `"deprecation": {"since": "..", "reason": ".."}` will be added to JSON schema
32/// * Deprecation callback will be called if value is present in the config during loading
33/// * `#[serde(...)]` attribute will be propagated and can be used to override default behaviour (e.g. `#[serde(tag = "type")]`)
34/// * `#[schemars(...)]` attribute will be propagated
35///
36#[proc_macro_derive(Config, attributes(config, serde, schemars))]
37pub fn derive_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
38 match derive_config_impl(input.into()) {
39 Ok(output) => proc_macro::TokenStream::from(output),
40 Err(err) => err.to_compile_error().into(),
41 }
42}
43
44pub(crate) fn derive_config_impl(
45 input: proc_macro2::TokenStream,
46) -> syn::Result<proc_macro2::TokenStream> {
47 let input: syn::DeriveInput = syn::parse2(input)?;
48 let combine_output = combine::combine_impl(&input)?;
49 let config_output = config::config_impl(input)?;
50
51 Ok(quote::quote! {
52 #config_output
53 #combine_output
54 })
55}
56
57/////////////////////////////////////////////////////////////////////////////////////////
58
59/// Special version of built-in `#[derive(Default)]` that recognizes `#[config(default = $expr)]` attributes
60#[proc_macro_derive(Default, attributes(config, default))]
61pub fn derive_default(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
62 let input = syn::parse_macro_input!(input as syn::DeriveInput);
63
64 match default::default_impl(input) {
65 Ok(output) => proc_macro::TokenStream::from(output),
66 Err(err) => err.to_compile_error().into(),
67 }
68}
69
70/////////////////////////////////////////////////////////////////////////////////////////
71
72/// Special version of `#[derive(..)]` macro. Works just like the standard one, except it
73/// will de-duplicate the derives expanded from [`Config`] and explicit ones.
74///
75/// Thus declaration such as `#[setty::derive(Config, Clone, serde::Deserialize)]` will
76/// always derive `Clone` and `Deserialize` even if those are not configured via top-level features,
77/// and will not duplicate implementations if those features were enabled.
78#[proc_macro_attribute]
79pub fn derive(
80 attr: proc_macro::TokenStream,
81 item: proc_macro::TokenStream,
82) -> proc_macro::TokenStream {
83 match derive::derive_impl(attr.into(), item.into()) {
84 Ok(output) => proc_macro::TokenStream::from(output),
85 Err(err) => err.to_compile_error().into(),
86 }
87}
88
89/////////////////////////////////////////////////////////////////////////////////////////
90
91#[proc_macro_attribute]
92pub fn __erase(
93 _attr: proc_macro::TokenStream,
94 _item: proc_macro::TokenStream,
95) -> proc_macro::TokenStream {
96 proc_macro::TokenStream::new()
97}
98
99/////////////////////////////////////////////////////////////////////////////////////////