Skip to main content

temporalio_client/
options_structs.rs

1use crate::{
2    ClientInterceptor, HttpConnectProxyOptions, RetryOptions, RpcOptions, VERSION, callback_based,
3};
4#[cfg(feature = "experimental")]
5use crate::{ClientPlugin, ErasedClientPlugin};
6use http::Uri;
7use std::{collections::HashMap, sync::Arc, time::Duration};
8use temporalio_common::{
9    ActivityCloseTimeouts, MemoValues, RetryPolicy,
10    data_converters::{
11        DataConverter, GenericPayloadConverter, PayloadConversionError, PayloadConverter,
12        SerializationContext, SerializationContextData, WorkflowSerializationContext,
13    },
14    payload_visitor::encode_payloads,
15    protos::temporal::api::{
16        common::{
17            self,
18            v1::{Header, Memo as ProtoMemo, Payloads},
19        },
20        enums::v1::{
21            ActivityIdConflictPolicy as ProtoActivityIdConflictPolicy,
22            ActivityIdReusePolicy as ProtoActivityIdReusePolicy,
23            ArchivalState as ProtoArchivalState,
24            HistoryEventFilterType as ProtoHistoryEventFilterType,
25            QueryRejectCondition as ProtoQueryRejectCondition,
26            WorkflowIdConflictPolicy as ProtoWorkflowIdConflictPolicy,
27            WorkflowIdReusePolicy as ProtoWorkflowIdReusePolicy,
28        },
29        replication::v1::ClusterReplicationConfig,
30        sdk::v1::UserMetadata,
31        workflowservice::v1::RegisterNamespaceRequest,
32    },
33    search_attributes::SearchAttributes,
34    telemetry::metrics::TemporalMeter,
35};
36#[cfg(feature = "dynamic-tls")]
37use tokio_rustls::rustls::client::ResolvesClientCert;
38use tokio_rustls::rustls::client::danger::ServerCertVerifier;
39use url::Url;
40
41pub(crate) const DEFAULT_PAYLOADS_WARN_SIZE: u64 = 512 * 1024;
42pub(crate) const DEFAULT_MEMO_WARN_SIZE: u64 = 2 * 1024;
43
44/// Options for [crate::Connection::connect].
45#[derive(bon::Builder, Clone, Debug)]
46#[non_exhaustive]
47#[builder(start_fn = new, on(String, into), state_mod(vis = "pub"))]
48pub struct ConnectionOptions {
49    /// The server to connect to.
50    #[builder(start_fn, into)]
51    pub target: Url,
52    /// A human-readable string that can identify this process. Defaults to empty string.
53    #[builder(default)]
54    pub identity: String,
55    /// When set, this client will record metrics using the provided meter. The meter can be
56    /// obtained from [temporalio_common::telemetry::TelemetryInstance::get_temporal_metric_meter].
57    pub metrics_meter: Option<TemporalMeter>,
58    /// If specified, use TLS as configured by the [TlsOptions] struct. If this is set core will
59    /// attempt to use TLS when connecting to the Temporal server. Lang SDK is expected to pass any
60    /// certs or keys as bytes, loading them from disk itself if needed.
61    pub tls_options: Option<TlsOptions>,
62    /// If set, override the origin used when connecting. May be useful in rare situations where tls
63    /// verification needs to use a different name from what should be set as the `:authority`
64    /// header. If [TlsOptions::domain] is set, and this is not, this will be set to
65    /// `https://<domain>`, effectively making the `:authority` header consistent with the domain
66    /// override.
67    pub override_origin: Option<Uri>,
68    /// An API key to use for auth. If set, TLS will be enabled by default, but without any mTLS
69    /// specific settings.
70    pub api_key: Option<String>,
71    /// When set, limits the time allowed to establish the initial TCP/TLS connection to the
72    /// server. If the connection cannot be established within this duration, `connect` will
73    /// return an error. When `None` (the default), no explicit timeout is applied and the
74    /// connection attempt may block indefinitely (subject to OS-level TCP timeouts).
75    pub connect_timeout: Option<Duration>,
76    /// Retry configuration for the server client. Default is [RetryOptions::default]
77    #[builder(default)]
78    pub retry_options: RetryOptions,
79    /// If set, HTTP2 gRPC keep alive will be enabled.
80    /// To enable with default settings, use `.keep_alive(Some(ClientKeepAliveConfig::default()))`.
81    #[builder(required, default = Some(ClientKeepAliveOptions::default()))]
82    pub keep_alive: Option<ClientKeepAliveOptions>,
83    /// HTTP headers to include on every RPC call.
84    ///
85    /// These must be valid gRPC metadata keys, and must not be binary metadata keys (ending in
86    /// `-bin). To set binary headers, use [ConnectionOptions::binary_headers]. Invalid header keys
87    /// or values will cause an error to be returned when connecting.
88    pub headers: Option<HashMap<String, String>>,
89    /// HTTP headers to include on every RPC call as binary gRPC metadata (encoded as base64).
90    ///
91    /// These must be valid binary gRPC metadata keys (and end with a `-bin` suffix). Invalid
92    /// header keys will cause an error to be returned when connecting.
93    pub binary_headers: Option<HashMap<String, Vec<u8>>>,
94    /// HTTP CONNECT proxy to use for this client.
95    pub http_connect_proxy: Option<HttpConnectProxyOptions>,
96    /// If set, DNS-based load balancing is enabled. When the target is a hostname (not an IP
97    /// literal), DNS is resolved to all addresses and requests are distributed across them.
98    /// Incompatible with `service_override` and `http_connect_proxy`. Setting either in addition
99    /// to this field is an error. Set to `None` to disable.
100    #[builder(required, default = Some(DnsLoadBalancingOptions::default()))]
101    pub dns_load_balancing: Option<DnsLoadBalancingOptions>,
102    /// If set true, error code labels will not be included on request failure metrics.
103    #[builder(default)]
104    pub disable_error_code_metric_tags: bool,
105    /// If set, all gRPC calls will be routed through the provided service.
106    pub service_override: Option<callback_based::CallbackBasedGrpcService>,
107    /// Controls transport-level gRPC compression for the client. Defaults to
108    /// [GrpcCompression::Gzip], which compresses outbound request bodies and accepts
109    /// compressed responses. Set to [GrpcCompression::None] to opt out.
110    /// If service_override is specified, is forced to `None`.
111    #[builder(default)]
112    pub grpc_compression: GrpcCompression,
113    /// Payload size limit options for this connection. Defaults to the standard warning thresholds;
114    /// disable an individual warning by setting its threshold to `0`.
115    /// NOTE: Experimental
116    #[cfg(feature = "experimental")]
117    #[cfg_attr(
118        docsrs,
119        builder(setters(
120            some_fn(name = payload_limits_impl, vis = "pub(crate)"),
121            option_fn(name = maybe_payload_limits_impl, vis = "pub(crate)")
122        ))
123    )]
124    #[builder(default)]
125    pub payload_limits: PayloadLimitsOptions,
126
127    // Internal / Core-based SDK only options below =============================================
128    /// If set true, get_system_info will not be called upon connection.
129    #[builder(default)]
130    #[cfg_attr(feature = "core-based-sdk", builder(setters(vis = "pub")))]
131    pub(crate) skip_get_system_info: bool,
132    /// The name of the SDK being implemented on top of core. Is set as `client-name` header in
133    /// all RPC calls
134    #[builder(default = "temporal-rust".to_owned())]
135    #[cfg_attr(feature = "core-based-sdk", builder(setters(vis = "pub")))]
136    pub(crate) client_name: String,
137    // TODO [rust-sdk-branch]: SDK should set this to its version. Doing that probably easiest
138    // after adding proper client interceptors.
139    /// The version of the SDK being implemented on top of core. Is set as `client-version` header
140    /// in all RPC calls. The server decides if the client is supported based on this.
141    #[builder(default = VERSION.to_owned())]
142    #[cfg_attr(feature = "core-based-sdk", builder(setters(vis = "pub")))]
143    pub(crate) client_version: String,
144}
145
146// Bon does not propagate `doc(cfg)` to generated setters, so these docs-only methods forward to
147// renamed generated implementations.
148#[cfg(all(feature = "experimental", docsrs))]
149impl<S: connection_options_builder::State> ConnectionOptionsBuilder<S> {
150    /// Set the payload size limit options for this connection.
151    #[doc(cfg(feature = "experimental"))]
152    pub fn payload_limits(
153        self,
154        value: PayloadLimitsOptions,
155    ) -> ConnectionOptionsBuilder<connection_options_builder::SetPayloadLimits<S>>
156    where
157        S::PayloadLimits: connection_options_builder::IsUnset,
158    {
159        self.payload_limits_impl(value)
160    }
161
162    /// Set the payload size limit options for this connection from an optional value.
163    #[doc(cfg(feature = "experimental"))]
164    pub fn maybe_payload_limits(
165        self,
166        value: Option<PayloadLimitsOptions>,
167    ) -> ConnectionOptionsBuilder<connection_options_builder::SetPayloadLimits<S>>
168    where
169        S::PayloadLimits: connection_options_builder::IsUnset,
170    {
171        self.maybe_payload_limits_impl(value)
172    }
173}
174
175// Setters/getters for fields that should only be touched by SDK implementers.
176#[cfg(feature = "core-based-sdk")]
177impl ConnectionOptions {
178    /// Set whether or not get_system_info will be called upon connection.
179    pub fn set_skip_get_system_info(&mut self, skip: bool) {
180        self.skip_get_system_info = skip;
181    }
182    /// Get whether or not get_system_info will be called upon connection.
183    pub fn get_skip_get_system_info(&self) -> bool {
184        self.skip_get_system_info
185    }
186    /// Get the name of the SDK being implemented on top of core.
187    pub fn get_client_name(&self) -> &str {
188        &self.client_name
189    }
190    /// Get the version of the SDK being implemented on top of core.
191    pub fn get_client_version(&self) -> &str {
192        &self.client_version
193    }
194}
195
196/// Options for [crate::Client::new].
197#[derive(Clone, derive_more::Debug, bon::Builder)]
198#[non_exhaustive]
199#[builder(start_fn = new, on(String, into), state_mod(vis = "pub"))]
200pub struct ClientOptions {
201    /// The namespace this client will be bound to.
202    #[builder(start_fn)]
203    pub namespace: String,
204
205    #[builder(field)]
206    #[debug(skip)]
207    #[cfg(feature = "experimental")]
208    plugins: Vec<ErasedClientPlugin>,
209
210    #[builder(field)]
211    #[debug(skip)]
212    #[cfg(feature = "experimental")]
213    client_plugins_applied: bool,
214
215    /// The data converter used for serializing/deserializing payloads.
216    #[builder(default)]
217    pub data_converter: DataConverter,
218    /// Interceptors for high-level client operations, ordered outermost to innermost.
219    #[builder(default)]
220    #[debug(skip)]
221    pub client_interceptors: Vec<Arc<dyn ClientInterceptor>>,
222}
223
224#[cfg(feature = "experimental")]
225impl<S: client_options_builder::State> ClientOptionsBuilder<S> {
226    /// Register a type-erased client plugin.
227    ///
228    /// **Experimental:** This API may change or be removed.
229    pub fn plugin<P: Into<ErasedClientPlugin>>(mut self, plugin: P) -> Self {
230        self.plugins.push(plugin.into());
231        self
232    }
233
234    /// Register type-erased client plugins in iteration order.
235    ///
236    /// **Experimental:** This API may change or be removed.
237    pub fn plugins<I, P>(mut self, plugins: I) -> Self
238    where
239        I: IntoIterator<Item = P>,
240        P: Into<ErasedClientPlugin>,
241    {
242        self.plugins.extend(plugins.into_iter().map(Into::into));
243        self
244    }
245
246    /// Register a client-only plugin.
247    ///
248    /// **Experimental:** This API may change or be removed.
249    pub fn client_plugin<P: ClientPlugin>(mut self, plugin: P) -> Self {
250        self.plugins.push(ErasedClientPlugin::new(plugin));
251        self
252    }
253}
254
255impl ClientOptions {
256    /// Return the registered plugins.
257    ///
258    /// This is intended for SDK integrations that propagate worker plugin registrations.
259    ///
260    /// **Experimental:** This API may change or be removed.
261    #[cfg(feature = "experimental")]
262    pub fn plugins(&self) -> &[ErasedClientPlugin] {
263        &self.plugins
264    }
265
266    #[cfg(feature = "experimental")]
267    pub(crate) fn client_plugins_applied(&self) -> bool {
268        self.client_plugins_applied
269    }
270
271    #[cfg(feature = "experimental")]
272    pub(crate) fn mark_client_plugins_applied(&mut self) {
273        self.client_plugins_applied = true;
274    }
275}
276
277/// Selects the transport-level compression used for gRPC calls. See
278/// [ConnectionOptions::grpc_compression].
279#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
280#[non_exhaustive]
281pub enum GrpcCompression {
282    /// Do not compress requests or advertise acceptance of compressed responses.
283    None,
284    /// Gzip-compress outbound requests and accept gzip-compressed responses.
285    #[default]
286    Gzip,
287}
288
289/// Configuration options for TLS
290#[derive(Clone, bon::Builder)]
291#[non_exhaustive]
292pub struct TlsOptions {
293    /// Bytes representing the root CA certificate used by the server. If not set, and the server's
294    /// cert is issued by someone the operating system trusts, verification will still work (ex:
295    /// Cloud offering).
296    pub server_root_ca_cert: Option<Vec<u8>>,
297    /// Sets the domain name against which to verify the server's TLS certificate. If not provided,
298    /// the domain name will be extracted from the URL used to connect.
299    pub domain: Option<String>,
300    /// TLS info for the client. If specified, core will attempt to use mTLS.
301    ///
302    /// Mutually exclusive with [`client_cert_resolver`](TlsOptions::client_cert_resolver).
303    /// Setting both is an error.
304    pub client_tls_options: Option<ClientTlsOptions>,
305    /// Optional custom server certificate verifier. When set, this replaces the default
306    /// certificate verification and `server_root_ca_cert` is ignored.
307    ///
308    /// This is useful for:
309    /// - Certificate pinning
310    /// - Custom trust-domain validation (e.g., SAN-URI extraction)
311    /// - Federated root certificate stores
312    ///
313    /// # WARNING
314    /// Implementing a custom `ServerCertVerifier` can lead to severely insecure TLS connections
315    /// (e.g., disabling all validation or allowing man-in-the-middle attacks) if not done carefully.
316    /// Only use this if you know exactly what you are doing.
317    ///
318    /// The verifier must implement [`ServerCertVerifier`] from the `rustls` crate.
319    /// Note that `domain` is still respected for the `:authority` header / origin override
320    /// even when a custom verifier is set.
321    pub server_cert_verifier: Option<Arc<dyn ServerCertVerifier>>,
322    /// Optional dynamic client certificate resolver for transparent mTLS certificate rotation.
323    ///
324    /// Mutually exclusive with [`client_tls_options`](TlsOptions::client_tls_options).
325    /// Setting both is an error.
326    #[cfg(feature = "dynamic-tls")]
327    pub client_cert_resolver: Option<Arc<dyn ResolvesClientCert>>,
328}
329
330impl Default for TlsOptions {
331    fn default() -> Self {
332        Self::builder().build()
333    }
334}
335
336impl std::fmt::Debug for TlsOptions {
337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338        let mut s = f.debug_struct("TlsOptions");
339        s.field(
340            "server_root_ca_cert",
341            &self
342                .server_root_ca_cert
343                .as_ref()
344                .map(|c| format!("{} bytes", c.len())),
345        );
346        s.field("domain", &self.domain);
347        s.field("client_tls_options", &self.client_tls_options);
348        s.field(
349            "server_cert_verifier",
350            &self.server_cert_verifier.as_ref().map(|_| "<custom>"),
351        );
352        #[cfg(feature = "dynamic-tls")]
353        s.field(
354            "client_cert_resolver",
355            &self.client_cert_resolver.as_ref().map(|_| "<custom>"),
356        );
357        s.finish()
358    }
359}
360
361/// If using mTLS, both the client cert and private key must be specified, this contains them.
362#[derive(Clone, bon::Builder)]
363#[non_exhaustive]
364pub struct ClientTlsOptions {
365    /// The certificate for this client, encoded as PEM
366    pub client_cert: Vec<u8>,
367    /// The private key for this client, encoded as PEM
368    pub client_private_key: Vec<u8>,
369}
370
371/// Client keep alive configuration.
372#[derive(Clone, Debug, PartialEq, bon::Builder)]
373#[non_exhaustive]
374pub struct ClientKeepAliveOptions {
375    /// Interval to send HTTP2 keep alive pings.
376    #[builder(default = Duration::from_secs(30))]
377    pub interval: Duration,
378    /// Timeout that the keep alive must be responded to within or the connection will be closed.
379    #[builder(default = Duration::from_secs(15))]
380    pub timeout: Duration,
381}
382
383impl Default for ClientKeepAliveOptions {
384    fn default() -> Self {
385        Self::builder().build()
386    }
387}
388
389/// Options for DNS-based load balancing.
390#[derive(Clone, Debug, PartialEq, bon::Builder)]
391#[non_exhaustive]
392pub struct DnsLoadBalancingOptions {
393    /// How often to re-resolve DNS. Defaults to 30 seconds.
394    #[builder(default = Duration::from_secs(30))]
395    pub resolution_interval: Duration,
396}
397
398impl Default for DnsLoadBalancingOptions {
399    fn default() -> Self {
400        Self::builder().build()
401    }
402}
403
404/// Payload size limit options for a connection.
405/// NOTE: Experimental
406#[cfg(feature = "experimental")]
407#[derive(Clone, Debug, PartialEq, bon::Builder)]
408#[non_exhaustive]
409pub struct PayloadLimitsOptions {
410    /// Warning threshold (bytes) for the size of an outbound payload-bearing field; over-threshold
411    /// fields are logged but still sent to server. Defaults to 512 KiB. Set to `0` to disable.
412    #[builder(default = DEFAULT_PAYLOADS_WARN_SIZE)]
413    pub payloads_warn_size: u64,
414    /// Warning threshold (bytes) for outbound memo sizes; over-threshold memos are logged but still
415    /// sent to server. Defaults to 2 KiB. Set to `0` to disable.
416    #[builder(default = DEFAULT_MEMO_WARN_SIZE)]
417    pub memo_warn_size: u64,
418}
419
420#[cfg(feature = "experimental")]
421impl Default for PayloadLimitsOptions {
422    fn default() -> Self {
423        Self::builder().build()
424    }
425}
426
427impl std::fmt::Debug for ClientTlsOptions {
428    // Intentionally omit details here since they could leak a key if ever printed
429    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430        write!(f, "ClientTlsOptions(..)")
431    }
432}
433
434/// Controls whether a closed workflow ID may be reused.
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
436#[non_exhaustive]
437pub enum WorkflowIdReusePolicy {
438    /// Use the server's default policy.
439    #[default]
440    Unspecified,
441    /// Allow starting a workflow using the same workflow ID.
442    AllowDuplicate,
443    /// Allow reuse only when the previous execution did not complete successfully.
444    AllowDuplicateFailedOnly,
445    /// Reject reuse of the workflow ID.
446    RejectDuplicate,
447}
448
449impl From<WorkflowIdReusePolicy> for ProtoWorkflowIdReusePolicy {
450    fn from(value: WorkflowIdReusePolicy) -> Self {
451        match value {
452            WorkflowIdReusePolicy::Unspecified => Self::Unspecified,
453            WorkflowIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
454            WorkflowIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
455            WorkflowIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
456        }
457    }
458}
459
460/// Controls how starting a workflow resolves a conflict with a running workflow using the same
461/// workflow ID.
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
463#[non_exhaustive]
464pub enum WorkflowIdConflictPolicy {
465    /// Use the server's default policy.
466    #[default]
467    Unspecified,
468    /// Do not start a new workflow and return an already-started error.
469    Fail,
470    /// Do not start a new workflow and return a handle for the running workflow.
471    UseExisting,
472    /// Terminate the running workflow before starting a new one.
473    TerminateExisting,
474}
475
476impl From<WorkflowIdConflictPolicy> for ProtoWorkflowIdConflictPolicy {
477    fn from(value: WorkflowIdConflictPolicy) -> Self {
478        match value {
479            WorkflowIdConflictPolicy::Unspecified => Self::Unspecified,
480            WorkflowIdConflictPolicy::Fail => Self::Fail,
481            WorkflowIdConflictPolicy::UseExisting => Self::UseExisting,
482            WorkflowIdConflictPolicy::TerminateExisting => Self::TerminateExisting,
483        }
484    }
485}
486
487/// Options for starting a workflow execution.
488#[derive(Debug, Clone, bon::Builder)]
489#[builder(start_fn = new, on(String, into))]
490#[non_exhaustive]
491pub struct WorkflowStartOptions {
492    /// The task queue to run the workflow on.
493    #[builder(start_fn)]
494    pub task_queue: String,
495
496    /// The workflow ID.
497    #[builder(start_fn)]
498    pub workflow_id: String,
499
500    /// Set the policy for reusing the workflow id
501    #[builder(default)]
502    pub id_reuse_policy: WorkflowIdReusePolicy,
503
504    /// Set the policy for how to resolve conflicts with running policies.
505    /// NOTE: This is ignored for child workflows.
506    #[builder(default)]
507    pub id_conflict_policy: WorkflowIdConflictPolicy,
508
509    /// Optionally set the execution timeout for the workflow
510    /// <https://docs.temporal.io/workflows/#workflow-execution-timeout>
511    pub execution_timeout: Option<Duration>,
512
513    /// Optionally indicates the default run timeout for a workflow run
514    pub run_timeout: Option<Duration>,
515
516    /// Optionally indicates the default task timeout for a workflow run
517    pub task_timeout: Option<Duration>,
518
519    /// Optionally set a cron schedule for the workflow
520    pub cron_schedule: Option<String>,
521
522    /// Additional search attributes for the workflow.
523    pub search_attributes: Option<SearchAttributes>,
524
525    /// Optionally enable Eager Workflow Start, a latency optimization using local workers.
526    #[builder(default)]
527    pub enable_eager_workflow_start: bool,
528
529    /// Optionally set a retry policy for the workflow
530    #[builder(into)]
531    pub retry_policy: Option<RetryPolicy>,
532
533    /// Links to associate with the workflow. Ex: References to a nexus operation.
534    #[builder(default)]
535    pub links: Vec<common::v1::Link>,
536
537    /// Callbacks that will be invoked upon workflow completion. For, ex, completing nexus
538    /// operations.
539    #[builder(default)]
540    pub completion_callbacks: Vec<common::v1::Callback>,
541
542    /// Priority for the workflow. Defaults to all-inherited (empty).
543    #[builder(default)]
544    pub priority: Priority,
545
546    /// Headers to include with the start request.
547    pub header: Option<Header>,
548
549    /// Non-indexed values attached to the workflow, serialized with the client's data converter.
550    pub memo: Option<MemoValues>,
551
552    /// Single-line static summary for the workflow, shown in the Temporal UI.
553    pub static_summary: Option<String>,
554
555    /// Multi-line static details for the workflow, shown in the Temporal UI.
556    pub static_details: Option<String>,
557
558    /// Controls for the RPC used to start the workflow.
559    #[builder(default)]
560    pub rpc_options: RpcOptions,
561}
562
563impl WorkflowStartOptions {
564    pub(crate) async fn encoded_memo(
565        &self,
566        data_converter: &DataConverter,
567    ) -> Result<Option<ProtoMemo>, PayloadConversionError> {
568        let Some(memo) = &self.memo else {
569            return Ok(None);
570        };
571
572        let payload_converter = data_converter.payload_converter();
573        let context_data = SerializationContextData::Workflow(WorkflowSerializationContext::new());
574        let context = SerializationContext::new(&context_data, payload_converter);
575        let mut memo = ProtoMemo {
576            fields: memo
577                .iter()
578                .map(|(key, value)| {
579                    payload_converter
580                        .to_payload(&context, value)
581                        .map(|payload| (key.to_owned(), payload))
582                })
583                .collect::<Result<_, _>>()?,
584        };
585        encode_payloads(
586            &mut memo,
587            data_converter.codec(),
588            &SerializationContextData::Workflow(WorkflowSerializationContext::new()),
589        )
590        .await?;
591        Ok(Some(memo))
592    }
593
594    pub(crate) fn user_metadata(&self) -> Option<UserMetadata> {
595        (self.static_summary.is_some() || self.static_details.is_some()).then(|| {
596            let payload_converter = PayloadConverter::default();
597            let context_data =
598                SerializationContextData::Workflow(WorkflowSerializationContext::new());
599            let context = SerializationContext::new(&context_data, &payload_converter);
600            UserMetadata {
601                summary: self.static_summary.as_ref().map(|summary| {
602                    payload_converter
603                        .to_payload(&context, summary)
604                        .expect("String-to-JSON payload serialization is infallible")
605                }),
606                details: self.static_details.as_ref().map(|details| {
607                    payload_converter
608                        .to_payload(&context, details)
609                        .expect("String-to-JSON payload serialization is infallible")
610                }),
611            }
612        })
613    }
614}
615
616/// Options for starting a workflow and sending it an update in one atomic operation.
617///
618/// See [crate::Client::start_update_with_start_workflow] and
619/// [crate::Client::execute_update_with_start_workflow].
620#[derive(Debug, Clone, bon::Builder)]
621#[builder(start_fn = new, on(String, into))]
622#[non_exhaustive]
623pub struct WorkflowUpdateWithStartOptions {
624    /// The task queue to run the workflow on.
625    #[builder(start_fn)]
626    pub task_queue: String,
627
628    /// The workflow ID.
629    #[builder(start_fn)]
630    pub workflow_id: String,
631
632    /// How to resolve a conflict with an already-running workflow. This is required so callers
633    /// explicitly choose whether an update may attach to an existing workflow.
634    #[builder(start_fn)]
635    pub id_conflict_policy: WorkflowIdConflictPolicy,
636
637    /// The policy for reusing the workflow ID after a workflow closes.
638    #[builder(default)]
639    pub id_reuse_policy: WorkflowIdReusePolicy,
640
641    /// The workflow execution timeout.
642    pub execution_timeout: Option<Duration>,
643
644    /// The workflow run timeout.
645    pub run_timeout: Option<Duration>,
646
647    /// The workflow task timeout.
648    pub task_timeout: Option<Duration>,
649
650    /// Search attributes for the workflow.
651    pub search_attributes: Option<SearchAttributes>,
652
653    /// The workflow retry policy.
654    #[builder(into)]
655    pub retry_policy: Option<RetryPolicy>,
656
657    /// Links to associate with the workflow.
658    #[builder(default)]
659    pub links: Vec<common::v1::Link>,
660
661    /// Callbacks invoked when the workflow completes.
662    #[builder(default)]
663    pub completion_callbacks: Vec<common::v1::Callback>,
664
665    /// Priority for the workflow. Defaults to all-inherited (empty).
666    #[builder(default)]
667    pub priority: Priority,
668
669    /// Headers to include with the start operation.
670    pub start_header: Option<Header>,
671
672    /// Headers to include with the update operation.
673    pub update_header: Option<Header>,
674
675    /// Non-indexed values attached to the workflow, serialized with the client's data converter.
676    pub memo: Option<MemoValues>,
677
678    /// Single-line static summary for the workflow, shown in the Temporal UI.
679    pub static_summary: Option<String>,
680
681    /// Multi-line static details for the workflow, shown in the Temporal UI.
682    pub static_details: Option<String>,
683
684    /// Update ID for idempotency. If not provided, a UUID will be generated.
685    pub update_id: Option<String>,
686
687    /// Controls for the multi-operation RPC and, when executing the update, subsequent polling.
688    #[builder(default)]
689    pub rpc_options: RpcOptions,
690}
691
692impl WorkflowUpdateWithStartOptions {
693    pub(crate) fn into_parts(self) -> (WorkflowStartOptions, Option<String>, Option<Header>) {
694        let Self {
695            task_queue,
696            workflow_id,
697            id_conflict_policy,
698            id_reuse_policy,
699            execution_timeout,
700            run_timeout,
701            task_timeout,
702            search_attributes,
703            retry_policy,
704            links,
705            completion_callbacks,
706            priority,
707            start_header,
708            update_header,
709            memo,
710            static_summary,
711            static_details,
712            update_id,
713            rpc_options: _,
714        } = self;
715        (
716            WorkflowStartOptions {
717                task_queue,
718                workflow_id,
719                id_reuse_policy,
720                id_conflict_policy,
721                execution_timeout,
722                run_timeout,
723                task_timeout,
724                cron_schedule: None,
725                search_attributes,
726                enable_eager_workflow_start: false,
727                retry_policy,
728                links,
729                completion_callbacks,
730                priority,
731                header: start_header,
732                memo,
733                static_summary,
734                static_details,
735                rpc_options: RpcOptions::default(),
736            },
737            update_id,
738            update_header,
739        )
740    }
741}
742
743pub use temporalio_common::Priority;
744
745/// Options for fetching workflow results
746#[derive(Debug, Clone, bon::Builder)]
747#[non_exhaustive]
748pub struct WorkflowGetResultOptions {
749    /// If true (the default), follows to the next workflow run in the execution chain while
750    /// retrieving results.
751    #[builder(default = true)]
752    pub follow_runs: bool,
753    /// Controls for each history RPC used to retrieve the result.
754    #[builder(default)]
755    pub rpc_options: RpcOptions,
756}
757impl Default for WorkflowGetResultOptions {
758    fn default() -> Self {
759        Self {
760            follow_runs: true,
761            rpc_options: RpcOptions::default(),
762        }
763    }
764}
765
766/// Options for starting a workflow update.
767#[derive(Debug, Clone, Default, bon::Builder)]
768#[non_exhaustive]
769pub struct WorkflowExecuteUpdateOptions {
770    /// Update ID for idempotency.
771    pub update_id: Option<String>,
772    /// Headers to include.
773    pub header: Option<Header>,
774    /// Controls for the start-update and poll-update RPCs.
775    #[builder(default)]
776    pub rpc_options: RpcOptions,
777}
778
779/// Options for sending a signal to a workflow.
780#[derive(Debug, Clone, Default, bon::Builder)]
781#[non_exhaustive]
782pub struct WorkflowSignalOptions {
783    /// Request ID for idempotency. If not provided, a UUID will be generated.
784    pub request_id: Option<String>,
785    /// Headers to include with the signal.
786    pub header: Option<Header>,
787    /// Controls for the signal RPC.
788    #[builder(default)]
789    pub rpc_options: RpcOptions,
790}
791
792/// Controls when a workflow query should be rejected based on workflow state.
793#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
794#[non_exhaustive]
795pub enum QueryRejectCondition {
796    /// Use the server's default condition.
797    #[default]
798    Unspecified,
799    /// Do not reject the query based on workflow state.
800    None,
801    /// Reject the query if the workflow is not open.
802    NotOpen,
803    /// Reject the query if the workflow did not complete successfully.
804    NotCompletedCleanly,
805}
806
807impl From<QueryRejectCondition> for ProtoQueryRejectCondition {
808    fn from(value: QueryRejectCondition) -> Self {
809        match value {
810            QueryRejectCondition::Unspecified => Self::Unspecified,
811            QueryRejectCondition::None => Self::None,
812            QueryRejectCondition::NotOpen => Self::NotOpen,
813            QueryRejectCondition::NotCompletedCleanly => Self::NotCompletedCleanly,
814        }
815    }
816}
817
818/// Options for querying a workflow.
819#[derive(Debug, Clone, Default, bon::Builder)]
820#[non_exhaustive]
821pub struct WorkflowQueryOptions {
822    /// Query reject condition. Determines when the query should be rejected
823    /// based on workflow state.
824    pub reject_condition: Option<QueryRejectCondition>,
825    /// Headers to include with the query.
826    pub header: Option<Header>,
827    /// Controls for the query RPC.
828    #[builder(default)]
829    pub rpc_options: RpcOptions,
830}
831
832/// Options for cancelling a workflow.
833#[derive(Debug, Clone, Default, bon::Builder)]
834#[builder(on(String, into))]
835#[non_exhaustive]
836pub struct WorkflowCancelOptions {
837    /// Reason for cancellation.
838    #[builder(default)]
839    pub reason: String,
840    /// Request ID for idempotency. If not provided, a UUID will be generated.
841    pub request_id: Option<String>,
842    /// Controls for the cancellation RPC.
843    #[builder(default)]
844    pub rpc_options: RpcOptions,
845}
846
847/// Options for terminating a workflow.
848#[derive(Debug, Clone, Default, bon::Builder)]
849#[builder(on(String, into))]
850#[non_exhaustive]
851pub struct WorkflowTerminateOptions {
852    /// Reason for termination.
853    #[builder(default)]
854    pub reason: String,
855    /// Additional details to include with the termination.
856    pub details: Option<Payloads>,
857    /// Controls for the termination RPC.
858    #[builder(default)]
859    pub rpc_options: RpcOptions,
860}
861
862/// Options for describing a workflow.
863#[derive(Debug, Clone, Default, bon::Builder)]
864#[non_exhaustive]
865pub struct WorkflowDescribeOptions {
866    /// Controls for the describe RPC.
867    #[builder(default)]
868    pub rpc_options: RpcOptions,
869}
870
871/// Default workflow execution retention for a Namespace is 3 days
872const DEFAULT_WORKFLOW_EXECUTION_RETENTION_PERIOD: Duration = Duration::from_secs(60 * 60 * 24 * 3);
873
874/// Controls whether archival is enabled for a namespace.
875#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
876#[non_exhaustive]
877pub enum ArchivalState {
878    /// Use the server's default archival state.
879    #[default]
880    Unspecified,
881    /// Disable archival.
882    Disabled,
883    /// Enable archival.
884    Enabled,
885}
886
887impl From<ArchivalState> for ProtoArchivalState {
888    fn from(value: ArchivalState) -> Self {
889        match value {
890            ArchivalState::Unspecified => Self::Unspecified,
891            ArchivalState::Disabled => Self::Disabled,
892            ArchivalState::Enabled => Self::Enabled,
893        }
894    }
895}
896
897/// Helper struct for `register_namespace`.
898#[derive(Clone, Debug, bon::Builder)]
899#[builder(on(String, into))]
900#[non_exhaustive]
901pub struct RegisterNamespaceOptions {
902    /// Name (required)
903    pub namespace: String,
904    /// Description (required)
905    pub description: String,
906    /// Owner's email
907    #[builder(default)]
908    pub owner_email: String,
909    /// Workflow execution retention period
910    #[builder(default = DEFAULT_WORKFLOW_EXECUTION_RETENTION_PERIOD)]
911    pub workflow_execution_retention_period: Duration,
912    /// Cluster settings
913    #[builder(default)]
914    pub clusters: Vec<ClusterReplicationConfig>,
915    /// Active cluster name
916    #[builder(default)]
917    pub active_cluster_name: String,
918    /// Custom Data
919    #[builder(default)]
920    pub data: HashMap<String, String>,
921    /// Security Token
922    #[builder(default)]
923    pub security_token: String,
924    /// Global namespace
925    #[builder(default)]
926    pub is_global_namespace: bool,
927    /// History Archival setting
928    #[builder(default = ArchivalState::Unspecified)]
929    pub history_archival_state: ArchivalState,
930    /// History Archival uri
931    #[builder(default)]
932    pub history_archival_uri: String,
933    /// Visibility Archival setting
934    #[builder(default = ArchivalState::Unspecified)]
935    pub visibility_archival_state: ArchivalState,
936    /// Visibility Archival uri
937    #[builder(default)]
938    pub visibility_archival_uri: String,
939}
940
941impl From<RegisterNamespaceOptions> for RegisterNamespaceRequest {
942    fn from(val: RegisterNamespaceOptions) -> Self {
943        RegisterNamespaceRequest {
944            namespace: val.namespace,
945            description: val.description,
946            owner_email: val.owner_email,
947            workflow_execution_retention_period: val
948                .workflow_execution_retention_period
949                .try_into()
950                .ok(),
951            clusters: val.clusters,
952            active_cluster_name: val.active_cluster_name,
953            data: val.data,
954            security_token: val.security_token,
955            is_global_namespace: val.is_global_namespace,
956            history_archival_state: ProtoArchivalState::from(val.history_archival_state) as i32,
957            history_archival_uri: val.history_archival_uri,
958            visibility_archival_state: ProtoArchivalState::from(val.visibility_archival_state)
959                as i32,
960            visibility_archival_uri: val.visibility_archival_uri,
961        }
962    }
963}
964
965/// Selects which workflow history events are returned when fetching history.
966#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
967#[non_exhaustive]
968pub enum HistoryEventFilterType {
969    /// Use the server's default filter.
970    #[default]
971    Unspecified,
972    /// Return all history events.
973    AllEvent,
974    /// Return only the workflow's close event.
975    CloseEvent,
976}
977
978impl From<HistoryEventFilterType> for ProtoHistoryEventFilterType {
979    fn from(value: HistoryEventFilterType) -> Self {
980        match value {
981            HistoryEventFilterType::Unspecified => Self::Unspecified,
982            HistoryEventFilterType::AllEvent => Self::AllEvent,
983            HistoryEventFilterType::CloseEvent => Self::CloseEvent,
984        }
985    }
986}
987
988/// Options for fetching workflow history.
989#[derive(Debug, Clone, Default, bon::Builder)]
990#[non_exhaustive]
991pub struct WorkflowFetchHistoryOptions {
992    /// Whether to skip archival.
993    #[builder(default)]
994    pub skip_archival: bool,
995    /// If set true, the fetch will wait for a new event before returning.
996    #[builder(default)]
997    pub wait_new_event: bool,
998    /// Specifies which kind of events will be retrieved. Defaults to all events.
999    #[builder(default = HistoryEventFilterType::AllEvent)]
1000    pub event_filter_type: HistoryEventFilterType,
1001    /// Controls for each history page RPC.
1002    #[builder(default)]
1003    pub rpc_options: RpcOptions,
1004}
1005
1006/// Options for starting an update without waiting for completion.
1007#[derive(Debug, Clone, Default, bon::Builder)]
1008#[non_exhaustive]
1009pub struct WorkflowStartUpdateOptions {
1010    /// Update ID for idempotency. If not provided, a UUID will be generated.
1011    pub update_id: Option<String>,
1012    /// Headers to include with the update.
1013    pub header: Option<Header>,
1014    /// Controls for the start-update RPC.
1015    #[builder(default)]
1016    pub rpc_options: RpcOptions,
1017}
1018
1019impl From<WorkflowExecuteUpdateOptions> for WorkflowStartUpdateOptions {
1020    /// Execute-update is start-update followed by waiting for the update result.
1021    fn from(options: WorkflowExecuteUpdateOptions) -> Self {
1022        Self::builder()
1023            .maybe_update_id(options.update_id)
1024            .maybe_header(options.header)
1025            .rpc_options(options.rpc_options)
1026            .build()
1027    }
1028}
1029
1030/// Options for listing workflows.
1031#[derive(Debug, Clone, Default, bon::Builder)]
1032#[non_exhaustive]
1033pub struct WorkflowListOptions {
1034    /// Maximum number of workflows to return.
1035    /// If not specified, returns all matching workflows.
1036    pub limit: Option<usize>,
1037    /// Controls for each list page RPC.
1038    #[builder(default)]
1039    pub rpc_options: RpcOptions,
1040}
1041
1042/// Options for counting workflows.
1043#[derive(Debug, Clone, Default, bon::Builder)]
1044#[non_exhaustive]
1045pub struct WorkflowCountOptions {
1046    /// Controls for the count RPC.
1047    #[builder(default)]
1048    pub rpc_options: RpcOptions,
1049}
1050
1051/// Options for starting a standalone activity.
1052#[derive(Clone, Debug, bon::Builder)]
1053#[builder(start_fn = new, on(String, into))]
1054#[non_exhaustive]
1055pub struct ActivityStartOptions {
1056    /// Task queue to run this activity on.
1057    #[builder(start_fn)]
1058    pub task_queue: String,
1059    /// Activity ID of the started activity. It's recommended to use a meaningful business ID.
1060    #[builder(start_fn)]
1061    pub id: String,
1062    /// Timeouts for activity completion.
1063    ///
1064    /// See [`ActivityCloseTimeouts`] for the meaning of each timeout variant.
1065    #[builder(start_fn)]
1066    pub close_timeouts: ActivityCloseTimeouts,
1067    /// If set, specifies maximum time the activity can wait in the task queue before being picked
1068    /// up by a worker. This timeout is non-retryable.
1069    pub schedule_to_start_timeout: Option<Duration>,
1070    /// If set, specifies maximum time between successful heartbeats.
1071    pub heartbeat_timeout: Option<Duration>,
1072    /// Controls how Activity is retried. If not set, the server will assign default retry policy.
1073    #[builder(into)]
1074    pub retry_policy: Option<RetryPolicy>,
1075    /// Priority to use when starting this activity.
1076    #[builder(default)]
1077    pub priority: Priority,
1078    /// Specifies behavior if there's a *closed* activity with the same ID.
1079    #[builder(default)]
1080    pub id_reuse_policy: ActivityIdReusePolicy,
1081    /// Specifies behavior if there's a *running* activity with the same ID. Note that there can
1082    /// only be one running activity for each Activity ID.
1083    #[builder(default)]
1084    pub id_conflict_policy: ActivityIdConflictPolicy,
1085    /// Search attributes for the activity.
1086    pub search_attributes: Option<SearchAttributes>,
1087    /// Headers to include with the start request.
1088    pub header: Option<Header>,
1089    /// Single-line static summary for the activity, shown in the Temporal UI.
1090    pub summary: Option<String>,
1091    /// Multi-line static details for the activity, shown in the Temporal UI.
1092    pub static_details: Option<String>,
1093    /// Time to wait before dispatching the first activity task.
1094    /// This delay is not applied to retry attempts.
1095    pub start_delay: Option<Duration>,
1096}
1097
1098impl ActivityStartOptions {
1099    /// Returns a builder with `close_timeouts` set to [`ActivityCloseTimeouts::StartToClose`].
1100    pub fn with_start_to_close_timeout(
1101        task_queue: impl Into<String>,
1102        activity_id: impl Into<String>,
1103        start_to_close_timeout: Duration,
1104    ) -> ActivityStartOptionsBuilder {
1105        Self::new(
1106            task_queue,
1107            activity_id,
1108            ActivityCloseTimeouts::StartToClose(start_to_close_timeout),
1109        )
1110    }
1111
1112    /// Returns a builder with `close_timeouts` set to [`ActivityCloseTimeouts::ScheduleToClose`].
1113    pub fn with_schedule_to_close_timeout(
1114        task_queue: impl Into<String>,
1115        activity_id: impl Into<String>,
1116        schedule_to_close_timeout: Duration,
1117    ) -> ActivityStartOptionsBuilder {
1118        Self::new(
1119            task_queue,
1120            activity_id,
1121            ActivityCloseTimeouts::ScheduleToClose(schedule_to_close_timeout),
1122        )
1123    }
1124}
1125
1126/// Specifies behavior when starting a standalone activity if there's a *closed* activity with
1127/// the same ID. See [`ActivityStartOptions::id_reuse_policy`].
1128#[non_exhaustive]
1129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1130pub enum ActivityIdReusePolicy {
1131    #[default]
1132    /// Always allow starting an activity using the same activity ID. This is the default.
1133    AllowDuplicate,
1134    /// Allow starting an activity using the same ID only when the last execution did not complete
1135    /// successfully.
1136    AllowDuplicateFailedOnly,
1137    /// Do not permit re-use of the ID for this activity.
1138    RejectDuplicate,
1139}
1140
1141impl From<ActivityIdReusePolicy> for ProtoActivityIdReusePolicy {
1142    fn from(value: ActivityIdReusePolicy) -> Self {
1143        match value {
1144            ActivityIdReusePolicy::AllowDuplicate => Self::AllowDuplicate,
1145            ActivityIdReusePolicy::AllowDuplicateFailedOnly => Self::AllowDuplicateFailedOnly,
1146            ActivityIdReusePolicy::RejectDuplicate => Self::RejectDuplicate,
1147        }
1148    }
1149}
1150
1151/// Specifies behavior when starting a standalone activity if there's a *running* activity with
1152/// the same ID. See [`ActivityStartOptions::id_conflict_policy`].
1153#[non_exhaustive]
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1155pub enum ActivityIdConflictPolicy {
1156    #[default]
1157    /// Don't start a new activity; instead return
1158    /// [`StartActivityError::AlreadyStarted`](crate::errors::StartActivityError::AlreadyStarted).
1159    Fail,
1160    /// Don't start a new activity; instead return a handle for the running activity.
1161    UseExisting,
1162}
1163
1164impl From<ActivityIdConflictPolicy> for ProtoActivityIdConflictPolicy {
1165    fn from(value: ActivityIdConflictPolicy) -> Self {
1166        match value {
1167            ActivityIdConflictPolicy::Fail => Self::Fail,
1168            ActivityIdConflictPolicy::UseExisting => Self::UseExisting,
1169        }
1170    }
1171}
1172
1173/// Options for listing activities.
1174#[derive(Debug, Clone, Default, bon::Builder)]
1175#[non_exhaustive]
1176pub struct ActivityListOptions {}
1177
1178/// Options for counting activities.
1179#[derive(Debug, Clone, Default, bon::Builder)]
1180#[non_exhaustive]
1181pub struct ActivityCountOptions {}
1182
1183/// Controls which optional fields will be requested in
1184/// [`ActivityHandle::describe`](crate::ActivityHandle::describe) operation. The fields will be
1185/// present in returned [`ActivityExecutionDescription`](crate::ActivityExecutionDescription),
1186/// subject to data availability and server support.
1187///
1188/// Note that these fields contain payloads that can be arbitrarily large. It's recommended not to
1189/// include them unless they're needed.
1190#[derive(Debug, Clone, Default, bon::Builder)]
1191#[non_exhaustive]
1192pub struct ActivityDescribeOptions {
1193    /// If set and the activity received input, the input will be included.
1194    #[builder(default)]
1195    pub include_input: bool,
1196    /// If set and the activity is closed, the activity outcome will be included.
1197    #[builder(default)]
1198    pub include_outcome: bool,
1199    /// If set and the activity sent heartbeat details, the heartbeat details will be included.
1200    #[builder(default)]
1201    pub include_heartbeat_details: bool,
1202    /// If set and the activity has a failed attempt, the last failure will be included.
1203    #[builder(default)]
1204    pub include_last_failure: bool,
1205}
1206
1207/// Options for [`ActivityHandle::cancel`](crate::ActivityHandle::cancel).
1208#[derive(Debug, Clone, Default, bon::Builder)]
1209#[builder(on(String, into))]
1210#[non_exhaustive]
1211pub struct ActivityCancelOptions {
1212    /// Reason for cancellation. Can be empty.
1213    #[builder(default)]
1214    pub reason: String,
1215}
1216
1217/// Options for [`ActivityHandle::terminate`](crate::ActivityHandle::terminate).
1218#[derive(Debug, Clone, Default, bon::Builder)]
1219#[builder(on(String, into))]
1220#[non_exhaustive]
1221pub struct ActivityTerminateOptions {
1222    /// Reason for termination. Can be empty.
1223    #[builder(default)]
1224    pub reason: String,
1225}