Skip to main content

swf_runtime/
handler.rs

1use crate::error::WorkflowResult;
2use serde_json::Value;
3use std::sync::Arc;
4
5/// Read-only snapshot of workflow context variables available to task handlers.
6///
7/// Provides access to `$context`, `$secret`, `$workflow`, and other runtime variables
8/// that were previously inaccessible from custom handlers.
9///
10/// # Example
11///
12/// ```no_run
13/// use async_trait::async_trait;
14/// use serde_json::Value;
15/// use swf_runtime::{CustomTaskHandler, HandlerContext, WorkflowResult};
16///
17/// struct SmartHandler;
18///
19/// #[async_trait]
20/// impl CustomTaskHandler for SmartHandler {
21///     fn task_type(&self) -> &str { "smart" }
22///
23///     async fn handle(
24///         &self,
25///         task_name: &str,
26///         task_type: &str,
27///         task_config: &Value,
28///         input: &Value,
29///         context: &HandlerContext,
30///     ) -> WorkflowResult<Value> {
31///         // Access $context to read workflow state
32///         let preferred = context.context().get("provider").and_then(|v| v.as_str());
33///         // Access $secret for credentials
34///         let api_key = context.secret().and_then(|s| s.get("API_KEY")).and_then(|v| v.as_str());
35///         Ok(input.clone())
36///     }
37/// }
38/// ```
39#[derive(Debug, Clone)]
40pub struct HandlerContext {
41    context: Value,
42    secret: Option<Value>,
43    workflow: Value,
44    authorization: Option<Value>,
45}
46
47impl HandlerContext {
48    /// Creates a new HandlerContext from the current workflow context variables
49    pub(crate) fn from_vars(vars: &std::collections::HashMap<String, Value>) -> Self {
50        Self {
51            context: vars
52                .get(crate::context::vars::CONTEXT)
53                .cloned()
54                .unwrap_or(Value::Null),
55            secret: vars.get(crate::context::vars::SECRET).cloned(),
56            workflow: vars
57                .get(crate::context::vars::WORKFLOW)
58                .cloned()
59                .unwrap_or(Value::Null),
60            authorization: vars.get(crate::context::vars::AUTHORIZATION).cloned(),
61        }
62    }
63
64    /// Returns the `$context` value (workflow instance state set by `export.as`)
65    pub fn context(&self) -> &Value {
66        &self.context
67    }
68
69    /// Returns the `$secret` value (all resolved secrets), if a secret manager is configured
70    pub fn secret(&self) -> Option<&Value> {
71        self.secret.as_ref()
72    }
73
74    /// Returns the `$workflow` descriptor (workflow metadata)
75    pub fn workflow(&self) -> &Value {
76        &self.workflow
77    }
78
79    /// Returns the `$authorization` value (set after HTTP authentication), if any
80    pub fn authorization(&self) -> Option<&Value> {
81        self.authorization.as_ref()
82    }
83}
84
85/// Handler for call task types that require custom implementations.
86///
87/// Implement this trait to provide support for call types like gRPC, OpenAPI,
88/// AsyncAPI, and A2A. Register handlers with `WorkflowRunner::with_call_handler()`.
89///
90/// # Example
91///
92/// ```no_run
93/// use async_trait::async_trait;
94/// use serde_json::Value;
95/// use swf_runtime::{CallHandler, HandlerContext, WorkflowResult};
96///
97/// struct GrpcCallHandler;
98///
99/// #[async_trait]
100/// impl CallHandler for GrpcCallHandler {
101///     fn call_type(&self) -> &str { "grpc" }
102///
103///     async fn handle(
104///         &self,
105///         task_name: &str,
106///         call_config: &Value,
107///         input: &Value,
108///         context: &HandlerContext,
109///     ) -> WorkflowResult<Value> {
110///         // Implement gRPC call logic here
111///         Ok(serde_json::json!({ "result": "grpc response" }))
112///     }
113/// }
114/// ```
115#[async_trait::async_trait]
116pub trait CallHandler: Send + Sync {
117    /// Returns the call type this handler supports (e.g., "grpc", "openapi", "asyncapi", "a2a")
118    fn call_type(&self) -> &str;
119
120    /// Executes the call with the given configuration, input, and workflow context.
121    async fn handle(
122        &self,
123        task_name: &str,
124        call_config: &Value,
125        input: &Value,
126        context: &HandlerContext,
127    ) -> WorkflowResult<Value>;
128}
129
130/// Handler for run task types that require custom implementations.
131///
132/// Implement this trait to provide support for run types like container and script.
133/// Register handlers with `WorkflowRunner::with_run_handler()`.
134///
135/// # Example
136///
137/// ```no_run
138/// use async_trait::async_trait;
139/// use serde_json::Value;
140/// use swf_runtime::{RunHandler, HandlerContext, WorkflowResult};
141///
142/// struct ContainerRunHandler;
143///
144/// #[async_trait]
145/// impl RunHandler for ContainerRunHandler {
146///     fn run_type(&self) -> &str { "container" }
147///
148///     async fn handle(
149///         &self,
150///         task_name: &str,
151///         run_config: &Value,
152///         input: &Value,
153///         context: &HandlerContext,
154///     ) -> WorkflowResult<Value> {
155///         // Implement container run logic here
156///         Ok(serde_json::json!({ "exitCode": 0 }))
157///     }
158/// }
159/// ```
160#[async_trait::async_trait]
161pub trait RunHandler: Send + Sync {
162    /// Returns the run type this handler supports (e.g., "container", "script")
163    fn run_type(&self) -> &str;
164
165    /// Executes the run with the given configuration, input, and workflow context.
166    async fn handle(
167        &self,
168        task_name: &str,
169        run_config: &Value,
170        input: &Value,
171        context: &HandlerContext,
172    ) -> WorkflowResult<Value>;
173}
174
175/// Handler for custom/extension task types.
176///
177/// Implement this trait to provide support for custom task types that are
178/// not part of the built-in Serverless Workflow specification.
179/// Register handlers with `WorkflowRunner::with_custom_task_handler()`.
180///
181/// # Example
182///
183/// ```no_run
184/// use async_trait::async_trait;
185/// use serde_json::Value;
186/// use swf_runtime::{CustomTaskHandler, HandlerContext, WorkflowResult};
187///
188/// struct UppercaseHandler;
189///
190/// #[async_trait]
191/// impl CustomTaskHandler for UppercaseHandler {
192///     fn task_type(&self) -> &str { "uppercase" }
193///
194///     async fn handle(
195///         &self,
196///         task_name: &str,
197///         task_type: &str,
198///         task_config: &Value,
199///         input: &Value,
200///         context: &HandlerContext,
201///     ) -> WorkflowResult<Value> {
202///         let text = input["text"].as_str().unwrap_or("");
203///         Ok(serde_json::json!({ "result": text.to_uppercase() }))
204///     }
205/// }
206/// ```
207#[async_trait::async_trait]
208pub trait CustomTaskHandler: Send + Sync {
209    /// Returns the custom task type this handler supports (e.g., "myCustomTask")
210    fn task_type(&self) -> &str;
211
212    /// Executes the custom task with the given configuration, input, and workflow context.
213    async fn handle(
214        &self,
215        task_name: &str,
216        task_type: &str,
217        task_config: &Value,
218        input: &Value,
219        context: &HandlerContext,
220    ) -> WorkflowResult<Value>;
221}
222
223/// Registry of call, run, and custom task handlers.
224/// Uses Arc for cheap cloning — handlers are shared across workflow context propagation.
225#[derive(Default, Clone)]
226pub struct HandlerRegistry {
227    call_handlers:
228        std::sync::Arc<std::collections::HashMap<String, std::sync::Arc<dyn CallHandler>>>,
229    run_handlers: std::sync::Arc<std::collections::HashMap<String, std::sync::Arc<dyn RunHandler>>>,
230    custom_task_handlers:
231        std::sync::Arc<std::collections::HashMap<String, std::sync::Arc<dyn CustomTaskHandler>>>,
232}
233
234impl HandlerRegistry {
235    /// Creates a new empty handler registry
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    /// Registers a call handler
241    pub fn register_call_handler(&mut self, handler: Box<dyn CallHandler>) {
242        let key = handler.call_type().to_string();
243        Arc::make_mut(&mut self.call_handlers).insert(key, std::sync::Arc::from(handler));
244    }
245
246    /// Registers a run handler
247    pub fn register_run_handler(&mut self, handler: Box<dyn RunHandler>) {
248        let key = handler.run_type().to_string();
249        Arc::make_mut(&mut self.run_handlers).insert(key, std::sync::Arc::from(handler));
250    }
251
252    /// Registers a custom task handler
253    pub fn register_custom_task_handler(&mut self, handler: Box<dyn CustomTaskHandler>) {
254        let key = handler.task_type().to_string();
255        Arc::make_mut(&mut self.custom_task_handlers).insert(key, std::sync::Arc::from(handler));
256    }
257
258    /// Looks up a call handler by type
259    pub fn get_call_handler(&self, call_type: &str) -> Option<std::sync::Arc<dyn CallHandler>> {
260        self.call_handlers.get(call_type).cloned()
261    }
262
263    /// Looks up a run handler by type
264    pub fn get_run_handler(&self, run_type: &str) -> Option<std::sync::Arc<dyn RunHandler>> {
265        self.run_handlers.get(run_type).cloned()
266    }
267
268    /// Looks up a custom task handler by task type
269    pub fn get_custom_task_handler(
270        &self,
271        task_type: &str,
272    ) -> Option<std::sync::Arc<dyn CustomTaskHandler>> {
273        self.custom_task_handlers.get(task_type).cloned()
274    }
275}