actrpc-core-macros 0.1.0

Procedural macros for ActRPC core.
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Error, Fields, parse_macro_input};

use crate::shared::{
    extract_enum_variants, extract_named_struct_fields, field_name, value_descriptor_tokens,
};

pub fn expand(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let expanded = match expand_impl(&input) {
        Ok(tokens) => tokens,
        Err(err) => err.to_compile_error(),
    };

    TokenStream::from(expanded)
}

fn expand_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let ident = &input.ident;

    let body = match &input.data {
        Data::Struct(_) => describe_struct_value(input)?,
        Data::Enum(_) => describe_enum_value(input)?,
        Data::Union(_) => {
            return Err(Error::new_spanned(input, "unions are not supported"));
        }
    };

    Ok(quote! {
        impl ::actrpc_core::descriptor::traits::DescribeValue for #ident {
            fn describe_value() -> ::actrpc_core::descriptor::types::ValueDescriptor {
                #body
            }
        }
    })
}

fn describe_struct_value(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let fields = extract_named_struct_fields(input)?;

    let field_tokens = fields
        .named
        .iter()
        .map(|field| {
            let name = field_name(field)?;
            let ty = value_descriptor_tokens(&field.ty, false)?;

            Ok(quote! {
                ::actrpc_core::descriptor::types::FieldDescriptor {
                    name: #name.to_string(),
                    ty: #ty,
                }
            })
        })
        .collect::<syn::Result<Vec<_>>>()?;

    Ok(quote! {
        ::actrpc_core::descriptor::types::ValueDescriptor::Object(
            ::actrpc_core::descriptor::types::NestedObjectDescriptor {
                fields: vec![#(#field_tokens),*],
            }
        )
    })
}

fn describe_enum_value(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let variants = extract_enum_variants(input)?;

    if variants.is_empty() {
        return Err(Error::new_spanned(
            input,
            "DescribeValue does not support empty enums",
        ));
    }

    let variant_tokens = variants
        .iter()
        .map(|variant| match &variant.fields {
            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
                let ty = &fields.unnamed.first().expect("len checked").ty;
                value_descriptor_tokens(ty, false)
            }
            Fields::Named(_) => Err(Error::new_spanned(
                variant,
                "enum variants with named fields are not supported by DescribeValue; use a separate struct type per variant",
            )),
            Fields::Unnamed(_) => Err(Error::new_spanned(
                variant,
                "enum variants must have exactly one unnamed field",
            )),
            Fields::Unit => Err(Error::new_spanned(
                variant,
                "unit variants are not supported by DescribeValue",
            )),
        })
        .collect::<syn::Result<Vec<_>>>()?;

    Ok(quote! {
        ::actrpc_core::descriptor::types::ValueDescriptor::OneOf(
            vec![#(#variant_tokens),*]
        )
    })
}