operaton_task_worker/
lib.rs

1/*!
2# Operaton Task Worker: A Rust library for external task polling and execution from Operaton
3
4This crate provides functionality to poll external tasks from the Operaton BPMN engine and execute them via a handler function.
5It is intended for implementing external task handlers and the BPMN business logic in Rust.
6
7## Operaton
8[Operaton](https://operaton.org) is an open source BPMN engine and a fork of Camunda 7.
9It provides an API to pull pending external tasks and return results to the engine, which then updates the task state.
10The crate uses the Operaton API to poll for external tasks and execute them via a handler function.
11
12## Compatibility
13The crate is tested with Operaton 1.0 and intends to provide a stable abstraction layer for future Operaton versions.
14Camunda 7 is not supported, however, at the current state, it should be possible to use the crate with Camunda 7.
15
16## How to Use this Crate
17
18Running a task worker with this crate is intended to be very easy and involves two steps:
19- Implement handler functions for the tasks to be executed
20- Start the task worker with the proper configuration
21
22A minimal working axample of a task worker with one handler function looks like this:
23
24```ignore
25use operaton_task_worker::{poll, settings};
26use operaton_task_worker_macros::task_handler;
27
28/// The prefix for all environment variables used by Operaton Task Worker
29///
30/// Note: This does not apply for Rust-specific environment variables such as `LOGLEVEL`.
31pub const ENV_PREFIX: &str = "OPERATON_TASK_WORKER";
32
33#[tokio::main]
34async fn main() {
35  // Get the parameters from the environment variables
36  let config = settings::load_config_from_env(ENV_PREFIX);
37  poll(config).await;
38}
39
40#[task_handler(name = "ServiceTask_Grant_Approval")]
41fn service_task_grant_approval(_input: &operaton_task_worker::types::InputVariables) -> Result<operaton_task_worker::types::OutputVariables, Box<dyn std::error::Error>> {
42  Ok(std::collections::HashMap::new())
43}
44```
45### Starting the main poll function
46
47The poll function is the main entry point for the task worker. It starts the polling loop and blocks the current thread until it ends (infinite loop).
48Use the top level `poll` function for async or the convenience function `poll_blocking` for non async environments.
49
50### Configuring the task worker
51The task worker is configured via the `ConfigParams` struct. The struct implementation provides a builder pattern to configure the task worker.
52You can also load the configuration from environment variables using the `load_config_from_env` function.
53
54
55#### Using the environment variables
56
57The following environment variables are used by the task worker--given that the prefix is `OPERATON_TASK_WORKER`:
58- `OPERATON_TASK_WORKER_URL` - URL of the Operaton Task Service
59- `OPERATON_TASK_WORKER_USERNAME` - Username for the Operaton Task Service (leave empty for anonymous access)
60- `OPERATON_TASK_WORKER_PASSWORD` - Password for the Operaton Task Service (leave empty for anonymous access)
61- `OPERATON_TASK_WORKER_POLL_INTERVAL` - Interval in milliseconds for polling the Operaton Task Service for new tasks
62- `OPERATON_TASK_WORKER_ID` - The task worker id which will be registered with Operaton
63- `OPERATON_TASK_WORKER_LOCK_DURATION` - Duration in milliseconds to lock an external task when picked up by this worker (default: 60000)
64- `RUST_LOG` - Logging level for the application, e.g. `info,operaton_task_worker=debug`
65
66```ignore
67use operaton_task_worker::settings::load_config_from_env;
68
69let config = load_config_from_env("OPERATON_TASK_WORKER"); // or use any other prefix that you like
70```
71#### Using the builder pattern
72```rust
73use operaton_task_worker::settings::ConfigParams;
74use url::Url;
75
76let config = ConfigParams::default()
77    .with_url(Url::parse("http://localhost:8080").unwrap())
78    .with_auth("user".to_string(), "pass".to_string())
79    .with_poll_interval(1000)
80    .with_worker_id("operaton_task_worker".to_string())
81    .with_lock_duration(60_000);
82```
83
84### Registering a Task Handler
85
86Create a function with the `task_handler` attribute and annotate it with the name of the task to be handled.
87The function must have the following signature:
88
89```ignore
90#[task_handler(name = "ServiceTask_ID")]
91fn any_function_name(_input: &operaton_task_worker::types::InputVariables) -> Result<operaton_task_worker::types::OutputVariables, Box<dyn std::error::Error>>
92```
93
94#### Input Variables
95The input variables are a `HashMap` of `String` to `structures::ProcessInstanceVariable`.
96The values are deserialized and are statically typed according to the type of the variable.
97
98#### Returning Successful Executions
99- Return `Ok(HashMap::new())` to indicate that the task was executed successfully.
100- Return `Ok(...)` with a non-empty output variable map to indicate that the task was executed successfully and that the output variables should be updated.
101
102#### Returning errors from a handler
103- For a BPMN Business Error (Camunda 7/Operaton), return `Err(Box::new(BpmnError::new(code, message)))`.
104  The worker will call `/external-task/{id}/bpmnError`.
105- For technical failures, return any other error; the worker calls `/external-task/{id}/failure` with `retries=0`.
106
107
108**/
109
110mod polling;
111pub mod structures;
112pub mod types;
113mod api;
114pub mod registry;
115pub mod settings;
116
117pub use inventory;
118pub use operaton_task_worker_macros::task_handler;
119
120use crate::settings::ConfigParams;
121
122/// Start the polling loop asynchronously. Call this inside a Tokio runtime.
123pub async fn poll(config: ConfigParams) {
124    polling::start_polling_loop(config).await;
125}
126
127/// Convenience: start the polling loop and block the current thread until it ends (infinite loop).
128pub fn poll_blocking(config: ConfigParams) {
129    let rt = tokio::runtime::Runtime::new().expect("failed to create Tokio runtime");
130    rt.block_on(async move { polling::start_polling_loop(config).await });
131}