#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme_doctests {}
mod contracts;
mod diagnostics;
mod source;
moddef::moddef!(
flat (pub) mod {
},
flat (pub(crate)) mod {
presentation,
state,
machine,
transition,
validators
}
);
pub(crate) use presentation::{
PresentationAttr, PresentationTypesAttr, parse_present_attrs, parse_presentation_types_attr,
strip_present_attrs,
};
pub(crate) use source::{
ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
extract_derives, source_file_fingerprint,
};
use crate::diagnostics::DiagnosticMessage;
use crate::source::{current_module_path_opt, module_path_for_span, source_info_for_span};
use proc_macro::TokenStream;
use proc_macro2::Span;
use syn::spanned::Spanned;
use syn::{Item, ItemImpl, parse_macro_input};
pub(crate) fn strict_introspection_enabled() -> bool {
cfg!(feature = "strict-introspection")
}
#[proc_macro_attribute]
pub fn state(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new(
Span::call_site(),
DiagnosticMessage::new("`#[state]` does not accept arguments.")
.found(format!("`#[state({attr})]`"))
.expected("`#[state] enum WorkflowState { Draft, Review(ReviewData) }`")
.fix("remove the attribute arguments and describe states with enum variants instead.".to_string())
.render(),
)
.to_compile_error()
.into();
}
let input = parse_macro_input!(item as Item);
let input = match input {
Item::Enum(item_enum) => item_enum,
other => return invalid_state_target_error(&other).into(),
};
expand_state(input).into()
}
#[proc_macro_attribute]
pub fn machine(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new(
Span::call_site(),
DiagnosticMessage::new("`#[machine]` does not accept arguments.")
.found(format!("`#[machine({attr})]`"))
.expected("`#[machine] struct WorkflowMachine<WorkflowState> { ... }`")
.fix("remove the attribute arguments and link the machine to `#[state]` through its first generic parameter.".to_string())
.render(),
)
.to_compile_error()
.into();
}
let input = parse_macro_input!(item as Item);
let input = match input {
Item::Struct(item_struct) => item_struct,
other => return invalid_machine_target_error(&other).into(),
};
expand_machine(input).into()
}
#[proc_macro_attribute]
pub fn transition(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
if !attr.is_empty() {
return syn::Error::new(
Span::call_site(),
DiagnosticMessage::new("`#[transition]` does not accept arguments.")
.found(format!("`#[transition({attr})]`"))
.expected("`#[transition] impl WorkflowMachine<Draft> { ... }`")
.fix("remove the attribute arguments and declare transition behavior with methods inside the impl block.".to_string())
.render(),
)
.to_compile_error()
.into();
}
let input = parse_macro_input!(item as ItemImpl);
expand_transition(input).into()
}
#[proc_macro_attribute]
pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
if attr.is_empty() {
return syn::Error::new(
Span::call_site(),
DiagnosticMessage::new("`#[validators(...)]` requires a machine path.")
.expected("`#[validators(WorkflowMachine)] impl PersistedRow { ... }`")
.fix("pass the target Statum machine type in the attribute, for example `#[validators(self::flow::WorkflowMachine)]`.".to_string())
.render(),
)
.to_compile_error()
.into();
}
let item_impl = parse_macro_input!(item as ItemImpl);
let module_path = match resolved_current_module_path(item_impl.self_ty.span(), "#[validators]")
{
Ok(path) => path,
Err(err) => return err,
};
parse_validators(attr, item_impl, &module_path)
}
pub(crate) fn resolved_current_module_path(
span: Span,
macro_name: &str,
) -> Result<String, TokenStream> {
let resolved = module_path_for_span(span)
.or_else(current_module_path_opt)
.or_else(|| {
source_info_for_span(span)
.is_none()
.then_some("crate".to_string())
});
resolved.ok_or_else(|| {
let message = format!(
"Internal error: could not resolve the module path for `{macro_name}` at this call site."
);
quote::quote_spanned! { span =>
compile_error!(#message);
}
.into()
})
}