apollo-configuration-macros 0.1.1

Supporting macros for apollo-configuration (internal, do not use directly)
Documentation
use crate::parse::ItemArgs;

extern crate proc_macro;

mod codegen;
mod ir;
mod parse;

/// Turn a Rust structure or enum into a configuration structure.
///
/// See the `apollo-configuration` crate-level documentation for more.
#[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(),
            // TODO(@goto-bus-stop): pick the error to report based on the keyword that was used
            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;

    // Separate serde/schemars helper attributes from other attributes.
    // Helper attributes must come AFTER the derive that introduces them.
    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<_>, _>>()?;

    // Validate that at most one variant is marked as default
    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)]",
        ));
    }

    // Validate that default variants don't have required fields
    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;