1#[cfg(doctest)]
16#[doc = include_str!("../README.md")]
17mod readme_doctests {}
18
19mod contracts;
20mod diagnostics;
21mod machine;
22mod presentation;
23mod source;
24mod state;
25mod transition;
26mod validators;
27
28pub(crate) use machine::{
29 LoadedMachineLookupFailure, MachineInfo, MachinePath, expand_machine,
30 format_loaded_machine_candidates, invalid_machine_target_error,
31 lookup_loaded_machine_in_module, lookup_unique_loaded_machine_by_name,
32};
33pub(crate) use state::{
34 EnumInfo, LoadedStateLookupFailure, StateModulePath, VariantInfo, VariantShape, expand_state,
35 format_loaded_state_candidates, invalid_state_target_error, lookup_loaded_state_enum,
36 lookup_loaded_state_enum_by_name, to_snake_case,
37};
38pub(crate) use transition::expand_transition;
39pub(crate) use validators::parse_validators;
40
41pub(crate) use presentation::{
42 PresentationAttr, PresentationTypesAttr, parse_present_attrs_for,
43 parse_presentation_types_attr, strip_present_attrs,
44};
45pub(crate) use source::{
46 ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
47 extract_derives, source_file_fingerprint,
48};
49
50use crate::diagnostics::DiagnosticMessage;
51use crate::source::{current_module_path_opt, module_path_for_span, source_info_for_span};
52use proc_macro::TokenStream;
53use proc_macro2::Span;
54use syn::spanned::Spanned;
55use syn::{Item, ItemImpl, parse_macro_input};
56
57pub(crate) fn strict_introspection_enabled() -> bool {
58 cfg!(feature = "strict-introspection")
59}
60
61#[proc_macro_attribute]
68pub fn state(attr: TokenStream, item: TokenStream) -> TokenStream {
69 if !attr.is_empty() {
70 return syn::Error::new(
71 Span::call_site(),
72 DiagnosticMessage::new("`#[state]` does not accept arguments.")
73 .found(format!("`#[state({attr})]`"))
74 .expected("`#[state] enum WorkflowState { Draft, Review(ReviewData) }`")
75 .fix("remove the attribute arguments and describe states with enum variants instead.".to_string())
76 .render(),
77 )
78 .to_compile_error()
79 .into();
80 }
81 let input = parse_macro_input!(item as Item);
82 let input = match input {
83 Item::Enum(item_enum) => item_enum,
84 other => return invalid_state_target_error(&other).into(),
85 };
86 expand_state(input).into()
87}
88
89#[proc_macro_attribute]
98pub fn machine(attr: TokenStream, item: TokenStream) -> TokenStream {
99 if !attr.is_empty() {
100 return syn::Error::new(
101 Span::call_site(),
102 DiagnosticMessage::new("`#[machine]` does not accept arguments.")
103 .found(format!("`#[machine({attr})]`"))
104 .expected("`#[machine] struct WorkflowMachine<WorkflowState> { ... }`")
105 .fix("remove the attribute arguments and link the machine to `#[state]` through its first generic parameter.".to_string())
106 .render(),
107 )
108 .to_compile_error()
109 .into();
110 }
111 let input = parse_macro_input!(item as Item);
112 let input = match input {
113 Item::Struct(item_struct) => item_struct,
114 other => return invalid_machine_target_error(&other).into(),
115 };
116 expand_machine(input).into()
117}
118
119#[proc_macro_attribute]
132pub fn transition(
133 attr: proc_macro::TokenStream,
134 item: proc_macro::TokenStream,
135) -> proc_macro::TokenStream {
136 if !attr.is_empty() {
137 return syn::Error::new(
138 Span::call_site(),
139 DiagnosticMessage::new("`#[transition]` does not accept arguments.")
140 .found(format!("`#[transition({attr})]`"))
141 .expected("`#[transition] impl WorkflowMachine<Draft> { ... }`")
142 .fix("remove the attribute arguments and declare transition behavior with methods inside the impl block.".to_string())
143 .render(),
144 )
145 .to_compile_error()
146 .into();
147 }
148 let input = parse_macro_input!(item as ItemImpl);
149 expand_transition(input).into()
150}
151
152#[proc_macro_attribute]
168pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
169 if attr.is_empty() {
170 return syn::Error::new(
171 Span::call_site(),
172 DiagnosticMessage::new("`#[validators(...)]` requires a machine path.")
173 .expected("`#[validators(WorkflowMachine)] impl PersistedRow { ... }`")
174 .fix("pass the target Statum machine type in the attribute, for example `#[validators(self::flow::WorkflowMachine)]`.".to_string())
175 .render(),
176 )
177 .to_compile_error()
178 .into();
179 }
180 let item_impl = parse_macro_input!(item as ItemImpl);
181 let module_path = match resolved_current_module_path(item_impl.self_ty.span(), "#[validators]")
182 {
183 Ok(path) => path,
184 Err(err) => return err,
185 };
186 parse_validators(attr, item_impl, &module_path)
187}
188
189pub(crate) fn resolved_current_module_path(
190 span: Span,
191 macro_name: &str,
192) -> Result<String, TokenStream> {
193 let resolved = module_path_for_span(span)
194 .or_else(current_module_path_opt)
195 .or_else(|| {
196 source_info_for_span(span)
197 .is_none()
198 .then_some("crate".to_string())
199 });
200
201 resolved.ok_or_else(|| {
202 let message = format!(
203 "Internal error: could not resolve the module path for `{macro_name}` at this call site."
204 );
205 quote::quote_spanned! { span =>
206 compile_error!(#message);
207 }
208 .into()
209 })
210}