Skip to main content

azalia_config_macros/
lib.rs

1// 🐻‍❄️🪚 azalia: Noelware's Rust commons library.
2// Copyright (c) 2024-2025 Noelware, LLC. <team@noelware.org>
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in all
12// copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20// SOFTWARE.
21
22#![doc(html_logo_url = "https://cdn.floofy.dev/images/trans.png")]
23#![doc(html_favicon_url = "https://cdn.floofy.dev/images/trans.png")]
24#![cfg_attr(any(noeldoc, docsrs), feature(doc_cfg))]
25
26mod merge;
27
28#[cfg(feature = "unstable")]
29mod tryfromenv;
30
31use proc_macro::TokenStream;
32use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
33
34/// Procedural macro to implement the [**`Merge`**] trait from `azalia::config`'s `merge` module.
35///
36/// [**`Merge`**]: trait.Merge.html
37///
38/// ## Example
39/// > **NOTE**: This will require the `macros` feature for `azalia_config` or `config+macros` for
40/// > the `azalia` crate.
41///
42/// ```ignore
43/// # mod azalia {
44/// #     mod config {
45/// #         mod merge {
46/// #             pub use azalia_config_macros::Merge;
47/// #         }
48/// #     }
49/// # }
50/// #
51/// use azalia::config::merge::Merge;
52///
53/// #[derive(Debug, Merge, Default, PartialEq)]
54/// pub struct Config {
55///     pub a: String,
56///
57///     #[merge(skip)]
58///     pub b: bool,
59///
60///     #[merge(strategy = "i32::merge")]
61///     pub c: i32,
62/// }
63///
64/// let mut config = Config::default();
65/// assert_eq!(config, Config { a: String::default(), b: false, c: 0i32 });
66///
67/// config.merge(Config { a: "hello world".into(), b: true, c: 9 });
68/// assert_eq!(config, Config { a: "hello world".into(), b: false, c: 42 });
69///
70/// // strategies can be regular functions that will expand
71/// // to a fn that implements `(&mut self, Self)`
72/// mod i32 {
73///     pub fn merge(lhs: &mut i32, rhs: i32) {
74///         *lhs = 42;
75///     }
76/// }
77/// ```
78#[allow(non_snake_case)]
79#[proc_macro_derive(Merge, attributes(merge))]
80pub fn Merge(input: TokenStream) -> TokenStream {
81    let derive = parse_macro_input!(input as DeriveInput);
82    match &derive.data {
83        Data::Struct(s) => merge::expand_struct(&derive, &s.fields).into(),
84        Data::Enum(e) => syn::Error::new(e.enum_token.span(), "merge trait for enumerations are not supported")
85            .into_compile_error()
86            .into(),
87
88        Data::Union(u) => syn::Error::new(u.union_token.span(), "merge trait for unions will never be supported")
89            .into_compile_error()
90            .into(),
91    }
92}
93
94/// Procedural macro to implement [`TryFromEnv`] for `struct`s.
95///
96/// [`TryFromEnv`]: trait.TryFromEnv.html
97///
98/// ## Example
99/// ```ignore
100/// # mod azalia {
101/// #     mod config {
102/// #         mod env {
103/// #             pub use azalia_config_macros::TryFromEnv;
104/// #         }
105/// #     }
106/// # }
107/// #
108/// use azalia::config::env::TryFromEnv;
109/// use std::error::Error;
110///
111/// #[derive(TryFromEnv)]
112/// #[env(error = Box<dyn Error>, prefix = "APP_")]
113/// pub struct Config {
114///     #[env(var = "A")]
115///     pub a: String,
116/// }
117///
118/// # fn main() -> Result<(), Box<dyn Error>> {
119/// #     let _ = unsafe { std::env::set_var("APP_A", "apple") };
120/// #     assert!(std::env::var("APP_A").is_ok());
121/// #
122/// // assume that APP_A=apple
123/// let config = Config::try_from_env()?;
124/// assert!(config.a, "apple");
125/// #
126/// #      let _ = unsafe { std::env::remove_var("APP_A") };
127/// #      assert!(std::env::var("APP_A").is_err());
128/// #
129/// #      Ok(())
130/// # }
131/// ```
132#[allow(non_snake_case)]
133#[cfg(feature = "unstable")]
134#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "unstable")))]
135#[proc_macro_derive(TryFromEnv, attributes(env))]
136pub fn TryFromEnv(input: TokenStream) -> TokenStream {
137    // let derive = parse_macro_input!(input as DeriveInput);
138    // match &derive.data {
139    //     Data::Struct(s) => tryfromenv::expand_struct(&derive, &s.fields)
140    //         .unwrap_or_else(syn::Error::into_compile_error)
141    //         .into(),
142
143    //     Data::Enum(_) => syn::Error::new(derive.span(), "TryFromEnv trait for enumerations are not supported")
144    //         .into_compile_error()
145    //         .into(),
146
147    //     Data::Union(_) => syn::Error::new(derive.span(), "TryFromEnv trait for unions will never be supported")
148    //         .into_compile_error()
149    //         .into(),
150    // }
151
152    syn::Error::new(
153        proc_macro2::Span::call_site(),
154        "`#[derive(TryFromEnv)]` is not implemented at this time",
155    )
156    .into_compile_error()
157    .into()
158}