@temporalio/core-bridge 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/Cargo.lock +172 -70
  2. package/lib/native.d.ts +1 -1
  3. package/package.json +2 -2
  4. package/releases/aarch64-apple-darwin/index.node +0 -0
  5. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  6. package/releases/x86_64-apple-darwin/index.node +0 -0
  7. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  8. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  9. package/sdk-core/.github/workflows/per-pr.yml +6 -6
  10. package/sdk-core/AGENTS.md +41 -30
  11. package/sdk-core/Cargo.toml +3 -0
  12. package/sdk-core/README.md +15 -9
  13. package/sdk-core/crates/client/Cargo.toml +4 -0
  14. package/sdk-core/crates/client/README.md +139 -0
  15. package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
  16. package/sdk-core/crates/client/src/callback_based.rs +7 -0
  17. package/sdk-core/crates/client/src/errors.rs +294 -0
  18. package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +280 -159
  19. package/sdk-core/crates/client/src/lib.rs +920 -1326
  20. package/sdk-core/crates/client/src/metrics.rs +24 -33
  21. package/sdk-core/crates/client/src/options_structs.rs +457 -0
  22. package/sdk-core/crates/client/src/replaceable.rs +5 -4
  23. package/sdk-core/crates/client/src/request_extensions.rs +8 -9
  24. package/sdk-core/crates/client/src/retry.rs +99 -54
  25. package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +1 -1
  26. package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
  27. package/sdk-core/crates/common/Cargo.toml +61 -2
  28. package/sdk-core/crates/common/build.rs +742 -12
  29. package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
  30. package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
  31. package/sdk-core/crates/common/protos/api_upstream/buf.yaml +0 -3
  32. package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
  33. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +1166 -770
  34. package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +1243 -750
  35. package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +2 -2
  36. package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -3
  37. package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
  38. package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +4 -0
  39. package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  40. package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +16 -1
  41. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -6
  42. package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +88 -33
  43. package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +4 -2
  44. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  45. package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +5 -5
  46. package/sdk-core/crates/common/src/activity_definition.rs +20 -0
  47. package/sdk-core/crates/common/src/data_converters.rs +770 -0
  48. package/sdk-core/crates/common/src/envconfig.rs +5 -0
  49. package/sdk-core/crates/common/src/lib.rs +15 -211
  50. package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
  51. package/sdk-core/crates/common/src/priority.rs +110 -0
  52. package/sdk-core/crates/common/src/protos/canned_histories.rs +3 -0
  53. package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
  54. package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
  55. package/sdk-core/crates/common/src/protos/mod.rs +122 -27
  56. package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
  57. package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
  58. package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +5 -7
  59. package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
  60. package/sdk-core/crates/common/src/telemetry/metrics.rs +268 -223
  61. package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
  62. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
  63. package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
  64. package/sdk-core/crates/common/src/telemetry.rs +264 -4
  65. package/sdk-core/crates/common/src/worker.rs +68 -603
  66. package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
  67. package/sdk-core/crates/macros/Cargo.toml +5 -1
  68. package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
  69. package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
  70. package/sdk-core/crates/macros/src/lib.rs +138 -512
  71. package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
  72. package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
  73. package/sdk-core/crates/sdk/Cargo.toml +19 -6
  74. package/sdk-core/crates/sdk/README.md +415 -0
  75. package/sdk-core/crates/sdk/src/activities.rs +417 -0
  76. package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
  77. package/sdk-core/crates/sdk/src/lib.rs +757 -442
  78. package/sdk-core/crates/sdk/src/workflow_context/options.rs +45 -35
  79. package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
  80. package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
  81. package/sdk-core/crates/sdk/src/workflows.rs +711 -0
  82. package/sdk-core/crates/sdk-core/Cargo.toml +57 -64
  83. package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +41 -35
  84. package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
  85. package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
  86. package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
  87. package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +13 -15
  88. package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
  89. package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
  90. package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
  91. package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +493 -26
  92. package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +4 -8
  93. package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +7 -7
  94. package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
  95. package/sdk-core/crates/sdk-core/src/lib.rs +41 -111
  96. package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
  97. package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +118 -19
  98. package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
  99. package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
  100. package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +179 -196
  101. package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -280
  102. package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +6 -9
  103. package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
  104. package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
  105. package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
  106. package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +9 -5
  107. package/sdk-core/crates/sdk-core/src/worker/client.rs +103 -81
  108. package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +7 -11
  109. package/sdk-core/crates/sdk-core/src/worker/mod.rs +1124 -229
  110. package/sdk-core/crates/sdk-core/src/worker/nexus.rs +145 -23
  111. package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
  112. package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
  113. package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +13 -13
  114. package/sdk-core/crates/sdk-core/src/worker/tuner.rs +28 -8
  115. package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
  116. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
  117. package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +16 -3
  118. package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +14 -18
  119. package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +4 -6
  120. package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
  121. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
  122. package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
  123. package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
  124. package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
  125. package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
  126. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
  127. package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
  128. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
  129. package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
  130. package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
  131. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
  132. package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
  133. package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
  134. package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
  135. package/sdk-core/crates/sdk-core/tests/common/mod.rs +241 -196
  136. package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
  137. package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +3 -5
  138. package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -64
  139. package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +298 -252
  140. package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
  141. package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
  142. package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
  143. package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +16 -12
  144. package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +48 -40
  145. package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +327 -255
  146. package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
  147. package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +147 -126
  148. package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
  149. package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -453
  150. package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
  151. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +360 -231
  152. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +248 -185
  153. package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -43
  154. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
  155. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +428 -315
  156. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -56
  157. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -28
  158. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -243
  159. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
  160. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +101 -42
  161. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -147
  162. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -28
  163. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1036
  164. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -41
  165. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +397 -238
  166. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +340 -188
  167. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
  168. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
  169. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +154 -137
  170. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -105
  171. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -38
  172. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -40
  173. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -54
  174. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +363 -226
  175. package/sdk-core/crates/sdk-core/tests/main.rs +17 -15
  176. package/sdk-core/crates/sdk-core/tests/manual_tests.rs +207 -152
  177. package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +65 -34
  178. package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
  179. package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
  180. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
  181. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
  182. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
  183. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
  184. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
  185. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
  186. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
  187. package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
  188. package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +7 -1
  189. package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +14 -14
  190. package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +83 -74
  191. package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +9 -14
  192. package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +1 -2
  193. package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +13 -13
  194. package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +6 -6
  195. package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +3 -4
  196. package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +62 -75
  197. package/sdk-core/rustfmt.toml +2 -1
  198. package/src/client.rs +205 -318
  199. package/src/metrics.rs +22 -30
  200. package/src/runtime.rs +4 -5
  201. package/src/worker.rs +15 -18
  202. package/ts/native.ts +1 -1
  203. package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
  204. package/sdk-core/crates/common/src/errors.rs +0 -85
  205. package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
  206. package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
  207. package/sdk-core/crates/sdk/src/app_data.rs +0 -37
  208. package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
  209. package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
@@ -0,0 +1,1224 @@
1
+ use crate::macro_utils::{
2
+ generate_const_definition, generate_marker_struct, method_name_to_pascal_case,
3
+ type_name_string, type_to_snake_case,
4
+ };
5
+ use proc_macro::TokenStream;
6
+ use proc_macro2::TokenStream as TokenStream2;
7
+ use quote::{format_ident, quote};
8
+ use syn::{
9
+ Attribute, FnArg, ImplItem, ItemImpl, ReturnType, Type, TypePath,
10
+ parse::{Parse, ParseStream},
11
+ };
12
+
13
+ /// Convert an optional type to a quoted token stream, defaulting to `()` if None.
14
+ fn type_to_tokens(ty: Option<&Type>) -> TokenStream2 {
15
+ ty.map(|t| quote! { #t }).unwrap_or(quote! { () })
16
+ }
17
+
18
+ /// Common data extracted from handler methods (signal, query, update)
19
+ struct HandlerCodegenInfo {
20
+ struct_ident: syn::Ident,
21
+ prefixed_method: syn::Ident,
22
+ input_type_tokens: TokenStream2,
23
+ handler_name: TokenStream2,
24
+ marker_struct: TokenStream2,
25
+ const_def: TokenStream2,
26
+ }
27
+
28
+ impl HandlerCodegenInfo {
29
+ fn new(
30
+ method: &syn::ImplItemFn,
31
+ attributes: &MethodAttributes,
32
+ input_type: Option<&Type>,
33
+ module_ident: &syn::Ident,
34
+ ) -> Self {
35
+ let struct_name = method_name_to_pascal_case(&method.sig.ident);
36
+ let struct_ident = format_ident!("{}", struct_name);
37
+ let prefixed_method = format_ident!("__{}", method.sig.ident);
38
+ let input_type_tokens = type_to_tokens(input_type);
39
+ let handler_name = {
40
+ let method_ident: &syn::Ident = &method.sig.ident;
41
+ if let Some(ref name_expr) = attributes.name_override {
42
+ quote! { #name_expr }
43
+ } else {
44
+ let name = method_ident.to_string();
45
+ quote! { #name }
46
+ }
47
+ };
48
+ let marker_struct = generate_marker_struct(method);
49
+ let const_def = generate_const_definition(method, module_ident);
50
+
51
+ Self {
52
+ struct_ident,
53
+ prefixed_method,
54
+ input_type_tokens,
55
+ handler_name,
56
+ marker_struct,
57
+ const_def,
58
+ }
59
+ }
60
+ }
61
+
62
+ /// Generate a method call with or without input parameter (for sync methods with &mut self)
63
+ fn generate_method_call(prefixed_method: &syn::Ident, has_input: bool) -> TokenStream2 {
64
+ if has_input {
65
+ quote! { self.#prefixed_method(ctx, input) }
66
+ } else {
67
+ quote! { self.#prefixed_method(ctx) }
68
+ }
69
+ }
70
+
71
+ /// Generate an async handler body. The async move block is required because the underlying
72
+ /// method borrows ctx, and we need to move ctx into the block to make the future 'static.
73
+ fn generate_async_handler_body(
74
+ impl_type: &Type,
75
+ prefixed_method: &syn::Ident,
76
+ has_input: bool,
77
+ ) -> TokenStream2 {
78
+ let method_call = if has_input {
79
+ quote! { #impl_type::#prefixed_method(&mut ctx, input) }
80
+ } else {
81
+ quote! { #impl_type::#prefixed_method(&mut ctx) }
82
+ };
83
+ quote! { async move { #method_call.await }.boxed_local() }
84
+ }
85
+
86
+ /// Generate an async update handler body that returns Result.
87
+ /// If is_fallible, the method already returns Result; otherwise wrap in Ok via .map().
88
+ fn generate_async_update_handler_body(
89
+ impl_type: &Type,
90
+ prefixed_method: &syn::Ident,
91
+ has_input: bool,
92
+ is_fallible: bool,
93
+ ) -> TokenStream2 {
94
+ let method_call = if has_input {
95
+ quote! { #impl_type::#prefixed_method(&mut ctx, input) }
96
+ } else {
97
+ quote! { #impl_type::#prefixed_method(&mut ctx) }
98
+ };
99
+ if is_fallible {
100
+ quote! { async move { #method_call.await }.boxed_local() }
101
+ } else {
102
+ quote! { async move { #method_call.await }.map(Ok).boxed_local() }
103
+ }
104
+ }
105
+
106
+ /// Parsed representation of a `#[workflow_methods]` impl block
107
+ pub(crate) struct WorkflowMethodsDefinition {
108
+ impl_block: ItemImpl,
109
+ init_method: Option<InitMethod>,
110
+ run_method: RunMethod,
111
+ signals: Vec<SignalMethod>,
112
+ queries: Vec<QueryMethod>,
113
+ updates: Vec<UpdateMethod>,
114
+ }
115
+
116
+ #[derive(Default)]
117
+ struct MethodAttributes {
118
+ name_override: Option<syn::Expr>,
119
+ }
120
+
121
+ struct InitMethod {
122
+ method: syn::ImplItemFn,
123
+ input_type: Option<Type>,
124
+ }
125
+
126
+ struct RunMethod {
127
+ method: syn::ImplItemFn,
128
+ attributes: MethodAttributes,
129
+ input_type: Option<Type>,
130
+ output_type: Option<Type>,
131
+ }
132
+
133
+ struct SignalMethod {
134
+ method: syn::ImplItemFn,
135
+ attributes: MethodAttributes,
136
+ is_async: bool,
137
+ input_type: Option<Type>,
138
+ }
139
+
140
+ struct QueryMethod {
141
+ method: syn::ImplItemFn,
142
+ attributes: MethodAttributes,
143
+ input_type: Option<Type>,
144
+ output_type: Option<Type>,
145
+ is_fallible: bool,
146
+ }
147
+
148
+ struct UpdateMethod {
149
+ method: syn::ImplItemFn,
150
+ attributes: MethodAttributes,
151
+ is_async: bool,
152
+ input_type: Option<Type>,
153
+ /// The Ok type if fallible, or the direct return type if infallible
154
+ output_type: Option<Type>,
155
+ /// True if the handler returns Result<T, E>, false if it returns T directly
156
+ is_fallible: bool,
157
+ validator: Option<syn::Ident>,
158
+ }
159
+
160
+ struct ValidatorMethod {
161
+ method: syn::ImplItemFn,
162
+ update_name: syn::Ident,
163
+ }
164
+
165
+ impl Parse for WorkflowMethodsDefinition {
166
+ fn parse(input: ParseStream) -> syn::Result<Self> {
167
+ let impl_block: ItemImpl = input.parse()?;
168
+ let mut init_method = None;
169
+ let mut run_method = None;
170
+ let mut signals = Vec::new();
171
+ let mut queries = Vec::new();
172
+ let mut updates = Vec::new();
173
+ let mut validators = Vec::new();
174
+
175
+ for item in &impl_block.items {
176
+ if let ImplItem::Fn(method) = item {
177
+ let has_init = has_attr(&method.attrs, "init");
178
+ let has_run = has_attr(&method.attrs, "run");
179
+ let has_signal = has_attr(&method.attrs, "signal");
180
+ let has_query = has_attr(&method.attrs, "query");
181
+ let has_update = has_attr(&method.attrs, "update");
182
+ let has_update_validator = has_attr(&method.attrs, "update_validator");
183
+
184
+ // Count how many workflow attributes are present
185
+ let attr_count = [
186
+ has_init,
187
+ has_run,
188
+ has_signal,
189
+ has_query,
190
+ has_update,
191
+ has_update_validator,
192
+ ]
193
+ .iter()
194
+ .filter(|&&b| b)
195
+ .count();
196
+
197
+ if attr_count > 1 {
198
+ return Err(syn::Error::new_spanned(
199
+ method,
200
+ "A method can only have one of #[init], #[run], #[signal], #[query], #[update], or #[update_validator]",
201
+ ));
202
+ }
203
+
204
+ if has_init {
205
+ if init_method.is_some() {
206
+ return Err(syn::Error::new_spanned(
207
+ method,
208
+ "Only one #[init] method is allowed per workflow",
209
+ ));
210
+ }
211
+ init_method = Some(parse_init_method(method)?);
212
+ } else if has_run {
213
+ if run_method.is_some() {
214
+ return Err(syn::Error::new_spanned(
215
+ method,
216
+ "Only one #[run] method is allowed per workflow",
217
+ ));
218
+ }
219
+ run_method = Some(parse_run_method(method)?);
220
+ } else if has_signal {
221
+ signals.push(parse_signal_method(method)?);
222
+ } else if has_query {
223
+ queries.push(parse_query_method(method)?);
224
+ } else if has_update {
225
+ updates.push(parse_update_method(method)?);
226
+ } else if has_update_validator {
227
+ validators.push(parse_validator_method(method)?);
228
+ }
229
+ }
230
+ }
231
+
232
+ let run_method = run_method.ok_or_else(|| {
233
+ syn::Error::new_spanned(
234
+ &impl_block,
235
+ "#[workflow_methods] requires exactly one #[run] method",
236
+ )
237
+ })?;
238
+
239
+ // Validate that all validator targets exist and link them to updates
240
+ for validator in &validators {
241
+ let target_exists = updates
242
+ .iter()
243
+ .any(|u| u.method.sig.ident == validator.update_name);
244
+ if !target_exists {
245
+ return Err(syn::Error::new_spanned(
246
+ &validator.method,
247
+ format!(
248
+ "No #[update] method '{}' found for validator",
249
+ validator.update_name
250
+ ),
251
+ ));
252
+ }
253
+ }
254
+
255
+ // Link validators to their updates
256
+ for update in &mut updates {
257
+ if let Some(validator) = validators
258
+ .iter()
259
+ .find(|v| v.update_name == update.method.sig.ident)
260
+ {
261
+ update.validator = Some(validator.method.sig.ident.clone());
262
+ }
263
+ }
264
+
265
+ Ok(WorkflowMethodsDefinition {
266
+ impl_block,
267
+ init_method,
268
+ run_method,
269
+ signals,
270
+ queries,
271
+ updates,
272
+ })
273
+ }
274
+ }
275
+
276
+ fn has_attr(attrs: &[Attribute], name: &str) -> bool {
277
+ attrs.iter().any(|attr| attr.path().is_ident(name))
278
+ }
279
+
280
+ fn extract_method_attributes(
281
+ attrs: &[Attribute],
282
+ attr_name: &str,
283
+ ) -> syn::Result<MethodAttributes> {
284
+ let mut method_attrs = MethodAttributes::default();
285
+
286
+ for attr in attrs {
287
+ if attr.path().is_ident(attr_name) && attr.meta.require_list().is_ok() {
288
+ attr.parse_nested_meta(|meta| {
289
+ if meta.path.is_ident("name") {
290
+ let value = meta.value()?;
291
+ let expr: syn::Expr = value.parse()?;
292
+ method_attrs.name_override = Some(expr);
293
+ Ok(())
294
+ } else {
295
+ Err(meta.error(format!("unsupported {attr_name} attribute")))
296
+ }
297
+ })?;
298
+ }
299
+ }
300
+
301
+ Ok(method_attrs)
302
+ }
303
+
304
+ fn parse_init_method(method: &syn::ImplItemFn) -> syn::Result<InitMethod> {
305
+ // Validate: must not be async
306
+ if method.sig.asyncness.is_some() {
307
+ return Err(syn::Error::new_spanned(
308
+ &method.sig,
309
+ "#[init] methods must not be async",
310
+ ));
311
+ }
312
+
313
+ // Validate: must return Self
314
+ let error_msg = "#[init] methods must return Self";
315
+ match &method.sig.output {
316
+ ReturnType::Type(_, ty) => {
317
+ if let Type::Path(TypePath { path, .. }) = &**ty {
318
+ if !path.is_ident("Self") {
319
+ return Err(syn::Error::new_spanned(ty, error_msg));
320
+ }
321
+ } else {
322
+ return Err(syn::Error::new_spanned(ty, error_msg));
323
+ }
324
+ }
325
+ ReturnType::Default => {
326
+ return Err(syn::Error::new_spanned(&method.sig, error_msg));
327
+ }
328
+ }
329
+ let input_type = extract_input_type(&method.sig)?;
330
+
331
+ Ok(InitMethod {
332
+ method: method.clone(),
333
+ input_type,
334
+ })
335
+ }
336
+
337
+ fn parse_run_method(method: &syn::ImplItemFn) -> syn::Result<RunMethod> {
338
+ let attributes = extract_method_attributes(&method.attrs, "run")?;
339
+
340
+ if method.sig.asyncness.is_none() {
341
+ return Err(syn::Error::new_spanned(
342
+ &method.sig,
343
+ "#[run] methods must be async",
344
+ ));
345
+ }
346
+
347
+ // Async run methods must not have self to prevent unsound references across awaits
348
+ validate_no_self_receiver_for_async(method, "run")?;
349
+
350
+ let input_type = extract_input_type(&method.sig)?;
351
+ let output_type = extract_workflow_result_type(&method.sig)?;
352
+
353
+ Ok(RunMethod {
354
+ method: method.clone(),
355
+ attributes,
356
+ input_type,
357
+ output_type,
358
+ })
359
+ }
360
+
361
+ fn parse_signal_method(method: &syn::ImplItemFn) -> syn::Result<SignalMethod> {
362
+ let attributes = extract_method_attributes(&method.attrs, "signal")?;
363
+ let is_async = method.sig.asyncness.is_some();
364
+
365
+ // Async signals must not have self, sync signals use &mut self
366
+ if is_async {
367
+ validate_no_self_receiver_for_async(method, "signal")?;
368
+ } else {
369
+ validate_mut_self_receiver(method)?;
370
+ }
371
+
372
+ let input_type = extract_input_type(&method.sig)?;
373
+
374
+ Ok(SignalMethod {
375
+ method: method.clone(),
376
+ attributes,
377
+ is_async,
378
+ input_type,
379
+ })
380
+ }
381
+
382
+ fn parse_query_method(method: &syn::ImplItemFn) -> syn::Result<QueryMethod> {
383
+ let attributes = extract_method_attributes(&method.attrs, "query")?;
384
+
385
+ if method.sig.asyncness.is_some() {
386
+ return Err(syn::Error::new_spanned(
387
+ &method.sig,
388
+ "#[query] methods must not be async (queries are synchronous)",
389
+ ));
390
+ }
391
+
392
+ validate_immut_self_receiver(method)?;
393
+
394
+ let input_type = extract_input_type(&method.sig)?;
395
+ let (output_type, is_fallible) = extract_maybe_result_output_type(&method.sig);
396
+
397
+ Ok(QueryMethod {
398
+ method: method.clone(),
399
+ attributes,
400
+ input_type,
401
+ output_type,
402
+ is_fallible,
403
+ })
404
+ }
405
+
406
+ fn parse_update_method(method: &syn::ImplItemFn) -> syn::Result<UpdateMethod> {
407
+ let attributes = extract_method_attributes(&method.attrs, "update")?;
408
+ let is_async = method.sig.asyncness.is_some();
409
+
410
+ // Async updates must not have self, sync updates use &mut self
411
+ if is_async {
412
+ validate_no_self_receiver_for_async(method, "update")?;
413
+ } else {
414
+ validate_mut_self_receiver(method)?;
415
+ }
416
+
417
+ let input_type = extract_input_type(&method.sig)?;
418
+ let (output_type, is_fallible) = extract_maybe_result_output_type(&method.sig);
419
+
420
+ Ok(UpdateMethod {
421
+ method: method.clone(),
422
+ attributes,
423
+ is_async,
424
+ input_type,
425
+ output_type,
426
+ is_fallible,
427
+ validator: None,
428
+ })
429
+ }
430
+
431
+ fn parse_validator_method(method: &syn::ImplItemFn) -> syn::Result<ValidatorMethod> {
432
+ if method.sig.asyncness.is_some() {
433
+ return Err(syn::Error::new_spanned(
434
+ &method.sig,
435
+ "#[update_validator] methods must not be async",
436
+ ));
437
+ }
438
+
439
+ validate_immut_self_receiver(method)?;
440
+
441
+ let update_name = extract_validator_target(&method.attrs)?;
442
+
443
+ Ok(ValidatorMethod {
444
+ method: method.clone(),
445
+ update_name,
446
+ })
447
+ }
448
+
449
+ fn extract_validator_target(attrs: &[Attribute]) -> syn::Result<syn::Ident> {
450
+ for attr in attrs {
451
+ if attr.path().is_ident("update_validator") {
452
+ return attr.parse_args();
453
+ }
454
+ }
455
+ unreachable!("extract_validator_target called without update_validator attribute")
456
+ }
457
+
458
+ fn validate_mut_self_receiver(method: &syn::ImplItemFn) -> syn::Result<()> {
459
+ match method.sig.inputs.first() {
460
+ Some(FnArg::Receiver(receiver)) => {
461
+ if receiver.reference.is_none() || receiver.mutability.is_none() {
462
+ return Err(syn::Error::new_spanned(
463
+ receiver,
464
+ "This method must use `&mut self` as the receiver",
465
+ ));
466
+ }
467
+ Ok(())
468
+ }
469
+ _ => Err(syn::Error::new_spanned(
470
+ &method.sig,
471
+ "This method must have `&mut self` as the first parameter",
472
+ )),
473
+ }
474
+ }
475
+
476
+ fn validate_immut_self_receiver(method: &syn::ImplItemFn) -> syn::Result<()> {
477
+ match method.sig.inputs.first() {
478
+ Some(FnArg::Receiver(receiver)) => {
479
+ if receiver.reference.is_none() {
480
+ return Err(syn::Error::new_spanned(
481
+ receiver,
482
+ "#[query] methods must use `&self` as the receiver (not `self`)",
483
+ ));
484
+ }
485
+ if receiver.mutability.is_some() {
486
+ return Err(syn::Error::new_spanned(
487
+ receiver,
488
+ "#[query] methods must use `&self` as the receiver (not `&mut self`) - queries are read-only",
489
+ ));
490
+ }
491
+ Ok(())
492
+ }
493
+ _ => Err(syn::Error::new_spanned(
494
+ &method.sig,
495
+ "#[query] methods must have `&self` as the first parameter",
496
+ )),
497
+ }
498
+ }
499
+
500
+ fn validate_no_self_receiver_for_async(
501
+ method: &syn::ImplItemFn,
502
+ attr_name: &str,
503
+ ) -> syn::Result<()> {
504
+ // Async workflow methods must NOT have a self receiver to prevent unsound references
505
+ // across await points (signals/updates can mutate state during awaits).
506
+ if let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() {
507
+ return Err(syn::Error::new_spanned(
508
+ receiver,
509
+ format!(
510
+ "Async #[{attr_name}] methods must not have a self parameter. \
511
+ Use WorkflowContext to access state safely."
512
+ ),
513
+ ));
514
+ }
515
+ Ok(())
516
+ }
517
+
518
+ fn extract_input_type(sig: &syn::Signature) -> syn::Result<Option<Type>> {
519
+ // Skip self (if present), then find parameter after context
520
+ let mut found_ctx = false;
521
+ for arg in &sig.inputs {
522
+ match arg {
523
+ FnArg::Receiver(_) => continue,
524
+ FnArg::Typed(pat_type) => {
525
+ if found_ctx {
526
+ return Ok(Some((*pat_type.ty).clone()));
527
+ }
528
+ if is_wf_context_type(&pat_type.ty) {
529
+ found_ctx = true;
530
+ }
531
+ }
532
+ }
533
+ }
534
+ Ok(None)
535
+ }
536
+
537
+ fn is_wf_context_type(ty: &Type) -> bool {
538
+ // Check for WorkflowContext, SyncWorkflowContext, or WorkflowContextView
539
+ let inner_type = match ty {
540
+ Type::Reference(r) => &*r.elem,
541
+ other => other,
542
+ };
543
+
544
+ if let Type::Path(TypePath { path, .. }) = inner_type
545
+ && let Some(segment) = path.segments.last()
546
+ {
547
+ return segment.ident == "WorkflowContext"
548
+ || segment.ident == "SyncWorkflowContext"
549
+ || segment.ident == "WorkflowContextView";
550
+ }
551
+ false
552
+ }
553
+
554
+ fn extract_workflow_result_type(sig: &syn::Signature) -> syn::Result<Option<Type>> {
555
+ match &sig.output {
556
+ ReturnType::Type(_, ty) => {
557
+ if let Type::Path(TypePath { path, .. }) = &**ty
558
+ && let Some(segment) = path.segments.last()
559
+ && segment.ident == "WorkflowResult"
560
+ && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
561
+ && let Some(syn::GenericArgument::Type(output_ty)) = args.args.first()
562
+ {
563
+ return Ok(Some(output_ty.clone()));
564
+ }
565
+ Ok(None)
566
+ }
567
+ ReturnType::Default => Ok(None),
568
+ }
569
+ }
570
+
571
+ /// Check if a return type looks like a Result and extract the Ok type if so.
572
+ /// Returns (ok_type, is_result) where:
573
+ /// - If the type looks like `Result<T, E>`, returns (T, true)
574
+ /// - Otherwise returns (original_type, false)
575
+ fn extract_maybe_result_output_type(sig: &syn::Signature) -> (Option<Type>, bool) {
576
+ match &sig.output {
577
+ ReturnType::Default => (None, false),
578
+ ReturnType::Type(_, ty) => {
579
+ if let Type::Path(TypePath { path, .. }) = &**ty
580
+ && let Some(segment) = path.segments.last()
581
+ && segment.ident == "Result"
582
+ && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
583
+ && let Some(syn::GenericArgument::Type(ok_ty)) = args.args.first()
584
+ {
585
+ (Some(ok_ty.clone()), true)
586
+ } else {
587
+ (Some((**ty).clone()), false)
588
+ }
589
+ }
590
+ }
591
+ }
592
+
593
+ impl WorkflowMethodsDefinition {
594
+ fn run_struct_ident(&self) -> syn::Ident {
595
+ let struct_name = method_name_to_pascal_case(&self.run_method.method.sig.ident);
596
+ format_ident!("{}", struct_name)
597
+ }
598
+
599
+ pub(crate) fn codegen_with_options(&self, factory_only: bool) -> TokenStream {
600
+ let impl_type = &self.impl_block.self_ty;
601
+ let impl_type_name = type_name_string(impl_type);
602
+ let module_name = type_to_snake_case(impl_type);
603
+ let module_ident = format_ident!("{}", module_name);
604
+
605
+ // Generate the cleaned impl block with:
606
+ // - workflow attributes stripped
607
+ // - Annotated methods renamed with __ prefix
608
+ let mut cleaned_impl = self.impl_block.clone();
609
+ for item in &mut cleaned_impl.items {
610
+ if let ImplItem::Fn(method) = item {
611
+ let is_workflow_method = has_attr(&method.attrs, "init")
612
+ || has_attr(&method.attrs, "run")
613
+ || has_attr(&method.attrs, "signal")
614
+ || has_attr(&method.attrs, "query")
615
+ || has_attr(&method.attrs, "update")
616
+ || has_attr(&method.attrs, "update_validator");
617
+
618
+ // Strip workflow-related attributes
619
+ method.attrs.retain(|attr| {
620
+ !attr.path().is_ident("init")
621
+ && !attr.path().is_ident("run")
622
+ && !attr.path().is_ident("signal")
623
+ && !attr.path().is_ident("query")
624
+ && !attr.path().is_ident("update")
625
+ && !attr.path().is_ident("update_validator")
626
+ });
627
+
628
+ // Rename workflow methods with __ prefix
629
+ if is_workflow_method {
630
+ let new_name = format_ident!("__{}", method.sig.ident);
631
+ method.sig.ident = new_name;
632
+ }
633
+ }
634
+ }
635
+
636
+ let mut marker_structs = Vec::new();
637
+ let mut const_definitions = Vec::new();
638
+ let mut trait_impls = Vec::new();
639
+
640
+ // Run method marker struct and impls
641
+ let run_method = &self.run_method;
642
+ let struct_ident = self.run_struct_ident();
643
+
644
+ let input_type = type_to_tokens(
645
+ run_method.input_type.as_ref().or(self
646
+ .init_method
647
+ .as_ref()
648
+ .and_then(|m| m.input_type.as_ref())),
649
+ );
650
+
651
+ let output_type = type_to_tokens(run_method.output_type.as_ref());
652
+
653
+ let workflow_name = if let Some(ref name_expr) = run_method.attributes.name_override {
654
+ quote! { #name_expr }
655
+ } else {
656
+ quote! { #impl_type_name }
657
+ };
658
+
659
+ marker_structs.push(generate_marker_struct(&run_method.method));
660
+ const_definitions.push(generate_const_definition(&run_method.method, &module_ident));
661
+
662
+ trait_impls.push(quote! {
663
+ impl ::temporalio_common::WorkflowDefinition for #module_ident::#struct_ident {
664
+ type Input = #input_type;
665
+ type Output = #output_type;
666
+
667
+ fn name(&self) -> &str {
668
+ #workflow_name
669
+ }
670
+ }
671
+
672
+ impl ::temporalio_common::WorkflowDefinition for #impl_type {
673
+ type Input = #input_type;
674
+ type Output = #output_type;
675
+
676
+ fn name(&self) -> &str {
677
+ #workflow_name
678
+ }
679
+ }
680
+ });
681
+
682
+ for signal in &self.signals {
683
+ let (structs, consts, impls) =
684
+ self.generate_signal_code(signal, impl_type, &module_ident);
685
+ marker_structs.push(structs);
686
+ const_definitions.push(consts);
687
+ trait_impls.push(impls);
688
+ }
689
+
690
+ for query in &self.queries {
691
+ let (structs, consts, impls) =
692
+ self.generate_query_code(query, impl_type, &module_ident);
693
+ marker_structs.push(structs);
694
+ const_definitions.push(consts);
695
+ trait_impls.push(impls);
696
+ }
697
+
698
+ for update in &self.updates {
699
+ let (structs, consts, impls) =
700
+ self.generate_update_code(update, impl_type, &module_ident);
701
+ marker_structs.push(structs);
702
+ const_definitions.push(consts);
703
+ trait_impls.push(impls);
704
+ }
705
+
706
+ let workflow_impl =
707
+ self.generate_workflow_implementation(impl_type, &module_ident, factory_only);
708
+ let implementer_impl = self.generate_workflow_implementer(impl_type);
709
+
710
+ let const_impl = quote! {
711
+ impl #impl_type {
712
+ /// Returns the workflow type name
713
+ pub const fn name() -> &'static str {
714
+ #workflow_name
715
+ }
716
+
717
+ #(#const_definitions)*
718
+ }
719
+ };
720
+
721
+ let output = quote! {
722
+ #cleaned_impl
723
+
724
+ #const_impl
725
+
726
+ mod #module_ident {
727
+ #(#marker_structs)*
728
+ }
729
+
730
+ #(#trait_impls)*
731
+
732
+ #workflow_impl
733
+
734
+ #implementer_impl
735
+ };
736
+
737
+ output.into()
738
+ }
739
+
740
+ fn generate_signal_code(
741
+ &self,
742
+ signal: &SignalMethod,
743
+ impl_type: &Type,
744
+ module_ident: &syn::Ident,
745
+ ) -> (TokenStream2, TokenStream2, TokenStream2) {
746
+ let info = HandlerCodegenInfo::new(
747
+ &signal.method,
748
+ &signal.attributes,
749
+ signal.input_type.as_ref(),
750
+ module_ident,
751
+ );
752
+ let struct_ident = &info.struct_ident;
753
+ let input_type = &info.input_type_tokens;
754
+ let signal_name = &info.handler_name;
755
+
756
+ let run_struct_ident = self.run_struct_ident();
757
+ let definition_impl = quote! {
758
+ impl ::temporalio_common::SignalDefinition for #module_ident::#struct_ident {
759
+ type Workflow = #module_ident::#run_struct_ident;
760
+ type Input = #input_type;
761
+
762
+ fn name(&self) -> &str {
763
+ #signal_name
764
+ }
765
+ }
766
+ };
767
+
768
+ let has_input = signal.input_type.is_some();
769
+ let executable_impl = if signal.is_async {
770
+ let handle_body =
771
+ generate_async_handler_body(impl_type, &info.prefixed_method, has_input);
772
+ quote! {
773
+ impl ::temporalio_sdk::workflows::ExecutableAsyncSignal<#module_ident::#struct_ident> for #impl_type {
774
+ fn handle(
775
+ mut ctx: ::temporalio_sdk::WorkflowContext<Self>,
776
+ input: <#module_ident::#struct_ident as ::temporalio_common::SignalDefinition>::Input,
777
+ ) -> ::futures_util::future::LocalBoxFuture<'static, ()> {
778
+ use ::futures_util::FutureExt;
779
+ #handle_body
780
+ }
781
+ }
782
+ }
783
+ } else {
784
+ let method_call = generate_method_call(&info.prefixed_method, has_input);
785
+ quote! {
786
+ impl ::temporalio_sdk::workflows::ExecutableSyncSignal<#module_ident::#struct_ident> for #impl_type {
787
+ fn handle(
788
+ &mut self,
789
+ ctx: &mut ::temporalio_sdk::SyncWorkflowContext<Self>,
790
+ input: <#module_ident::#struct_ident as ::temporalio_common::SignalDefinition>::Input,
791
+ ) {
792
+ #method_call
793
+ }
794
+ }
795
+ }
796
+ };
797
+
798
+ let trait_impl = quote! {
799
+ #definition_impl
800
+ #executable_impl
801
+ };
802
+
803
+ (info.marker_struct, info.const_def, trait_impl)
804
+ }
805
+
806
+ fn generate_query_code(
807
+ &self,
808
+ query: &QueryMethod,
809
+ impl_type: &Type,
810
+ module_ident: &syn::Ident,
811
+ ) -> (TokenStream2, TokenStream2, TokenStream2) {
812
+ let info = HandlerCodegenInfo::new(
813
+ &query.method,
814
+ &query.attributes,
815
+ query.input_type.as_ref(),
816
+ module_ident,
817
+ );
818
+ let struct_ident = &info.struct_ident;
819
+ let prefixed_method = &info.prefixed_method;
820
+ let input_type = &info.input_type_tokens;
821
+ let output_type = type_to_tokens(query.output_type.as_ref());
822
+ let query_name = &info.handler_name;
823
+
824
+ let method_call = generate_method_call(prefixed_method, query.input_type.is_some());
825
+
826
+ let body = if query.is_fallible {
827
+ quote! { #method_call }
828
+ } else {
829
+ quote! { Ok(#method_call) }
830
+ };
831
+
832
+ let run_struct_ident = self.run_struct_ident();
833
+ let trait_impl = quote! {
834
+ impl ::temporalio_common::QueryDefinition for #module_ident::#struct_ident {
835
+ type Workflow = #module_ident::#run_struct_ident;
836
+ type Input = #input_type;
837
+ type Output = #output_type;
838
+
839
+ fn name(&self) -> &str {
840
+ #query_name
841
+ }
842
+ }
843
+
844
+ impl ::temporalio_sdk::workflows::ExecutableQuery<#module_ident::#struct_ident> for #impl_type {
845
+ fn handle(
846
+ &self,
847
+ ctx: &::temporalio_sdk::WorkflowContextView,
848
+ input: <#module_ident::#struct_ident as ::temporalio_common::QueryDefinition>::Input,
849
+ ) -> Result<<#module_ident::#struct_ident as ::temporalio_common::QueryDefinition>::Output, Box<dyn ::std::error::Error + Send + Sync>> {
850
+ #body
851
+ }
852
+ }
853
+ };
854
+
855
+ (info.marker_struct, info.const_def, trait_impl)
856
+ }
857
+
858
+ fn generate_update_code(
859
+ &self,
860
+ update: &UpdateMethod,
861
+ impl_type: &Type,
862
+ module_ident: &syn::Ident,
863
+ ) -> (TokenStream2, TokenStream2, TokenStream2) {
864
+ let info = HandlerCodegenInfo::new(
865
+ &update.method,
866
+ &update.attributes,
867
+ update.input_type.as_ref(),
868
+ module_ident,
869
+ );
870
+ let struct_ident = &info.struct_ident;
871
+ let input_type = &info.input_type_tokens;
872
+ let output_type = type_to_tokens(update.output_type.as_ref());
873
+ let update_name = &info.handler_name;
874
+
875
+ let run_struct_ident = self.run_struct_ident();
876
+ let definition_impl = quote! {
877
+ impl ::temporalio_common::UpdateDefinition for #module_ident::#struct_ident {
878
+ type Workflow = #module_ident::#run_struct_ident;
879
+ type Input = #input_type;
880
+ type Output = #output_type;
881
+
882
+ fn name(&self) -> &str {
883
+ #update_name
884
+ }
885
+ }
886
+ };
887
+
888
+ let has_input = update.input_type.is_some();
889
+ let validate_impl = if let Some(ref validator_name) = update.validator {
890
+ let prefixed_validator = format_ident!("__{}", validator_name);
891
+ quote! {
892
+ fn validate(&self, ctx: &::temporalio_sdk::WorkflowContextView, input: &<#module_ident::#struct_ident as ::temporalio_common::UpdateDefinition>::Input) -> Result<(), Box<dyn ::std::error::Error + Send + Sync>> {
893
+ self.#prefixed_validator(ctx, input)
894
+ }
895
+ }
896
+ } else {
897
+ quote! {}
898
+ };
899
+
900
+ let executable_impl = if update.is_async {
901
+ let handle_body = generate_async_update_handler_body(
902
+ impl_type,
903
+ &info.prefixed_method,
904
+ has_input,
905
+ update.is_fallible,
906
+ );
907
+ quote! {
908
+ impl ::temporalio_sdk::workflows::ExecutableAsyncUpdate<#module_ident::#struct_ident> for #impl_type {
909
+ fn handle(
910
+ mut ctx: ::temporalio_sdk::WorkflowContext<Self>,
911
+ input: <#module_ident::#struct_ident as ::temporalio_common::UpdateDefinition>::Input,
912
+ ) -> ::futures_util::future::LocalBoxFuture<'static, Result<<#module_ident::#struct_ident as ::temporalio_common::UpdateDefinition>::Output, Box<dyn ::std::error::Error + Send + Sync>>> {
913
+ use ::futures_util::FutureExt;
914
+ #handle_body
915
+ }
916
+
917
+ #validate_impl
918
+ }
919
+ }
920
+ } else {
921
+ let method_call = generate_method_call(&info.prefixed_method, has_input);
922
+ let body = if update.is_fallible {
923
+ quote! { #method_call }
924
+ } else {
925
+ quote! { Ok(#method_call) }
926
+ };
927
+ quote! {
928
+ impl ::temporalio_sdk::workflows::ExecutableSyncUpdate<#module_ident::#struct_ident> for #impl_type {
929
+ fn handle(
930
+ &mut self,
931
+ ctx: &mut ::temporalio_sdk::SyncWorkflowContext<Self>,
932
+ input: <#module_ident::#struct_ident as ::temporalio_common::UpdateDefinition>::Input,
933
+ ) -> Result<<#module_ident::#struct_ident as ::temporalio_common::UpdateDefinition>::Output, Box<dyn ::std::error::Error + Send + Sync>> {
934
+ #body
935
+ }
936
+
937
+ #validate_impl
938
+ }
939
+ }
940
+ };
941
+
942
+ let trait_impl = quote! {
943
+ #definition_impl
944
+ #executable_impl
945
+ };
946
+
947
+ (info.marker_struct, info.const_def, trait_impl)
948
+ }
949
+
950
+ fn generate_workflow_implementation(
951
+ &self,
952
+ impl_type: &Type,
953
+ module_ident: &syn::Ident,
954
+ factory_only: bool,
955
+ ) -> TokenStream2 {
956
+ let run_method = &self.run_method;
957
+
958
+ let run_struct_name = method_name_to_pascal_case(&run_method.method.sig.ident);
959
+ let run_struct_ident = format_ident!("{}", run_struct_name);
960
+ let prefixed_run = format_ident!("__{}", run_method.method.sig.ident);
961
+
962
+ // Determine if init exists and takes input, or if run takes input
963
+ let has_init = self.init_method.is_some();
964
+ let init_has_input = self
965
+ .init_method
966
+ .as_ref()
967
+ .map(|m| m.input_type.is_some())
968
+ .unwrap_or(false);
969
+ let run_has_input = run_method.input_type.is_some();
970
+
971
+ let init_body = if let Some(ref init_method) = self.init_method {
972
+ let prefixed_init = format_ident!("__{}", init_method.method.sig.ident);
973
+ if init_has_input {
974
+ quote! {
975
+ #impl_type::#prefixed_init(&ctx, input.expect("init takes input"))
976
+ }
977
+ } else {
978
+ quote! {
979
+ #impl_type::#prefixed_init(&ctx)
980
+ }
981
+ }
982
+ } else if factory_only {
983
+ // For factory-only workflows, init() should never be called
984
+ quote! {
985
+ panic!(
986
+ "This workflow must be registered with register_workflow_with_factory"
987
+ )
988
+ }
989
+ } else {
990
+ quote! {
991
+ Self::default()
992
+ }
993
+ };
994
+
995
+ let run_call = if run_has_input {
996
+ quote! { Self::#prefixed_run(&mut ctx, input.expect("run takes input")).await }
997
+ } else {
998
+ quote! { Self::#prefixed_run(&mut ctx).await }
999
+ };
1000
+
1001
+ let run_impl_body = quote! {
1002
+ use ::futures_util::FutureExt;
1003
+ async move {
1004
+ let result = #run_call;
1005
+ match result {
1006
+ Ok(value) => ::temporalio_sdk::workflows::serialize_result(value, &ctx.payload_converter())
1007
+ .map_err(|e| ::temporalio_sdk::WorkflowTermination::failed(e)),
1008
+ Err(e) => Err(e),
1009
+ }
1010
+ }.boxed_local()
1011
+ };
1012
+
1013
+ // Generate signal dispatch match arms
1014
+ let dispatch_signal_arms: Vec<TokenStream2> = self
1015
+ .signals
1016
+ .iter()
1017
+ .map(|s| {
1018
+ let info = HandlerCodegenInfo::new(
1019
+ &s.method,
1020
+ &s.attributes,
1021
+ s.input_type.as_ref(),
1022
+ module_ident,
1023
+ );
1024
+ let struct_ident = &info.struct_ident;
1025
+ let handler_name = &info.handler_name;
1026
+
1027
+ if s.is_async {
1028
+ quote! {
1029
+ #handler_name => Some(<Self as ::temporalio_sdk::workflows::ExecutableAsyncSignal<#module_ident::#struct_ident>>::dispatch(ctx.clone(), payloads, converter)),
1030
+ }
1031
+ } else {
1032
+ quote! {
1033
+ #handler_name => Some(<Self as ::temporalio_sdk::workflows::ExecutableSyncSignal<#module_ident::#struct_ident>>::dispatch(ctx, payloads, converter)),
1034
+ }
1035
+ }
1036
+ })
1037
+ .collect();
1038
+
1039
+ // Generate dispatch_signal only if there are signals
1040
+ let dispatch_signal_impl = if self.signals.is_empty() {
1041
+ quote! {}
1042
+ } else {
1043
+ quote! {
1044
+ fn dispatch_signal(
1045
+ ctx: ::temporalio_sdk::WorkflowContext<Self>,
1046
+ name: &str,
1047
+ payloads: ::temporalio_common::protos::temporal::api::common::v1::Payloads,
1048
+ converter: &::temporalio_common::data_converters::PayloadConverter,
1049
+ ) -> Option<::futures_util::future::LocalBoxFuture<'static, Result<(), ::temporalio_sdk::workflows::WorkflowError>>> {
1050
+ match name {
1051
+ #(#dispatch_signal_arms)*
1052
+ _ => None,
1053
+ }
1054
+ }
1055
+ }
1056
+ };
1057
+
1058
+ // Generate update dispatch match arms
1059
+ let dispatch_update_arms: Vec<TokenStream2> = self
1060
+ .updates
1061
+ .iter()
1062
+ .map(|u| {
1063
+ let info = HandlerCodegenInfo::new(
1064
+ &u.method,
1065
+ &u.attributes,
1066
+ u.input_type.as_ref(),
1067
+ module_ident,
1068
+ );
1069
+ let struct_ident = &info.struct_ident;
1070
+ let handler_name = &info.handler_name;
1071
+
1072
+ if u.is_async {
1073
+ quote! {
1074
+ #handler_name => Some(<Self as ::temporalio_sdk::workflows::ExecutableAsyncUpdate<#module_ident::#struct_ident>>::dispatch(ctx.clone(), payloads, converter)),
1075
+ }
1076
+ } else {
1077
+ quote! {
1078
+ #handler_name => Some(<Self as ::temporalio_sdk::workflows::ExecutableSyncUpdate<#module_ident::#struct_ident>>::dispatch(ctx, payloads, converter)),
1079
+ }
1080
+ }
1081
+ })
1082
+ .collect();
1083
+
1084
+ // Generate validate_update match arms
1085
+ let validate_update_arms: Vec<TokenStream2> = self
1086
+ .updates
1087
+ .iter()
1088
+ .map(|u| {
1089
+ let info = HandlerCodegenInfo::new(
1090
+ &u.method,
1091
+ &u.attributes,
1092
+ u.input_type.as_ref(),
1093
+ module_ident,
1094
+ );
1095
+ let struct_ident = &info.struct_ident;
1096
+ let handler_name = &info.handler_name;
1097
+
1098
+ let validate_trait = if u.is_async {
1099
+ quote! { ::temporalio_sdk::workflows::ExecutableAsyncUpdate<#module_ident::#struct_ident> }
1100
+ } else {
1101
+ quote! { ::temporalio_sdk::workflows::ExecutableSyncUpdate<#module_ident::#struct_ident> }
1102
+ };
1103
+
1104
+ quote! {
1105
+ #handler_name => Some(<Self as #validate_trait>::dispatch_validate(self, &ctx, payloads, converter)),
1106
+ }
1107
+ })
1108
+ .collect();
1109
+
1110
+ // Generate dispatch_update and validate_update only if there are updates
1111
+ let dispatch_update_impl = if self.updates.is_empty() {
1112
+ quote! {}
1113
+ } else {
1114
+ quote! {
1115
+ fn dispatch_update(
1116
+ ctx: ::temporalio_sdk::WorkflowContext<Self>,
1117
+ name: &str,
1118
+ payloads: ::temporalio_common::protos::temporal::api::common::v1::Payloads,
1119
+ converter: &::temporalio_common::data_converters::PayloadConverter,
1120
+ ) -> Option<::futures_util::future::LocalBoxFuture<'static, Result<::temporalio_common::protos::temporal::api::common::v1::Payload, ::temporalio_sdk::workflows::WorkflowError>>> {
1121
+ match name {
1122
+ #(#dispatch_update_arms)*
1123
+ _ => None,
1124
+ }
1125
+ }
1126
+
1127
+ fn validate_update(
1128
+ &self,
1129
+ ctx: ::temporalio_sdk::WorkflowContextView,
1130
+ name: &str,
1131
+ payloads: &::temporalio_common::protos::temporal::api::common::v1::Payloads,
1132
+ converter: &::temporalio_common::data_converters::PayloadConverter,
1133
+ ) -> Option<Result<(), ::temporalio_sdk::workflows::WorkflowError>> {
1134
+
1135
+ match name {
1136
+ #(#validate_update_arms)*
1137
+ _ => None,
1138
+ }
1139
+ }
1140
+ }
1141
+ };
1142
+
1143
+ // Generate dispatch_query match arms
1144
+ let dispatch_query_arms: Vec<TokenStream2> = self
1145
+ .queries
1146
+ .iter()
1147
+ .map(|q| {
1148
+ let info = HandlerCodegenInfo::new(
1149
+ &q.method,
1150
+ &q.attributes,
1151
+ q.input_type.as_ref(),
1152
+ module_ident,
1153
+ );
1154
+ let struct_ident = &info.struct_ident;
1155
+ let handler_name = &info.handler_name;
1156
+
1157
+ quote! {
1158
+ #handler_name => Some(<Self as ::temporalio_sdk::workflows::ExecutableQuery<#module_ident::#struct_ident>>::dispatch(self, &ctx, payloads, converter)),
1159
+ }
1160
+ })
1161
+ .collect();
1162
+
1163
+ // Generate dispatch_query only if there are queries
1164
+ let dispatch_query_impl = if self.queries.is_empty() {
1165
+ quote! {}
1166
+ } else {
1167
+ quote! {
1168
+ fn dispatch_query(
1169
+ &self,
1170
+ ctx: ::temporalio_sdk::WorkflowContextView,
1171
+ name: &str,
1172
+ payloads: &::temporalio_common::protos::temporal::api::common::v1::Payloads,
1173
+ converter: &::temporalio_common::data_converters::PayloadConverter,
1174
+ ) -> Option<Result<::temporalio_common::protos::temporal::api::common::v1::Payload, ::temporalio_sdk::workflows::WorkflowError>> {
1175
+ match name {
1176
+ #(#dispatch_query_arms)*
1177
+ _ => None,
1178
+ }
1179
+ }
1180
+ }
1181
+ };
1182
+
1183
+ quote! {
1184
+ impl ::temporalio_sdk::workflows::WorkflowImplementation for #impl_type {
1185
+ type Run = #module_ident::#run_struct_ident;
1186
+
1187
+ const HAS_INIT: bool = #has_init;
1188
+ const INIT_TAKES_INPUT: bool = #init_has_input;
1189
+
1190
+ fn name() -> &'static str {
1191
+ <#impl_type>::name()
1192
+ }
1193
+
1194
+ fn init(
1195
+ ctx: ::temporalio_sdk::WorkflowContextView,
1196
+ input: ::std::option::Option<<Self::Run as ::temporalio_common::WorkflowDefinition>::Input>,
1197
+ ) -> Self {
1198
+ #init_body
1199
+ }
1200
+
1201
+ fn run(
1202
+ mut ctx: ::temporalio_sdk::WorkflowContext<Self>,
1203
+ input: ::std::option::Option<<Self::Run as ::temporalio_common::WorkflowDefinition>::Input>,
1204
+ ) -> ::futures_util::future::LocalBoxFuture<'static, Result<::temporalio_common::protos::temporal::api::common::v1::Payload, ::temporalio_sdk::WorkflowTermination>> {
1205
+ #run_impl_body
1206
+ }
1207
+
1208
+ #dispatch_signal_impl
1209
+ #dispatch_update_impl
1210
+ #dispatch_query_impl
1211
+ }
1212
+ }
1213
+ }
1214
+
1215
+ fn generate_workflow_implementer(&self, impl_type: &Type) -> TokenStream2 {
1216
+ quote! {
1217
+ impl ::temporalio_sdk::workflows::WorkflowImplementer for #impl_type {
1218
+ fn register_all(defs: &mut ::temporalio_sdk::workflows::WorkflowDefinitions) {
1219
+ defs.register_workflow_run::<Self>();
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ }