restate_sdk/lib.rs
1//! # Restate Rust SDK
2//!
3//! [Restate](https://restate.dev/) is a system for easily building resilient applications.
4//! This crate is the Restate SDK for writing Restate services using Rust.
5//!
6//! ## New to Restate?
7//!
8//! If you are new to Restate, we recommend the following resources:
9//!
10//! - [Learn about the concepts of Restate](https://docs.restate.dev/concepts/durable_building_blocks)
11//! - Use cases:
12//! - [Workflows](https://docs.restate.dev/use-cases/workflows)
13//! - [Microservice orchestration](https://docs.restate.dev/use-cases/microservice-orchestration)
14//! - [Event processing](https://docs.restate.dev/use-cases/event-processing)
15//! - [Async tasks](https://docs.restate.dev/use-cases/async-tasks)
16//! - [Quickstart](https://docs.restate.dev/get_started/quickstart?sdk=rust)
17//! - [Do the Tour of Restate to try out the APIs](https://docs.restate.dev/get_started/tour/?sdk=rust)
18//!
19//! # Features
20//!
21//! Have a look at the following SDK capabilities:
22//!
23//! - [SDK Overview](#sdk-overview): Overview of the SDK and how to implement services, virtual objects, and workflows.
24//! - [Service Communication][crate::context::ContextClient]: Durable RPC and messaging between services (optionally with a delay).
25//! - [Journaling Results][crate::context::ContextSideEffects]: Persist results in Restate's log to avoid re-execution on retries
26//! - State: [read][crate::context::ContextReadState] and [write](crate::context::ContextWriteState): Store and retrieve state in Restate's key-value store
27//! - [Scheduling & Timers][crate::context::ContextTimers]: Let a handler pause for a certain amount of time. Restate durably tracks the timer across failures.
28//! - [Awakeables][crate::context::ContextAwakeables]: Durable Futures to wait for events and the completion of external tasks.
29//! - [Error Handling][crate::errors]: Restate retries failures infinitely. Use `TerminalError` to stop retries.
30//! - [Serialization][crate::serde]: The SDK serializes results to send them to the Server. Includes [Schema Generation and payload metadata](crate::serde::PayloadMetadata) for documentation & discovery.
31//! - [Serving][crate::http_server]: Start an HTTP server to expose services.
32//!
33//! # SDK Overview
34//!
35//! The Restate Rust SDK lets you implement durable handlers. Handlers can be part of three types of services:
36//!
37//! - [Services](https://docs.restate.dev/concepts/services/#services-1): a collection of durable handlers
38//! - [Virtual Objects](https://docs.restate.dev/concepts/services/#virtual-objects): an object consists of a collection of durable handlers and isolated K/V state. Virtual Objects are useful for modeling stateful entities, where at most one handler can run at a time per object.
39//! - [Workflows](https://docs.restate.dev/concepts/services/#workflows): Workflows have a `run` handler that executes exactly once per workflow instance, and executes a set of steps durably. Workflows can have other handlers that can be called multiple times and interact with the workflow.
40//!
41//! ## Services
42//!
43//! [Services](https://docs.restate.dev/concepts/services/#services-1) and their handlers are defined as follows:
44//!
45//! ```rust,no_run
46//! // The prelude contains all the imports you need to get started
47//! use restate_sdk::prelude::*;
48//!
49//! // Define the service using Rust traits
50//! #[restate_sdk::service]
51//! trait MyService {
52//! async fn my_handler(greeting: String) -> Result<String, HandlerError>;
53//! }
54//!
55//! // Implement the service
56//! struct MyServiceImpl;
57//! impl MyService for MyServiceImpl {
58//!
59//! async fn my_handler(&self, ctx: Context<'_>, greeting: String) -> Result<String, HandlerError> {
60//! Ok(format!("{greeting}!"))
61//! }
62//!
63//! }
64//!
65//! // Start the HTTP server to expose services
66//! #[tokio::main]
67//! async fn main() {
68//! HttpServer::new(Endpoint::builder().bind(MyServiceImpl.serve()).build())
69//! .listen_and_serve("0.0.0.0:9080".parse().unwrap())
70//! .await;
71//! }
72//! ```
73//!
74//! - Specify that you want to create a service by using the [`#[restate_sdk::service]` macro](restate_sdk_macros::service).
75//! - Create a trait with the service handlers.
76//! - Handlers can accept zero or one parameter and return a [`Result`].
77//! - The type of the input parameter of the handler needs to implement [`Serialize`](crate::serde::Deserialize) and [`Deserialize`](crate::serde::Deserialize). See [`crate::serde`].
78//! - The Result contains the return value or a [`HandlerError`][crate::errors::HandlerError], which can be a [`TerminalError`](crate::errors::TerminalError) or any other Rust's [`std::error::Error`].
79//! - The service handler can now be called at `<RESTATE_INGRESS_URL>/MyService/myHandler`. You can optionally override the handler name used via `#[name = "myHandler"]`. More details on handler invocations can be found in the [docs](https://docs.restate.dev/invoke/http).
80//! - Implement the trait on a concrete type, for example on a struct.
81//! - The first parameter of a handler after `&self` is always a [`Context`](crate::context::Context) to interact with Restate.
82//! The SDK stores the actions you do on the context in the Restate journal to make them durable.
83//! - Finally, create an HTTP endpoint and bind the service(s) to it. Listen on the specified port (here 9080) for connections and requests.
84//!
85//! ## Virtual Objects
86//! [Virtual Objects](https://docs.restate.dev/concepts/services/#virtual-objects) and their handlers are defined similarly to services, with the following differences:
87//!
88//! ```rust,no_run
89//!use restate_sdk::prelude::*;
90//!
91//! #[restate_sdk::object]
92//! pub trait MyVirtualObject {
93//! async fn my_handler(name: String) -> Result<String, HandlerError>;
94//! #[shared]
95//! async fn my_concurrent_handler(name: String) -> Result<String, HandlerError>;
96//! }
97//!
98//! pub struct MyVirtualObjectImpl;
99//!
100//! impl MyVirtualObject for MyVirtualObjectImpl {
101//!
102//! async fn my_handler(
103//! &self,
104//! ctx: ObjectContext<'_>,
105//! greeting: String,
106//! ) -> Result<String, HandlerError> {
107//! Ok(format!("{} {}", greeting, ctx.key()))
108//! }
109//!
110//! async fn my_concurrent_handler(
111//! &self,
112//! ctx: SharedObjectContext<'_>,
113//! greeting: String,
114//! ) -> Result<String, HandlerError> {
115//! Ok(format!("{} {}", greeting, ctx.key()))
116//! }
117//!
118//! }
119//!
120//! #[tokio::main]
121//! async fn main() {
122//! HttpServer::new(
123//! Endpoint::builder()
124//! .bind(MyVirtualObjectImpl.serve())
125//! .build(),
126//! )
127//! .listen_and_serve("0.0.0.0:9080".parse().unwrap())
128//! .await;
129//! }
130//! ```
131//!
132//! - Specify that you want to create a Virtual Object by using the [`#[restate_sdk::object]` macro](restate_sdk_macros::object).
133//! - The first argument of each handler must be the [`ObjectContext`](crate::context::ObjectContext) parameter. Handlers with the `ObjectContext` parameter can write to the K/V state store. Only one handler can be active at a time per object, to ensure consistency.
134//! - You can retrieve the key of the object you are in via [`ObjectContext.key`].
135//! - If you want to have a handler that executes concurrently to the others and doesn't have write access to the K/V state, add `#[shared]` to the handler definition in the trait.
136//! Shared handlers need to use the [`SharedObjectContext`](crate::context::SharedObjectContext).
137//! You can use these handlers, for example, to read K/V state and expose it to the outside world, or to interact with the blocking handler and resolve awakeables etc.
138//!
139//! ## Workflows
140//!
141//! [Workflows](https://docs.restate.dev/concepts/services/#workflows) are a special type of Virtual Objects, their definition is similar but with the following differences:
142//!
143//! ```rust,no_run
144//! use restate_sdk::prelude::*;
145//!
146//! #[restate_sdk::workflow]
147//! pub trait MyWorkflow {
148//! async fn run(req: String) -> Result<String, HandlerError>;
149//! #[shared]
150//! async fn interact_with_workflow() -> Result<(), HandlerError>;
151//! }
152//!
153//! pub struct MyWorkflowImpl;
154//!
155//! impl MyWorkflow for MyWorkflowImpl {
156//!
157//! async fn run(&self, ctx: WorkflowContext<'_>, req: String) -> Result<String, HandlerError> {
158//! //! implement workflow logic here
159//!
160//! Ok(String::from("success"))
161//! }
162//!
163//! async fn interact_with_workflow(&self, ctx: SharedWorkflowContext<'_>) -> Result<(), HandlerError> {
164//! //! implement interaction logic here
165//! //! e.g. resolve a promise that the workflow is waiting on
166//!
167//! Ok(())
168//! }
169//!
170//! }
171//!
172//! #[tokio::main]
173//! async fn main() {
174//! HttpServer::new(Endpoint::builder().bind(MyWorkflowImpl.serve()).build())
175//! .listen_and_serve("0.0.0.0:9080".parse().unwrap())
176//! .await;
177//! }
178//! ```
179//!
180//! - Specify that you want to create a Workflow by using the [`#[restate_sdk::workflow]` macro](workflow).
181//! - The workflow needs to have a `run` handler.
182//! - The first argument of the `run` handler must be the [`WorkflowContext`](crate::context::WorkflowContext) parameter.
183//! The `WorkflowContext` parameter is used to interact with Restate.
184//! The `run` handler executes exactly once per workflow instance.
185//! - The other handlers of the workflow are used to interact with the workflow: either query it, or signal it.
186//! They use the [`SharedWorkflowContext`](crate::context::SharedWorkflowContext) to interact with the SDK.
187//! These handlers can run concurrently with the run handler and can still be called after the run handler has finished.
188//! - Have a look at the [workflow docs](workflow) to learn more.
189//!
190//!
191//! Learn more about each service type here:
192//! - [Service](restate_sdk_macros::service)
193//! - [Virtual Object](object)
194//! - [Workflow](workflow)
195//!
196//!
197//! ### Logging
198//!
199//! This crate uses the [tracing crate][tracing] to emit logs, so you'll need to configure a tracing subscriber to get logs. For example, to configure console logging using `tracing_subscriber::fmt`:
200//! ```rust,no_run
201//! #[tokio::main]
202//! async fn main() {
203//! //! To enable logging
204//! tracing_subscriber::fmt::init();
205//!
206//! // Start http server etc...
207//! }
208//! ```
209//!
210//! You can filter logs *when a handler is being replayed* configuring the [filter::ReplayAwareFilter].
211//!
212//! For more information about tracing and logging, have a look at the [tracing subscriber doc](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/index.html#filtering-events-with-environment-variables).
213//!
214//! Next, have a look at the other [SDK features](#features).
215//!
216
217pub mod endpoint;
218pub mod service;
219
220pub mod context;
221pub mod discovery;
222pub mod errors;
223#[cfg(feature = "tracing-span-filter")]
224pub mod filter;
225#[cfg(feature = "http_server")]
226pub mod http_server;
227#[cfg(feature = "hyper")]
228pub mod hyper;
229#[cfg(feature = "lambda")]
230pub mod lambda;
231pub mod serde;
232
233/// Entry-point macro to define a Restate [Service](https://docs.restate.dev/concepts/services#services-1).
234///
235/// ```rust,no_run
236/// use restate_sdk::prelude::*;
237///
238/// #[restate_sdk::service]
239/// trait Greeter {
240/// async fn greet(name: String) -> Result<String, HandlerError>;
241/// }
242/// ```
243///
244/// This macro accepts a `trait` as input, and generates as output:
245///
246/// * A trait with the same name, that you should implement on your own concrete type (e.g. `struct`), e.g.:
247///
248/// ```rust,no_run
249/// # use restate_sdk::prelude::*;
250/// # #[restate_sdk::service]
251/// # trait Greeter {
252/// # async fn greet(name: String) -> Result<String, HandlerError>;
253/// # }
254/// struct GreeterImpl;
255/// impl Greeter for GreeterImpl {
256/// async fn greet(&self, _: Context<'_>, name: String) -> Result<String, HandlerError> {
257/// Ok(format!("Greetings {name}"))
258/// }
259/// }
260/// ```
261///
262/// This trait will additionally contain, for each handler, the appropriate [`Context`](crate::prelude::Context), to interact with Restate.
263///
264/// * An implementation of the [`Service`](crate::service::Service) trait, to bind the service in the [`Endpoint`](crate::prelude::Endpoint) and expose it:
265///
266/// ```rust,no_run
267/// # use restate_sdk::prelude::*;
268/// # #[restate_sdk::service]
269/// # trait Greeter {
270/// # async fn greet(name: String) -> HandlerResult<String>;
271/// # }
272/// # struct GreeterImpl;
273/// # impl Greeter for GreeterImpl {
274/// # async fn greet(&self, _: Context<'_>, name: String) -> HandlerResult<String> {
275/// # Ok(format!("Greetings {name}"))
276/// # }
277/// # }
278/// let endpoint = Endpoint::builder()
279/// // .serve() returns the implementation of Service used by the SDK
280/// // to bind your struct to the endpoint
281/// .bind(GreeterImpl.serve())
282/// .build();
283/// ```
284///
285/// * A client implementation to call this service from another service, object or workflow, e.g.:
286///
287/// ```rust,no_run
288/// # use restate_sdk::prelude::*;
289/// # #[restate_sdk::service]
290/// # trait Greeter {
291/// # async fn greet(name: String) -> HandlerResult<String>;
292/// # }
293/// # async fn example(ctx: Context<'_>) -> Result<(), TerminalError> {
294/// let result = ctx
295/// .service_client::<GreeterClient>()
296/// .greet("My greetings".to_string())
297/// .call()
298/// .await?;
299/// # Ok(())
300/// # }
301/// ```
302///
303/// Methods of this trait can accept either no parameter, or one parameter implementing [`Deserialize`](crate::serde::Deserialize).
304/// The return value MUST always be a `Result`. Down the hood, the error type is always converted to [`HandlerError`](crate::prelude::HandlerError) for the SDK to distinguish between terminal and retryable errors. For more details, check the [`HandlerError`](crate::prelude::HandlerError) doc.
305///
306/// When invoking the service through Restate, the method name should be used as handler name, that is:
307///
308/// ```rust,no_run
309/// use restate_sdk::prelude::*;
310///
311/// #[restate_sdk::service]
312/// trait Greeter {
313/// async fn my_greet(name: String) -> Result<String, HandlerError>;
314/// }
315/// ```
316///
317/// The `Greeter/my_greet` handler be invoked sending a request to `http://<RESTATE_ENDPOINT>/Greeter/my_greet`.
318/// You can override the names used by Restate during registration using the `name` attribute:
319///
320/// ```rust,no_run
321/// use restate_sdk::prelude::*;
322///
323/// #[restate_sdk::service]
324/// #[name = "greeter"]
325/// trait Greeter {
326/// // You can invoke this handler with `http://<RESTATE_ENDPOINT>/greeter/myGreet`
327/// #[name = "myGreet"]
328/// async fn my_greet(name: String) -> Result<String, HandlerError>;
329/// }
330/// ```
331pub use restate_sdk_macros::service;
332
333/// Entry-point macro to define a Restate [Virtual object](https://docs.restate.dev/concepts/services#virtual-objects).
334///
335/// For more details, check the [`service` macro](macro@crate::service) documentation.
336///
337/// ## Shared handlers
338///
339/// To define a shared handler, simply annotate the handler with the `#[shared]` annotation:
340///
341/// ```rust,no_run
342/// use restate_sdk::prelude::*;
343///
344/// #[restate_sdk::object]
345/// trait Counter {
346/// async fn add(val: u64) -> Result<u64, TerminalError>;
347/// #[shared]
348/// async fn get() -> Result<u64, TerminalError>;
349/// }
350/// ```
351pub use restate_sdk_macros::object;
352
353///
354/// # Workflows
355///
356/// Entry-point macro to define a Restate [Workflow](https://docs.restate.dev/concepts/services#workflows).
357///
358/// [Workflows](https://docs.restate.dev/concepts/services#workflows) are a sequence of steps that gets executed durably.
359///
360/// A workflow can be seen as a special type of [Virtual Object](https://docs.restate.dev/concepts/services#virtual-objects) with the following characteristics:
361///
362/// - Each workflow definition has a **`run` handler** that implements the workflow logic.
363/// - The `run` handler **executes exactly one time** for each workflow instance (object / key).
364/// - The `run` handler executes a set of **durable steps/activities**. These can either be:
365/// - Inline activities: for example a [run block](crate::context::ContextSideEffects) or [sleep](crate::context::ContextTimers)
366/// - [Calls to other handlers](crate::context::ContextClient) implementing the activities
367/// - You can **submit a workflow** in the same way as any handler invocation (via SDK clients or Restate services, over HTTP or Kafka).
368/// - A workflow definition can implement other handlers that can be called multiple times, and can **interact with the workflow**:
369/// - Query the workflow (get information out of it) by getting K/V state or awaiting promises that are resolved by the workflow.
370/// - Signal the workflow (send information to it) by resolving promises that the workflow waits on.
371/// - Workflows have access to the [`WorkflowContext`](crate::context::WorkflowContext) and [`SharedWorkflowContext`](crate::context::SharedWorkflowContext), giving them some extra functionality, for example [Durable Promises](#signaling-workflows) to signal workflows.
372/// - The K/V state of the workflow is isolated to the workflow execution, and can only be mutated by the `run` handler.
373///
374/// **Note: Workflow retention time**:
375/// The retention time of a workflow execution is 24 hours after the finishing of the `run` handler.
376/// After this timeout any [K/V state][crate::context::ContextReadState] is cleared, the workflow's shared handlers cannot be called anymore, and the Durable Promises are discarded.
377/// The retention time can be configured via the [Admin API](https://docs.restate.dev/references/admin-api/#tag/service/operation/modify_service) per Workflow definition by setting `workflow_completion_retention`.
378///
379/// ## Implementing workflows
380/// Have a look at the code example to get a better understanding of how workflows are implemented:
381///
382/// ```rust,no_run
383/// use restate_sdk::prelude::*;
384///
385/// #[restate_sdk::workflow]
386/// pub trait SignupWorkflow {
387/// async fn run(req: String) -> Result<bool, HandlerError>;
388/// #[shared]
389/// async fn click(click_secret: String) -> Result<(), HandlerError>;
390/// #[shared]
391/// async fn get_status() -> Result<String, HandlerError>;
392/// }
393///
394/// pub struct SignupWorkflowImpl;
395///
396/// impl SignupWorkflow for SignupWorkflowImpl {
397///
398/// async fn run(&self, mut ctx: WorkflowContext<'_>, email: String) -> Result<bool, HandlerError> {
399/// let secret = ctx.rand_uuid().to_string();
400/// ctx.run(|| send_email_with_link(email.clone(), secret.clone())).await?;
401/// ctx.set("status", "Email sent".to_string());
402///
403/// let click_secret = ctx.promise::<String>("email.clicked").await?;
404/// ctx.set("status", "Email clicked".to_string());
405///
406/// Ok(click_secret == secret)
407/// }
408///
409/// async fn click(&self, ctx: SharedWorkflowContext<'_>, click_secret: String) -> Result<(), HandlerError> {
410/// ctx.resolve_promise::<String>("email.clicked", click_secret);
411/// Ok(())
412/// }
413///
414/// async fn get_status(&self, ctx: SharedWorkflowContext<'_>) -> Result<String, HandlerError> {
415/// Ok(ctx.get("status").await?.unwrap_or("unknown".to_string()))
416/// }
417///
418/// }
419/// # async fn send_email_with_link(email: String, secret: String) -> Result<(), HandlerError> {
420/// # Ok(())
421/// # }
422///
423/// #[tokio::main]
424/// async fn main() {
425/// HttpServer::new(Endpoint::builder().bind(SignupWorkflowImpl.serve()).build())
426/// .listen_and_serve("0.0.0.0:9080".parse().unwrap())
427/// .await;
428/// }
429/// ```
430///
431/// ### The run handler
432///
433/// Every workflow needs a `run` handler.
434/// This handler has access to the same SDK features as Service and Virtual Object handlers.
435/// In the example above, we use [`ctx.run`][crate::context::ContextSideEffects::run] to log the sending of the email in Restate and avoid re-execution on replay.
436/// Or call other handlers to execute activities.
437///
438/// ## Shared handlers
439///
440/// To define a shared handler, simply annotate the handler with the `#[shared]` annotation:
441///
442/// ### Querying workflows
443///
444/// Similar to Virtual Objects, you can retrieve the [K/V state][crate::context::ContextReadState] of workflows via the other handlers defined in the workflow definition,
445/// In the example we expose the status of the workflow to external clients.
446/// Every workflow execution can be seen as a new object, so the state is isolated to a single workflow execution.
447/// The state can only be mutated by the `run` handler of the workflow. The other handlers can only read the state.
448///
449/// ### Signaling workflows
450///
451/// You can use Durable Promises to interact with your running workflows: to let the workflow block until an event occurs, or to send a signal / information into or out of a running workflow.
452/// These promises are durable and distributed, meaning they survive crashes and can be resolved or rejected by any handler in the workflow.
453///
454/// Do the following:
455/// 1. Create a promise that is durable and distributed in the `run` handler, and wait for its completion. In the example, we wait on the promise `email.clicked`.
456/// 2. Resolve or reject the promise in another handler in the workflow. This can be done at most one time.
457/// In the example, the `click` handler gets called when the user clicks a link in an email and resolves the `email.clicked` promise.
458///
459/// You can also use this pattern in reverse and let the `run` handler resolve promises that other handlers are waiting on.
460/// For example, the `run` handler could resolve a promise when it finishes a step of the workflow, so that other handlers can request whether this step has been completed.
461///
462/// ### Serving and registering workflows
463///
464/// You serve workflows in the same way as Services and Virtual Objects. Have a look at the [Serving docs][crate::http_server].
465/// Make sure you [register the endpoint or Lambda handler](https://docs.restate.dev/operate/registration) in Restate before invoking it.
466///
467/// **Tip: Workflows-as-code with Restate**:
468/// [Check out some examples of workflows-as-code with Restate on the use case page](https://docs.restate.dev/use-cases/workflows).
469///
470///
471/// ## Submitting workflows from a Restate service
472/// [**Submit/query/signal**][crate::context::ContextClient]:
473/// Call the workflow handlers in the same way as for Services and Virtual Objects.
474/// You can only call the `run` handler (submit) once per workflow ID (here `"someone"`).
475/// Check out the [Service Communication docs][crate::context::ContextClient] for more information.
476///
477/// ## Submitting workflows over HTTP
478/// [**Submit/query/signal**](https://docs.restate.dev/invoke/http#request-response-calls-over-http):
479/// Call any handler of the workflow in the same way as for Services and Virtual Objects.
480/// This returns the result of the handler once it has finished.
481/// Add `/send` to the path for one-way calls.
482/// You can only call the `run` handler once per workflow ID (here `"someone"`).
483///
484/// ```shell
485/// curl localhost:8080/SignupWorkflow/someone/run \
486/// -H 'content-type: application/json' \
487/// -d '"someone@restate.dev"'
488/// ```
489///
490/// [**Attach/peek**](https://docs.restate.dev/invoke/http#retrieve-result-of-invocations-and-workflows):
491/// This lets you retrieve the result of a workflow or check if it's finished.
492///
493/// ```shell
494/// curl localhost:8080/restate/workflow/SignupWorkflow/someone/attach
495/// curl localhost:8080/restate/workflow/SignupWorkflow/someone/output
496/// ```
497///
498/// ## Inspecting workflows
499///
500/// Have a look at the [introspection docs](https://docs.restate.dev/operate/introspection) on how to inspect workflows.
501/// You can use this to for example:
502/// - [Inspect the progress of a workflow by looking at the invocation journal](https://docs.restate.dev/operate/introspection#inspecting-the-invocation-journal)
503/// - [Inspect the K/V state of a workflow](https://docs.restate.dev/operate/introspection#inspecting-application-state)
504///
505///
506/// For more details, check the [`service` macro](macro@crate::service) documentation.
507pub use restate_sdk_macros::workflow;
508
509/// Prelude contains all the useful imports you need to get started with Restate.
510pub mod prelude {
511 #[cfg(feature = "http_server")]
512 pub use crate::http_server::HttpServer;
513
514 #[cfg(feature = "lambda")]
515 pub use crate::lambda::LambdaEndpoint;
516
517 pub use crate::context::{
518 CallFuture, Context, ContextAwakeables, ContextClient, ContextPromises, ContextReadState,
519 ContextSideEffects, ContextTimers, ContextWriteState, HeaderMap, InvocationHandle,
520 ObjectContext, Request, RunFuture, RunRetryPolicy, SharedObjectContext,
521 SharedWorkflowContext, WorkflowContext,
522 };
523 pub use crate::endpoint::{Endpoint, HandlerOptions, ServiceOptions};
524 pub use crate::errors::{HandlerError, HandlerResult, TerminalError};
525 pub use crate::serde::Json;
526}