envman_derive/
lib.rs

1#![deny(
2    clippy::unwrap_used,
3    clippy::expect_used,
4    clippy::panic,
5    clippy::print_stdout,
6    clippy::print_stderr
7)]
8
9use syn::{parse_macro_input, DeriveInput};
10
11/// Automatically implements [`envman::EnvMan`]
12///
13/// # Note
14/// - If the field is `Option`, the default value is `None`.
15/// - If the field has a `rename` attribute, the field name is not affected by `suffix`, `prefix`, and `rename_all`.
16/// - The `rename_all` attribute affects only the base field name transformation and does not influence the application of `prefix` or `suffix`.
17///
18/// # Struct Attributes:
19///
20/// ### rename_all: `rename_all = "rule"` (default: SCREAMING_SNAKE_CASE)
21/// Applies to all fields.
22///
23/// The possible values are ("lowercase", "UPPERCASE", "PascalCase", "camelCase",
24/// "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE").
25///
26/// ### prefix: `prefix = "prefix"` (default: None)
27/// Prefix to all fields.
28///
29/// ### suffix: `suffix = "suffix"` (default: None)
30/// Suffix to all fields.
31///
32/// # Field Attributes:
33///
34/// ### rename : `rename = "new name"` (default: UPPER_CASE)
35///
36/// ### parser: `parser = utils::default_parser` (default: FromStr::from_str)
37/// Parser type is `fn(&str) -> Result<T, E>` and `E` must implement `std::error::Error`.
38///
39/// ### separator: `separator = ","` (default: None)
40/// For Vec/array types, split the environment variable by this separator.
41/// Example: `ALLOWED_HOSTS=host1,host2,host3` becomes `vec!["host1", "host2", "host3"]`
42///
43/// ### validate: `validate = my_validator` (default: None)
44/// Custom validation function with signature `fn(&T) -> Result<(), E>` where `E: Display`.
45/// The error message from the Result will be included in the validation error.
46///
47/// ### secret: `secret` (default: false)
48/// Mark this field as secret. When used with `EnvManDebug`, the value will be masked as "***".
49///
50/// ### group_test: (default: None)
51/// If under test, use this value (Priority is first).
52///
53/// - test_flag: `test` (Equivalent to the code below)
54/// - test_expr: `test = Default::default()` (put any expression)
55///
56/// ### group_default: (default: None)
57/// If not found in the environment, use this value.
58/// If a test exists and is under test, use the test.
59///
60/// - default_flag: `default` (Equivalent to the code below)
61/// - default_expr: `default = Default::default()` (put any expression)
62///
63/// ### nest: `nest` (default: false)
64/// If the field implements `envman::EnvMan`, it will be parsed as a struct.
65///
66/// # Example
67/// ```rust
68/// # use envman_derive::EnvMan;
69/// # mod envman {
70/// #   include!("../../envman/src/def.rs");
71/// # }
72/// # use envman::EnvMan;
73///
74/// #[derive(EnvMan)]
75/// struct Foo {
76///   normal: i32,
77///   #[envman(rename = "renamed")]
78///   so_long_name: u8,
79///   #[envman(default = "default value")]
80///   default: String,
81///   wrapped: Option<i32>,
82///   #[envman(default = 1, test = 2)]
83///   test_value: u8,
84///   #[envman(nest)]
85///   nested: DbData,
86///   #[envman(separator = ",")]
87///   allowed_hosts: Vec<String>,
88/// }
89///
90/// #[derive(EnvMan, Default)]
91/// #[envman(prefix = "DB_")]
92/// struct DbData {
93///   url: String,
94/// }
95///
96/// #[allow(unused_unsafe)]
97/// unsafe {
98///     std::env::set_var("NORMAL", "1");
99///     // rename attribute is not affected by rename_all, prefix, and suffix
100///     std::env::set_var("renamed", "2");
101///     std::env::set_var("DB_URL", "url");
102///     std::env::set_var("ALLOWED_HOSTS", "host1,host2,host3");
103/// }
104///
105/// let foo = Foo::load_from_env().unwrap();
106/// assert_eq!(foo.normal, 1);
107/// assert_eq!(foo.so_long_name, 2);
108/// assert_eq!(foo.default, "default value");
109/// assert_eq!(foo.wrapped, None);
110/// // in doctest, cfg is doctest, not test
111/// assert_eq!(foo.test_value, 1);
112/// assert_eq!(foo.nested.url, "url");
113/// assert_eq!(foo.allowed_hosts, vec!["host1", "host2", "host3"]);
114/// ```
115#[proc_macro_derive(EnvMan, attributes(envman))]
116pub fn derive_envman(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
117    envman_derive_internals::derive_envman(parse_macro_input!(input as DeriveInput))
118        .unwrap_or_else(syn::Error::into_compile_error)
119        .into()
120}
121
122/// Derives Debug with masking support for fields marked with `#[envman(secret)]`
123///
124/// # Example
125/// ```rust
126/// # use envman_derive::{EnvMan, EnvManDebug};
127/// # mod envman {
128/// #   include!("../../envman/src/def.rs");
129/// # }
130/// # use envman::EnvMan;
131///
132/// #[derive(EnvMan, EnvManDebug)]
133/// struct Config {
134///     username: String,
135///     #[envman(secret)]
136///     password: String,
137///     #[envman(secret)]
138///     api_key: String,
139/// }
140///
141/// #[allow(unused_unsafe)]
142/// unsafe {
143///     std::env::set_var("USERNAME", "admin");
144///     std::env::set_var("PASSWORD", "secret123");
145///     std::env::set_var("API_KEY", "key_123456");
146/// }
147///
148/// let config = Config::load_from_env().unwrap();
149/// let debug_output = format!("{:?}", config);
150/// assert!(debug_output.contains("username: \"admin\""));
151/// assert!(debug_output.contains("password: \"***\""));
152/// assert!(debug_output.contains("api_key: \"***\""));
153/// ```
154#[proc_macro_derive(EnvManDebug, attributes(envman))]
155pub fn derive_envman_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
156    envman_derive_internals::derive_envman_debug(parse_macro_input!(input as DeriveInput))
157        .unwrap_or_else(syn::Error::into_compile_error)
158        .into()
159}