#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![warn(clippy::all)]
mod config_api;
mod config_form;
mod config_store;
mod utils;
use proc_macro::TokenStream;
use syn::parse_macro_input;
#[proc_macro_derive(
WifiCaddyConfig,
attributes(config_store, config_form, config_server, config_notify, config_ui)
)]
pub fn derive_wifi_caddy_config(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let store = config_store::derive_config_store_impl(&input);
let form = config_form::derive_config_form_impl(&input);
let group = config_api::derive_config_api_impl(&input);
proc_macro::TokenStream::from(quote::quote! {
#store
#form
#group
})
}
#[cfg(test)]
mod tests {
use syn::parse_str;
#[test]
fn config_form_password_recognized_after_fieldset_and_help() {
let input: syn::DeriveInput = parse_str(
r#"
struct S {
#[config_form(fieldset = "WiFi", input_type = "password", help = "Secret")]
wifi_pass: String,
}
"#,
)
.unwrap();
let syn::Data::Struct(data) = &input.data else {
panic!("expected struct");
};
let field = data.fields.iter().next().unwrap();
let attr = field
.attrs
.iter()
.find(|a| a.path().is_ident("config_form"))
.unwrap();
let mut input_type = String::from("text");
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("input_type") {
if let Ok(lit) = meta.value().and_then(|v| v.parse::<syn::LitStr>()) {
input_type = lit.value();
}
} else {
let _ = meta.value().and_then(|v| v.parse::<syn::Expr>());
}
Ok(())
});
assert_eq!(
input_type, "password",
"input_type must be recognized so GET /config-group/main redacts the field"
);
}
}