use crate::parse::ItemArgs;
extern crate proc_macro;
mod codegen;
mod ir;
mod parse;
#[proc_macro_attribute]
pub fn configuration(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = match parse::parse_configuration_attribute(attr.into()) {
Ok(value) => value,
Err(err) => return syn::Error::into_compile_error(err).into(),
};
match syn::parse::<syn::ItemStruct>(input.clone()) {
Ok(input) => derive_configuration_struct(args, input)
.unwrap_or_else(syn::Error::into_compile_error)
.into(),
Err(struct_err) => match syn::parse::<syn::ItemEnum>(input) {
Ok(input) => derive_configuration_enum(args, input)
.unwrap_or_else(syn::Error::into_compile_error)
.into(),
Err(_enum_err) => syn::Error::into_compile_error(struct_err).into(),
},
}
}
fn derive_configuration_struct(
args: ItemArgs,
input: syn::ItemStruct,
) -> syn::Result<proc_macro2::TokenStream> {
let syn::ItemStruct {
attrs,
generics,
ident,
struct_token,
vis,
fields,
semi_token: _,
} = input;
let field_definitions = match fields {
syn::Fields::Unit => vec![],
syn::Fields::Named(fields) => fields
.named
.into_iter()
.map(parse::parse_named_field)
.collect::<Result<Vec<_>, _>>()?,
syn::Fields::Unnamed(fields) => {
return Err(syn::Error::new_spanned(
fields,
"tuple structs are not yet supported",
));
}
};
let ir = ir::ConfigStructDefinition {
struct_token,
name: ident,
vis,
generics,
attrs,
fields: field_definitions,
validator: args.validator,
};
Ok(codegen::generate_configuration_struct(ir))
}
fn derive_configuration_enum(
args: ItemArgs,
input: syn::ItemEnum,
) -> syn::Result<proc_macro2::TokenStream> {
let syn::ItemEnum {
attrs,
enum_token,
generics,
ident,
vis,
variants,
brace_token: _,
} = input;
let (derive_helper_attrs, attrs): (Vec<_>, Vec<_>) = attrs
.into_iter()
.partition(|attr| attr.path().is_ident("serde") || attr.path().is_ident("schemars"));
let variant_definitions = variants
.into_iter()
.map(parse::parse_variant)
.collect::<Result<Vec<_>, _>>()?;
let default_variants: Vec<_> = variant_definitions
.iter()
.filter(|v| v.is_default)
.collect();
if default_variants.len() > 1 {
return Err(syn::Error::new(
default_variants[1].name.span(),
"only one variant can be marked as #[config(default)]",
));
}
if let Some(default_variant) = default_variants.first()
&& let ir::VariantFields::Struct(fields) = &default_variant.fields
{
for field in fields {
if let Some(required_ident) = &field.required {
return Err(syn::Error::new_spanned(
required_ident,
"cannot mark field as required in a #[config(default)] variant",
));
}
}
}
let ir = ir::ConfigEnumDefinition {
enum_token,
name: ident,
vis,
generics,
attrs,
derive_helper_attrs,
variants: variant_definitions,
validator: args.validator,
};
Ok(codegen::generate_configuration_enum(ir))
}
#[cfg(test)]
mod tests;