use darling::{
Error, FromDeriveInput, FromField, FromMeta,
ast::{Data, Fields},
util::Ignored,
};
use syn::{Attribute, Expr, Ident, Type};
use crate::TypeExt;
#[derive(FromDeriveInput)]
#[darling(attributes(config), supports(struct_named), and_then = ConfigParser::autocorrect)]
pub struct ConfigParser {
ident: Ident,
data: Data<Ignored, ConfigParserField>,
#[darling(default)]
skip_all: bool,
}
impl ConfigParser {
pub fn autocorrect(mut self) -> Result<Self, Error> {
let Data::Struct(Fields { fields, .. }) = &mut self.data else {
return Err(
Error::custom("`ConfigParser` can only be used with structs")
.with_span(&self.ident),
);
};
let mut path_field = None;
for field in fields.iter_mut() {
field.skip = field.skip || self.skip_all || field.path;
if field.ident.is_none() {
return Err(Error::custom("Tuple fields are not supported").with_span(&field.ty));
}
if field.path {
if path_field.is_some() {
return Err(Error::custom(
"`#[config(path)]` can only be applied to one field",
)
.with_span(&field.ident));
}
if !(field.ty.is("String") || field.ty.is_option_of("String")) {
return Err(Error::custom(
"`#[config(path)]` requires a field of type `String` or `Option<String>`",
)
.with_span(&field.ident));
}
path_field = Some(field);
}
}
Ok(self)
}
pub fn ident(&self) -> &Ident {
&self.ident
}
pub fn fields(&self) -> impl Iterator<Item = &ConfigParserField> {
match &self.data {
Data::Struct(Fields { fields, .. }) => fields.iter(),
_ => unreachable!(),
}
}
}
#[derive(FromField)]
#[darling(attributes(config), forward_attrs(arg, command))]
pub struct ConfigParserField {
ident: Option<Ident>,
ty: Type,
attrs: Vec<Attribute>,
#[darling(default)]
skip: bool,
#[darling(default)]
path: bool,
#[darling(default)]
format: Option<ConfigFormat>,
#[darling(default)]
pub value_format: Option<Expr>,
}
impl ConfigParserField {
pub fn ident(&self) -> &Ident {
self.ident.as_ref().unwrap()
}
pub fn ty(&self) -> &Type {
&self.ty
}
pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
self.attrs.iter()
}
pub fn is_path(&self) -> bool {
self.path
}
pub fn is_skipped(&self) -> bool {
self.skip
}
pub fn format(&self) -> Option<ConfigFormat> {
self.format
}
pub fn value_format(&self) -> Option<&Expr> {
self.value_format.as_ref()
}
}
#[derive(Copy, Clone, Default, FromMeta)]
pub enum ConfigFormat {
Toml,
Yaml,
Json,
#[default]
Auto,
}