Skip to main content

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/////////////////////////////////////////////////////////////////////////////////////////