statum-macros 0.9.0

Proc macros for representing legal workflow and protocol states explicitly in Rust
Documentation
use quote::quote;
use syn::{Generics, Ident, Type};

use super::shared::{
    SlotSetterContext, field_binding_tokens, field_storage_tokens, slot_setter_impls,
    slot_state_idents, slot_storage_idents,
};
use crate::machine::{builder_generics, extra_generics, generic_argument_tokens};

pub(super) struct IntoMachineBuilderContext<'a> {
    pub(super) machine_ident: &'a Ident,
    pub(super) candidate_states: &'a [proc_macro2::TokenStream],
    pub(super) builder_ident: &'a Ident,
    pub(super) struct_ident: &'a Type,
    pub(super) machine_generics: &'a Generics,
    pub(super) machine_state_ty: &'a proc_macro2::TokenStream,
    pub(super) field_names: &'a [Ident],
    pub(super) field_types: &'a [Type],
    pub(super) validator_checks: &'a [proc_macro2::TokenStream],
    pub(super) validator_report_checks: &'a [proc_macro2::TokenStream],
    pub(super) validator_explain_checks: &'a [proc_macro2::TokenStream],
    pub(super) validator_explain_storages: &'a [proc_macro2::TokenStream],
    pub(super) validator_explain_finalizers: &'a [proc_macro2::TokenStream],
    pub(super) async_token: &'a proc_macro2::TokenStream,
    pub(super) machine_vis: &'a syn::Visibility,
}

pub(super) fn generate_into_machine_builder(
    context: IntoMachineBuilderContext<'_>,
) -> proc_macro2::TokenStream {
    let machine_ident = context.machine_ident;
    let candidate_states = context.candidate_states;
    let builder_ident = context.builder_ident;
    let struct_ident = context.struct_ident;
    let machine_generics = context.machine_generics;
    let machine_state_ty = context.machine_state_ty;
    let field_names = context.field_names;
    let field_types = context.field_types;
    let validator_checks = context.validator_checks;
    let validator_report_checks = context.validator_report_checks;
    let validator_explain_checks = context.validator_explain_checks;
    let validator_explain_storages = context.validator_explain_storages;
    let validator_explain_finalizers = context.validator_explain_finalizers;
    let validator_report_count = validator_report_checks.len();
    let async_token = context.async_token;
    let machine_vis = context.machine_vis;
    let extra_machine_generics = extra_generics(machine_generics);
    let slot_storage_idents = slot_storage_idents(field_names.len());
    let slot_state_idents = slot_state_idents(field_names.len());
    let builder_defaults =
        builder_generics(&extra_machine_generics, true, &slot_state_idents, true);
    let complete_slots = slot_state_idents
        .iter()
        .map(|_| quote! { true })
        .collect::<Vec<_>>();
    let complete_builder_ty_generics = generic_argument_tokens(
        extra_machine_generics.params.iter(),
        Some(quote! { '__statum_row }),
        &complete_slots,
    );
    let complete_builder_impl_generics_decl =
        builder_generics(&extra_machine_generics, true, &[], false);
    let (complete_builder_impl_generics, _, complete_builder_where_clause) =
        complete_builder_impl_generics_decl.split_for_impl();

    let struct_fields = field_storage_tokens(&slot_storage_idents, field_types);
    let field_bindings = field_binding_tokens(field_names, &slot_storage_idents);
    let setters = slot_setter_impls(
        SlotSetterContext {
            builder_ident,
            machine_vis,
            extra_machine_generics: &extra_machine_generics,
            field_names,
            field_types,
            slot_state_idents: &slot_state_idents,
            slot_storage_idents: &slot_storage_idents,
            row_lifetime: Some(quote! { '__statum_row }),
        },
        |assignments| {
            quote! {
                #builder_ident {
                    __statum_item: self.__statum_item,
                    #(#assignments),*
                }
            }
        },
    );
    let report_methods = if cfg!(feature = "rebuild-reports") {
        quote! {
            #machine_vis #async_token fn build_report(self) -> statum::RebuildReport<#machine_state_ty> {
                let __statum_persisted = self.__statum_item;
                let mut __statum_attempts = ::std::vec::Vec::with_capacity(#validator_report_count);
                #(#field_bindings)*
                #(#validator_report_checks)*

                statum::RebuildReport::new(
                    stringify!(#machine_ident),
                    statum::__private::RebuildInput {
                        type_name: core::any::type_name::<#struct_ident>(),
                        identifier: core::option::Option::None,
                    },
                    ::std::vec![#(#candidate_states),*],
                    statum::__private::RebuildAmbiguity::NotChecked,
                    __statum_attempts,
                    Err(statum::Error::InvalidState),
                )
            }

            #machine_vis #async_token fn explain(self) -> statum::RebuildReport<#machine_state_ty> {
                let __statum_persisted = self.__statum_item;
                let mut __statum_attempts = ::std::vec::Vec::with_capacity(#validator_report_count);
                let mut __statum_matched_states = ::std::vec::Vec::new();
                #(#field_bindings)*
                #(#validator_explain_storages)*
                #(#validator_explain_checks)*

                let __statum_result = match __statum_matched_states.as_slice() {
                    #(#validator_explain_finalizers,)*
                    _ => Err(statum::Error::InvalidState),
                };
                let __statum_ambiguity = if __statum_matched_states.len() > 1 {
                    statum::__private::RebuildAmbiguity::Ambiguous {
                        matched_states: __statum_matched_states,
                    }
                } else {
                    statum::__private::RebuildAmbiguity::Unambiguous
                };

                statum::RebuildReport::new(
                    stringify!(#machine_ident),
                    statum::__private::RebuildInput {
                        type_name: core::any::type_name::<#struct_ident>(),
                        identifier: core::option::Option::None,
                    },
                    ::std::vec![#(#candidate_states),*],
                    __statum_ambiguity,
                    __statum_attempts,
                    __statum_result,
                )
            }
        }
    } else {
        quote! {}
    };

    quote! {
        #[doc(hidden)]
        #machine_vis struct #builder_ident #builder_defaults {
            __statum_item: &'__statum_row #struct_ident,
            #(#struct_fields),*
        }

        #(#setters)*

        impl #complete_builder_impl_generics #builder_ident #complete_builder_ty_generics #complete_builder_where_clause {
            #machine_vis #async_token fn build(self) -> core::result::Result<#machine_state_ty, statum::Error> {
                let __statum_persisted = self.__statum_item;
                #(#field_bindings)*
                #(#validator_checks)*

                Err(statum::Error::InvalidState)
            }

            #report_methods

        }
    }
}