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