use syn::{Data, DeriveInput, Expr, Fields, LitStr, Token};
#[derive(Clone)]
pub(crate) enum DefaultKind {
Inherit,
Explicit(Expr),
}
pub(crate) struct FieldOpts {
pub(crate) ident: syn::Ident,
pub(crate) ty: syn::Type,
pub(crate) name: Option<String>,
pub(crate) default: Option<DefaultKind>,
pub(crate) example: Option<Expr>,
pub(crate) auto_vec: bool,
pub(crate) auto_vec_delimiter: Option<String>,
pub(crate) humantime: bool,
pub(crate) attrs: Vec<syn::Attribute>,
}
pub(crate) struct InputOpts {
pub(crate) ident: syn::Ident,
pub(crate) generics: syn::Generics,
pub(crate) fields: Vec<FieldOpts>,
pub(crate) prefix: Option<String>,
}
impl InputOpts {
pub(crate) fn parse(input: &DeriveInput) -> syn::Result<Self> {
let mut prefix = None;
for attr in input
.attrs
.iter()
.filter(|attr| attr.path().is_ident("confroid"))
{
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("prefix") {
set_once(&mut prefix, parse_string(&meta)?, &meta, "prefix")
} else {
Err(meta.error("unsupported container attribute"))
}
})?;
}
let Data::Struct(data) = &input.data else {
return Err(syn::Error::new_spanned(
input,
"`Config` can only be derived for a struct with named fields",
));
};
let Fields::Named(fields) = &data.fields else {
return Err(syn::Error::new_spanned(
&data.fields,
"`Config` requires named fields",
));
};
let fields = fields
.named
.iter()
.map(FieldOpts::parse)
.collect::<syn::Result<_>>()?;
Ok(Self {
ident: input.ident.clone(),
generics: input.generics.clone(),
fields,
prefix,
})
}
}
impl FieldOpts {
fn parse(field: &syn::Field) -> syn::Result<Self> {
let mut opts = Self {
ident: field.ident.clone().expect("named fields have identifiers"),
ty: field.ty.clone(),
name: None,
default: None,
example: None,
auto_vec: false,
auto_vec_delimiter: None,
humantime: false,
attrs: field.attrs.clone(),
};
for attr in field
.attrs
.iter()
.filter(|attr| attr.path().is_ident("confroid"))
{
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value = parse_string(&meta)?;
set_once(&mut opts.name, value, &meta, "name")
} else if meta.path.is_ident("default") {
let value = if meta.input.peek(Token![=]) {
DefaultKind::Explicit(meta.value()?.parse()?)
} else {
DefaultKind::Inherit
};
set_once(&mut opts.default, value, &meta, "default")
} else if meta.path.is_ident("example") {
let value = meta.value()?.parse()?;
set_once(&mut opts.example, value, &meta, "example")
} else if meta.path.is_ident("auto_vec") {
set_flag(&mut opts.auto_vec, &meta, "auto_vec")
} else if meta.path.is_ident("auto_vec_delimiter") {
let value = parse_string(&meta)?;
set_once(
&mut opts.auto_vec_delimiter,
value,
&meta,
"auto_vec_delimiter",
)
} else if meta.path.is_ident("humantime") {
set_flag(&mut opts.humantime, &meta, "humantime")
} else {
Err(meta.error("unsupported field attribute"))
}
})?;
}
Ok(opts)
}
}
fn parse_string(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<String> {
Ok(meta.value()?.parse::<LitStr>()?.value())
}
fn set_once<T>(
slot: &mut Option<T>,
value: T,
meta: &syn::meta::ParseNestedMeta<'_>,
name: &str,
) -> syn::Result<()> {
if slot.replace(value).is_some() {
Err(meta.error(format!("duplicate `{name}` attribute")))
} else {
Ok(())
}
}
fn set_flag(flag: &mut bool, meta: &syn::meta::ParseNestedMeta<'_>, name: &str) -> syn::Result<()> {
if *flag {
Err(meta.error(format!("duplicate `{name}` attribute")))
} else {
*flag = true;
Ok(())
}
}