Skip to main content

dex_sdk/
context.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use License 1.0.
4// You may not use this file except in compliance with the License.
5// See the LICENSE file in the repository root.
6//
7// SPDX-License-Identifier: LicenseRef-Sustainable-Use-1.0
8
9use std::collections::{HashMap, HashSet};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::{Arc, Condvar, Mutex};
12use std::time::{Duration, SystemTime};
13
14use dex_protocol::dex::{
15    AttributeWrite, ChannelInfo, ChannelMessage as ProtoChannelMessage, ChannelMessageDeletion,
16    ChannelValues, ConditionResults, ConditionStatus, Context as ProtoContext, Kv, StepStreamWrite,
17    Value as ProtoValue, value,
18};
19
20use crate::attribute::{
21    AttributeWriteSettings, require_registered_hard_delete_setting,
22    validate_hard_delete_attribute_write,
23};
24use crate::persistence::PersistenceKind;
25use crate::registry::{RegisteredFlow, decode_instance, physical_name, validate_map_instance};
26use crate::server_protocol::NegotiatedServerProtocolVersion;
27use crate::value_mapper;
28use crate::worker_output::StepOutputEmitter;
29use crate::{
30    Attribute, AttributeMap, Channel, ChannelMap, ChannelMessage, FlowResult, HandlerError,
31    HandlerResult, Stream, Value,
32};
33
34#[derive(Clone, Copy, Eq, PartialEq)]
35pub(crate) enum InvocationMethod {
36    WaitFor,
37    Execute,
38    Rpc,
39}
40
41pub(crate) struct ContextInput {
42    pub(crate) method: InvocationMethod,
43    pub(crate) flow: RegisteredFlow,
44    pub(crate) metadata: ProtoContext,
45    pub(crate) attributes: Vec<Kv>,
46    pub(crate) locals: Vec<Kv>,
47    pub(crate) condition_results: Option<ConditionResults>,
48    pub(crate) channel_infos: HashMap<String, ChannelInfo>,
49    pub(crate) loaded_channel_messages: HashMap<String, ChannelValues>,
50    pub(crate) loaded_attribute_map_instances: Vec<String>,
51    pub(crate) loaded_channel_names: Vec<String>,
52    pub(crate) loaded_channel_map_instances: Vec<String>,
53    pub(crate) negotiated_protocol_version: NegotiatedServerProtocolVersion,
54}
55
56pub(crate) struct InvocationOutputs {
57    pub(crate) attributes: Vec<AttributeWrite>,
58    pub(crate) locals: Vec<Kv>,
59    pub(crate) events: Vec<Kv>,
60    pub(crate) publications: Vec<ProtoChannelMessage>,
61    pub(crate) channel_deletions: Vec<ChannelMessageDeletion>,
62}
63
64/// Describes the final failure that caused a Step method recovery.
65///
66/// Read this value from [`Context::recovery_error`]. Worker failures preserve their original
67/// error type and detail. Dex failures such as timeouts use the corresponding failure type.
68///
69/// ```no_run
70/// # fn inspect(context: &dex_sdk::Context) {
71/// if let Some(error) = context.recovery_error() {
72///     eprintln!("Recovering from {}: {}", error.error_type(), error.detail());
73/// }
74/// # }
75/// ```
76#[derive(Clone, Debug, Eq, PartialEq)]
77pub struct RecoveryErrorInfo {
78    detail: String,
79    error_type: String,
80    attempt_progress: StepAttemptProgress,
81}
82
83/// Reports how far attempts of a failed Step method progressed.
84///
85/// Since 1.35.0.
86///
87/// Read [`RecoveryErrorInfo::attempt_progress`] during recovery before deciding whether to repeat
88/// side effects. Progress describes all attempts of that method call, rather than just the last one.
89/// A handler may have performed side effects for both [`Self::NotCompleted`] and [`Self::Unspecified`].
90/// These values correspond to NOT_STARTED, NOT_COMPLETED, and UNSPECIFIED on the wire.
91/// NotCompleted does not prove that an external effect failed; reconcile it or repeat it idempotently.
92/// Unspecified also covers an unknown progress value and must be handled conservatively.
93/// Only Sync can report NotStarted. Async failures always report NotCompleted because an
94/// unpersisted start can replay even if an earlier execution already ran. A disconnect after
95/// hand-out, or an input loading or decoding failure, conservatively reports NotCompleted.
96/// Gracefully returning a task without running it consumes no retry and reports no failure.
97///
98/// ```no_run
99/// # fn recover(context: &dex_sdk::Context) {
100/// if let Some(error) = context.recovery_error() {
101///     match error.attempt_progress() {
102///         dex_sdk::StepAttemptProgress::NotStarted => { /* no handler side effects */ }
103///         _ => { /* reconcile possible side effects before retrying */ }
104///     }
105/// }
106/// # }
107/// ```
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109#[non_exhaustive]
110pub enum StepAttemptProgress {
111    /// An older Server supplied no progress; treat this like [`Self::NotCompleted`].
112    Unspecified,
113    /// No handler ran in any attempt of this Step method call.
114    NotStarted,
115    /// At least one attempt may have run, but no attempt completed.
116    NotCompleted,
117}
118
119impl StepAttemptProgress {
120    fn from_protocol_value(value: i32) -> Self {
121        match dex_protocol::dex::StepAttemptProgress::try_from(value) {
122            Ok(dex_protocol::dex::StepAttemptProgress::NotStarted) => Self::NotStarted,
123            Ok(dex_protocol::dex::StepAttemptProgress::NotCompleted) => Self::NotCompleted,
124            Ok(dex_protocol::dex::StepAttemptProgress::Unspecified) | Err(_) => Self::Unspecified,
125        }
126    }
127}
128
129impl RecoveryErrorInfo {
130    /// Returns the furthest progress across all attempts of the failed Step method call.
131    ///
132    /// [`StepAttemptProgress::NotStarted`] guarantees its handler did not run. Both other values
133    /// mean side effects may have happened. The value is copied; see [`StepAttemptProgress`] for
134    /// a recovery example. Older Servers report [`StepAttemptProgress::Unspecified`].
135    /// Since 1.35.0.
136    pub fn attempt_progress(&self) -> StepAttemptProgress {
137        self.attempt_progress
138    }
139
140    /// Returns the failure detail supplied by the Worker or backend.
141    pub fn detail(&self) -> &str {
142        &self.detail
143    }
144
145    /// Returns the original Worker error type or backend failure type.
146    pub fn error_type(&self) -> &str {
147        &self.error_type
148    }
149}
150
151/// Provides invocation metadata and staged durable changes to Step and RPC handlers.
152///
153/// A Context belongs to one handler attempt and must not outlive the call. Attribute writes,
154/// Channel publications, locals, and events become visible atomically only when the handler returns
155/// successfully. Application values are freshly decoded and owned by the invocation.
156/// Heartbeats and Stream messages are emitted during the call, independently of the final result.
157///
158/// The Rust Context exposes cancellation, not an absolute attempt-deadline accessor. Bound external
159/// calls with their own timeout and check [`Self::is_cancelled`] between bounded units of work.
160/// The Worker observes the server-supplied attempt deadline and requests cancellation on expiry.
161/// There is no separate attempt ID accessor: use Flow ID, run ID, Step execution ID, and attempt
162/// number together for diagnostics. These identifiers do not make external effects idempotent.
163/// A re-run after [`crate::Client::time_travel`] reuses the Flow ID, run ID, and Step execution
164/// ID, and its attempt number can repeat. The Rust Context does not expose the run attempt.
165/// An idempotency key built from these values matches the effect from before time travel.
166pub struct Context {
167    method: InvocationMethod,
168    flow: RegisteredFlow,
169    output_emitter: Option<StepOutputEmitter>,
170    metadata: ProtoContext,
171    recovery_error: Option<RecoveryErrorInfo>,
172    attributes: HashMap<String, ProtoValue>,
173    locals: HashMap<String, ProtoValue>,
174    condition_results: Option<ConditionResults>,
175    channel_infos: HashMap<String, ChannelInfo>,
176    loaded_channel_messages: HashMap<String, ChannelValues>,
177    loaded_attribute_map_instances: HashSet<String>,
178    loaded_channel_names: HashSet<String>,
179    loaded_channel_map_instances: HashSet<String>,
180    attribute_writes: HashMap<String, AttributeWrite>,
181    local_writes: HashMap<String, Kv>,
182    events: Vec<Kv>,
183    event_names: HashSet<String>,
184    publications: Vec<ProtoChannelMessage>,
185    channel_deletions: Vec<ChannelMessageDeletion>,
186    cancellation: InvocationCancellation,
187    runtime: Option<tokio::runtime::Handle>,
188    step_output_finalizers: Vec<Arc<dyn StepOutputFinalizer>>,
189    step_outputs_finalized: bool,
190    negotiated_protocol_version: NegotiatedServerProtocolVersion,
191    forked_join_names: HashSet<String>,
192}
193
194pub(crate) trait StepOutputFinalizer: Send + Sync {
195    fn finalize_step_output(&self) -> HandlerResult<()>;
196}
197
198impl Context {
199    pub(crate) fn new(
200        input: ContextInput,
201        output_emitter: Option<StepOutputEmitter>,
202        cancellation: InvocationCancellation,
203    ) -> HandlerResult<Self> {
204        let recovery_error =
205            input
206                .metadata
207                .recovery_error
208                .as_ref()
209                .map(|error| RecoveryErrorInfo {
210                    attempt_progress: StepAttemptProgress::from_protocol_value(
211                        error.attempt_progress,
212                    ),
213                    detail: error.detail.clone(),
214                    error_type: error.error_type.clone(),
215                });
216        Ok(Self {
217            method: input.method,
218            flow: input.flow,
219            output_emitter,
220            metadata: input.metadata,
221            recovery_error,
222            attributes: map_values("Attribute", input.attributes)?,
223            locals: map_values("step-execution local", input.locals)?,
224            condition_results: input.condition_results,
225            channel_infos: input.channel_infos,
226            loaded_channel_messages: input.loaded_channel_messages,
227            loaded_attribute_map_instances: input
228                .loaded_attribute_map_instances
229                .into_iter()
230                .collect(),
231            loaded_channel_names: input.loaded_channel_names.into_iter().collect(),
232            loaded_channel_map_instances: input.loaded_channel_map_instances.into_iter().collect(),
233            attribute_writes: HashMap::new(),
234            local_writes: HashMap::new(),
235            events: Vec::new(),
236            event_names: HashSet::new(),
237            publications: Vec::new(),
238            channel_deletions: Vec::new(),
239            cancellation,
240            runtime: tokio::runtime::Handle::try_current().ok(),
241            step_output_finalizers: Vec::new(),
242            step_outputs_finalized: false,
243            negotiated_protocol_version: input.negotiated_protocol_version,
244            forked_join_names: HashSet::new(),
245        })
246    }
247
248    /// Returns the application-assigned Flow ID.
249    pub fn flow_id(&self) -> &str {
250        &self.metadata.flow_id
251    }
252
253    /// Returns the server-assigned run ID for this execution.
254    ///
255    /// A Flow ID identifies the application operation; a run ID identifies one server execution.
256    /// Do not assume a run ID remains fixed for the entire lifetime of a Flow ID.
257    /// [`crate::Client::time_travel`] keeps the run ID and starts a new run attempt of that run.
258    /// A Step re-run after time travel therefore sees the same run ID as before.
259    pub fn run_id(&self) -> &str {
260        &self.metadata.run_id
261    }
262
263    /// Returns when the current Flow run started.
264    pub fn flow_started_at(&self) -> SystemTime {
265        system_time(self.metadata.flow_started_timestamp)
266    }
267
268    /// Returns the current Step execution ID, or an empty string for an RPC.
269    ///
270    /// A Step re-run after [`crate::Client::time_travel`] reuses its Step execution ID.
271    pub fn step_execution_id(&self) -> &str {
272        &self.metadata.step_execution_id
273    }
274
275    /// Returns the predecessor Step execution ID, or an empty string when absent.
276    pub fn from_step_execution_id(&self) -> &str {
277        &self.metadata.from_step_execution_id
278    }
279
280    /// Records that `join_name` forked in this invocation, rejecting a second fork.
281    pub(crate) fn record_join_fork(&mut self, join_name: &str) -> HandlerResult<()> {
282        if self.forked_join_names.insert(join_name.to_string()) {
283            return Ok(());
284        }
285        Err(HandlerError::new(
286            "dex_sdk::HandlerError",
287            format!(
288                "Join {join_name} already forked in this invocation; use another Join or fork_with_round_key"
289            ),
290        ))
291    }
292
293    /// Returns the final failure that caused recovery, or `None` outside a recovery Step.
294    pub fn recovery_error(&self) -> Option<&RecoveryErrorInfo> {
295        self.recovery_error.as_ref()
296    }
297
298    /// Returns when the first attempt of this handler invocation started.
299    pub fn first_attempt_at(&self) -> SystemTime {
300        system_time(self.metadata.first_attempt_timestamp)
301    }
302
303    /// Returns the one-based handler attempt number.
304    ///
305    /// With 1.35.0 (Server protocol 4): an unpersisted Async attempt start can replay with the same
306    /// number.
307    /// Attempts count within one method call; they are not unique external-effect identifiers.
308    /// This is the handler attempt, not the run attempt. A Step re-run after
309    /// [`crate::Client::time_travel`] can report attempt numbers seen before time travel.
310    pub fn attempt(&self) -> u32 {
311        u32::try_from(self.metadata.attempt).unwrap_or_default()
312    }
313
314    /// Records a heartbeat without a Value and clears persisted heartbeat details.
315    ///
316    /// The call blocks only while the Worker's one-frame output buffer is full. It does not wait
317    /// for Dex to process the heartbeat. Flow RPC Contexts reject this operation.
318    ///
319    /// # Errors
320    ///
321    /// Returns [`HandlerError`] for an RPC Context, cancellation, or a closed Worker response
322    /// stream.
323    pub fn record_heartbeat(&mut self) -> HandlerResult<()> {
324        self.step_output_emitter()?.send_heartbeat(None)
325    }
326
327    /// Records a heartbeat carrying a typed checkpoint Value.
328    ///
329    /// A later attempt can decode the most recently accepted checkpoint with
330    /// [`Self::last_heartbeat_value`]. Both durability modes preserve checkpoint values.
331    /// The call uses the same bounded local buffering as [`Self::record_heartbeat`].
332    ///
333    /// # Examples
334    ///
335    /// ```
336    /// use dex_sdk::{Context, HandlerResult};
337    /// use serde::{Deserialize, Serialize};
338    ///
339    /// #[derive(Deserialize, Serialize)]
340    /// struct Checkpoint {
341    ///     offset: u64,
342    /// }
343    ///
344    /// fn import_batch(context: &mut Context, next_offset: u64) -> HandlerResult<()> {
345    ///     let previous = context.last_heartbeat_value::<Checkpoint>()?;
346    ///     let offset = previous.map_or(next_offset, |checkpoint| checkpoint.offset);
347    ///     context.record_heartbeat_value(Checkpoint { offset })
348    /// }
349    /// ```
350    ///
351    /// # Errors
352    ///
353    /// Returns [`HandlerError`] for an RPC Context, encoding failure, cancellation, or a closed
354    /// Worker response stream.
355    pub fn record_heartbeat_value<T: Value>(&mut self, value: T) -> HandlerResult<()> {
356        let value = value_mapper::encode_handler(&value)?;
357        self.step_output_emitter()?.send_heartbeat(Some(value))
358    }
359
360    /// Decodes the checkpoint persisted by the previous regular attempt.
361    ///
362    /// `None` means no heartbeat Value is present. To distinguish a null Value, request an
363    /// optional application type: `last_heartbeat_value::<Option<T>>()` returns `Some(None)` for a
364    /// present null Value.
365    ///
366    /// # Errors
367    ///
368    /// Returns [`HandlerError`] for an RPC Context or when the persisted Value cannot be decoded
369    /// as `T`.
370    pub fn last_heartbeat_value<T: Value>(&self) -> HandlerResult<Option<T>> {
371        if self.method == InvocationMethod::Rpc {
372            return Err(HandlerError::new(
373                "dex_sdk::HandlerError",
374                "Heartbeat values require a Step Context",
375            ));
376        }
377        self.metadata
378            .last_heartbeat_value
379            .as_ref()
380            .map(value_mapper::decode_handler)
381            .transpose()
382    }
383
384    /// Returns whether any timer condition completed for this `execute` invocation.
385    pub fn has_any_timer_fired(&self) -> bool {
386        self.condition_results.as_ref().is_some_and(|results| {
387            results
388                .timer_results
389                .iter()
390                .any(|timer| timer.condition_status == ConditionStatus::Completed as i32)
391        })
392    }
393
394    /// Returns whether the zero-based timer at `index` completed.
395    ///
396    /// Missing indexes return `false`.
397    pub fn has_timer_fired(&self, index: usize) -> bool {
398        self.condition_results
399            .as_ref()
400            .and_then(|results| results.timer_results.get(index))
401            .is_some_and(|timer| timer.condition_status == ConditionStatus::Completed as i32)
402    }
403
404    /// Returns whether `execute` is running because `wait_for` exhausted retries with Proceed.
405    pub fn wait_for_method_failed(&self) -> bool {
406        self.condition_results
407            .as_ref()
408            .is_some_and(|results| results.wait_for_failed)
409    }
410
411    pub(crate) fn sub_flow_result(&self, index: usize) -> HandlerResult<FlowResult> {
412        if self.method != InvocationMethod::Execute {
413            return Err(HandlerError::new(
414                "dex_sdk::HandlerError",
415                "SubFlow results are available only during execute",
416            ));
417        }
418        let result = self
419            .condition_results
420            .as_ref()
421            .and_then(|results| results.sub_flow_results.get(index))
422            .ok_or_else(|| {
423                HandlerError::new(
424                    "dex_sdk::HandlerError",
425                    format!("SubFlow result index is unavailable: {index}"),
426                )
427            })?;
428        FlowResult::from_proto(result).map_err(HandlerError::from_error)
429    }
430
431    pub(crate) fn sub_flow_id(&self, index: usize) -> HandlerResult<String> {
432        self.sub_flow_result(index)?;
433        Ok(format!(
434            "SubFlow:{}-{}-{index}",
435            self.flow_id(),
436            self.step_execution_id()
437        ))
438    }
439
440    /// Blocks the current thread until Dex cancels this handler attempt.
441    ///
442    /// Use this only for interruptible application work; ordinary handlers should return normally.
443    pub fn wait_for_cancellation(&self) {
444        self.cancellation.wait();
445    }
446
447    /// Returns immediately with the current handler-cancellation state.
448    pub fn is_cancelled(&self) -> bool {
449        self.cancellation.is_cancelled()
450    }
451
452    /// Stages a Step-execution-local value visible to later attempts of the same execution.
453    ///
454    /// # Errors
455    ///
456    /// Returns [`HandlerError`] for an empty key or an encoding failure.
457    pub fn set_step_execution_local<T: Value>(&mut self, key: &str, value: T) -> HandlerResult<()> {
458        require_name(key, "step-execution local key")?;
459        let value = value_mapper::encode_handler(&value)?;
460        self.local_writes.insert(
461            key.to_string(),
462            Kv {
463                key: key.to_string(),
464                value: Some(value),
465            },
466        );
467        Ok(())
468    }
469
470    /// Reads a required Step-execution-local value, including writes staged by this attempt.
471    ///
472    /// # Errors
473    ///
474    /// Returns [`HandlerError`] for an empty key, a missing value, or a decoding failure.
475    pub fn step_execution_local<T: Value>(&self, key: &str) -> HandlerResult<T> {
476        require_name(key, "step-execution local key")?;
477        let value = self
478            .local_writes
479            .get(key)
480            .and_then(|entry| entry.value.as_ref())
481            .or_else(|| self.locals.get(key))
482            .ok_or_else(|| {
483                HandlerError::new(
484                    "dex_sdk::HandlerError",
485                    format!("step-execution local {key} is missing"),
486                )
487            })?;
488        value_mapper::decode_handler(value)
489    }
490
491    /// Records one named diagnostic event with a typed payload.
492    ///
493    /// Event names must be unique within the invocation.
494    ///
495    /// # Errors
496    ///
497    /// Returns [`HandlerError`] for an empty or duplicate name or an encoding failure.
498    pub fn record_event<T: Value>(&mut self, name: &str, value: T) -> HandlerResult<()> {
499        require_name(name, "event name")?;
500        if !self.event_names.insert(name.to_string()) {
501            return Err(HandlerError::new(
502                "dex_sdk::HandlerError",
503                format!("event was already recorded: {name}"),
504            ));
505        }
506        self.events.push(Kv {
507            key: name.to_string(),
508            value: Some(value_mapper::encode_handler(&value)?),
509        });
510        Ok(())
511    }
512
513    /// Reads an Attribute value, including writes staged by this invocation.
514    pub fn get_attribute<T: Value>(&self, attribute: &Attribute<T>) -> HandlerResult<Option<T>> {
515        self.get_attribute_value(attribute.name(), PersistenceKind::Attribute, None)
516    }
517
518    /// Reads one Attribute-map instance, including staged writes.
519    pub fn get_attribute_map<T: Value>(
520        &self,
521        attribute: &AttributeMap<T>,
522        instance: &str,
523    ) -> HandlerResult<Option<T>> {
524        self.get_attribute_value(
525            attribute.name(),
526            PersistenceKind::AttributeMap,
527            Some(instance),
528        )
529    }
530
531    /// Stages a typed Attribute write.
532    pub fn set_attribute<T: Value>(
533        &mut self,
534        attribute: &Attribute<T>,
535        value: T,
536    ) -> HandlerResult<()> {
537        self.set_attribute_value(
538            attribute.name(),
539            PersistenceKind::Attribute,
540            None,
541            value_mapper::encode_handler(&value)?,
542            attribute.write_settings(),
543        )
544    }
545
546    /// Stages a typed write for one Attribute-map instance.
547    pub fn set_attribute_map<T: Value>(
548        &mut self,
549        attribute: &AttributeMap<T>,
550        instance: &str,
551        value: T,
552    ) -> HandlerResult<()> {
553        self.set_attribute_value(
554            attribute.name(),
555            PersistenceKind::AttributeMap,
556            Some(instance),
557            value_mapper::encode_handler(&value)?,
558            attribute.write_settings(),
559        )
560    }
561
562    /// Stages deletion of an Attribute value.
563    pub fn delete_attribute<T>(&mut self, attribute: &Attribute<T>) -> HandlerResult<()> {
564        self.set_attribute_value(
565            attribute.name(),
566            PersistenceKind::Attribute,
567            None,
568            value_mapper::deletion(),
569            attribute.write_settings(),
570        )
571    }
572
573    /// Stages deletion of one Attribute-map instance.
574    pub fn delete_attribute_map<T>(
575        &mut self,
576        attribute: &AttributeMap<T>,
577        instance: &str,
578    ) -> HandlerResult<()> {
579        self.set_attribute_value(
580            attribute.name(),
581            PersistenceKind::AttributeMap,
582            Some(instance),
583            value_mapper::deletion(),
584            attribute.write_settings(),
585        )
586    }
587
588    pub(crate) fn attribute_map_keys<T>(
589        &self,
590        attribute: &AttributeMap<T>,
591    ) -> HandlerResult<Vec<String>> {
592        self.registered_name(
593            attribute.name(),
594            PersistenceKind::AttributeMap,
595            Some("key-check"),
596        )?;
597        if !self
598            .loaded_attribute_map_instances
599            .contains(&format!("{}/", attribute.name()))
600        {
601            return Err(attribute_map_not_loaded(format!(
602                "all AttributeMap instances were not loaded for this invocation: {}",
603                attribute.name()
604            )));
605        }
606        let prefix = format!("{}/", attribute.name());
607        let mut physical_keys = self
608            .attributes
609            .keys()
610            .filter(|key| key.starts_with(&prefix))
611            .cloned()
612            .collect::<HashSet<_>>();
613        for (key, write) in &self.attribute_writes {
614            if !key.starts_with(&prefix) {
615                continue;
616            }
617            if matches!(
618                write.value.as_ref().and_then(|value| value.kind.as_ref()),
619                Some(value::Kind::NullValue(_))
620            ) {
621                physical_keys.remove(key);
622            } else {
623                physical_keys.insert(key.clone());
624            }
625        }
626        sorted_instance_keys(&prefix, physical_keys)
627    }
628
629    /// Stages one typed Channel publication.
630    pub fn publish<T: Value>(&mut self, channel: &Channel<T>, value: T) -> HandlerResult<()> {
631        self.publish_value(
632            channel.name(),
633            PersistenceKind::Channel,
634            None,
635            value_mapper::encode_handler(&value)?,
636        )
637    }
638
639    /// Stages one typed publication to a Channel-map instance.
640    pub fn publish_map<T: Value>(
641        &mut self,
642        channel: &ChannelMap<T>,
643        instance: &str,
644        value: T,
645    ) -> HandlerResult<()> {
646        self.publish_value(
647            channel.name(),
648            PersistenceKind::ChannelMap,
649            Some(instance),
650            value_mapper::encode_handler(&value)?,
651        )
652    }
653
654    /// Returns the current invocation snapshot's Channel queue size.
655    pub fn channel_size<T>(&self, channel: &Channel<T>) -> HandlerResult<usize> {
656        self.channel_size_value(channel.name(), PersistenceKind::Channel, None)
657    }
658
659    /// Returns one Channel-map instance's queue size.
660    pub fn channel_map_size<T>(
661        &self,
662        channel: &ChannelMap<T>,
663        instance: &str,
664    ) -> HandlerResult<usize> {
665        self.channel_size_value(channel.name(), PersistenceKind::ChannelMap, Some(instance))
666    }
667
668    /// Returns one singleton Channel's loaded pending-message snapshot.
669    pub fn pending_channel_messages<T: Value>(
670        &self,
671        channel: &Channel<T>,
672    ) -> HandlerResult<Vec<ChannelMessage<T>>> {
673        self.pending_channel_messages_value(channel.name(), PersistenceKind::Channel, None)
674    }
675
676    /// Returns one Channel-map instance's loaded pending-message snapshot.
677    pub fn pending_channel_map_messages<T: Value>(
678        &self,
679        channel: &ChannelMap<T>,
680        instance: &str,
681    ) -> HandlerResult<Vec<ChannelMessage<T>>> {
682        self.pending_channel_messages_value(
683            channel.name(),
684            PersistenceKind::ChannelMap,
685            Some(instance),
686        )
687    }
688
689    pub(crate) fn channel_map_keys<T>(
690        &self,
691        channel: &ChannelMap<T>,
692    ) -> HandlerResult<Vec<String>> {
693        self.registered_name(
694            channel.name(),
695            PersistenceKind::ChannelMap,
696            Some("key-check"),
697        )?;
698        let prefix = format!("{}/", channel.name());
699        let physical_keys = self
700            .channel_infos
701            .iter()
702            .filter(|(key, info)| key.starts_with(&prefix) && info.size > 0)
703            .map(|(key, _)| key.clone())
704            .collect::<HashSet<_>>();
705        sorted_instance_keys(&prefix, physical_keys)
706    }
707
708    /// Decodes the messages consumed by a satisfied Channel condition.
709    pub fn channel_results<T: Value>(&self, channel: &Channel<T>) -> HandlerResult<Vec<T>> {
710        self.channel_results_value(channel.name(), PersistenceKind::Channel, None)
711    }
712
713    /// Decodes messages consumed by one satisfied Channel-map condition.
714    pub fn channel_map_results<T: Value>(
715        &self,
716        channel: &ChannelMap<T>,
717        instance: &str,
718    ) -> HandlerResult<Vec<T>> {
719        self.channel_results_value(channel.name(), PersistenceKind::ChannelMap, Some(instance))
720    }
721
722    /// Appends one best-effort Stream message to the current Worker response stream.
723    ///
724    /// The call returns after the Worker's bounded output buffer accepts the frame. It does not
725    /// wait for Dex or the Stream Store, and a later server-side write failure is not returned to
726    /// the handler. One invocation may append any number of messages to the same Stream.
727    ///
728    /// # Errors
729    ///
730    /// Returns [`HandlerError`] for an RPC or Flow timeout Context, an unregistered Stream, a
731    /// capacity mismatch, encoding failure, cancellation, or a closed Worker response stream.
732    pub fn write_stream<T: Value>(&mut self, stream: &Stream<T>, value: T) -> HandlerResult<()> {
733        let output_emitter = self.step_output_emitter()?.clone();
734        let definition = self
735            .flow
736            .persistence
737            .get(stream.name())
738            .filter(|definition| {
739                definition.kind == PersistenceKind::Stream
740                    && definition.stream_identity == Some(stream.identity())
741            })
742            .ok_or_else(|| {
743                HandlerError::new(
744                    "dex_sdk::HandlerError",
745                    format!("Stream does not belong to Flow: {}", stream.name()),
746                )
747            })?;
748        if definition.stream_capacity_bytes != Some(stream.stream_capacity_bytes()) {
749            return Err(HandlerError::new(
750                "dex_sdk::HandlerError",
751                format!(
752                    "Stream capacity does not match its registered definition: {}",
753                    stream.name()
754                ),
755            ));
756        }
757        output_emitter.send_stream_write(StepStreamWrite {
758            stream_name: stream.name().to_string(),
759            stream_capacity_bytes: stream.stream_capacity_bytes(),
760            value: Some(value_mapper::encode_handler(&value)?),
761        })
762    }
763
764    pub(crate) fn prepare_buffered_text_stream(
765        &self,
766        stream: &Stream<String>,
767    ) -> HandlerResult<(
768        StepOutputEmitter,
769        InvocationCancellation,
770        tokio::runtime::Handle,
771    )> {
772        let output_emitter = self.step_output_emitter()?.clone();
773        let definition = self
774            .flow
775            .persistence
776            .get(stream.name())
777            .filter(|definition| {
778                definition.kind == PersistenceKind::Stream
779                    && definition.stream_identity == Some(stream.identity())
780            })
781            .ok_or_else(|| {
782                HandlerError::new(
783                    "dex_sdk::HandlerError",
784                    format!("Stream does not belong to Flow: {}", stream.name()),
785                )
786            })?;
787        if definition.stream_capacity_bytes != Some(stream.stream_capacity_bytes()) {
788            return Err(HandlerError::new(
789                "dex_sdk::HandlerError",
790                format!(
791                    "Stream capacity does not match its registered definition: {}",
792                    stream.name()
793                ),
794            ));
795        }
796        let runtime = self.runtime.clone().ok_or_else(|| {
797            HandlerError::new(
798                "dex_sdk::HandlerError",
799                "Buffered Stream runtime is unavailable",
800            )
801        })?;
802        Ok((output_emitter, self.cancellation.clone(), runtime))
803    }
804
805    pub(crate) fn register_step_output_finalizer(
806        &mut self,
807        finalizer: Arc<dyn StepOutputFinalizer>,
808    ) -> HandlerResult<()> {
809        if self.step_outputs_finalized {
810            return Err(HandlerError::new(
811                "dex_sdk::HandlerError",
812                "Buffered Stream invocation has finished",
813            ));
814        }
815        self.step_output_finalizers.push(finalizer);
816        Ok(())
817    }
818
819    pub(crate) fn finalize_step_outputs<T>(
820        &mut self,
821        result: HandlerResult<T>,
822    ) -> HandlerResult<T> {
823        if self.step_outputs_finalized {
824            return result;
825        }
826        self.step_outputs_finalized = true;
827        let mut result = result;
828        for finalizer in &self.step_output_finalizers {
829            if let Err(finalization_failure) = finalizer.finalize_step_output() {
830                match &mut result {
831                    Ok(_) => result = Err(finalization_failure),
832                    Err(handler_failure) => {
833                        handler_failure.attach_finalizer_error(&finalization_failure);
834                    }
835                }
836            }
837        }
838        result
839    }
840
841    pub(crate) fn cancellation(&self) -> InvocationCancellation {
842        self.cancellation.clone()
843    }
844
845    pub(crate) fn take_outputs(self) -> InvocationOutputs {
846        InvocationOutputs {
847            attributes: self.attribute_writes.into_values().collect(),
848            locals: self.local_writes.into_values().collect(),
849            events: self.events,
850            publications: self.publications,
851            channel_deletions: self.channel_deletions,
852        }
853    }
854
855    fn step_output_emitter(&self) -> HandlerResult<&StepOutputEmitter> {
856        if self.method == InvocationMethod::Rpc {
857            return Err(HandlerError::new(
858                "dex_sdk::HandlerError",
859                "Step progress requires a Step Context",
860            ));
861        }
862        if self.cancellation.is_cancelled() {
863            return Err(HandlerError::new(
864                "dex_sdk::HandlerError",
865                "Step invocation is canceled",
866            ));
867        }
868        self.output_emitter.as_ref().ok_or_else(|| {
869            HandlerError::new("dex_sdk::HandlerError", "Step output stream is unavailable")
870        })
871    }
872
873    fn get_attribute_value<T: Value>(
874        &self,
875        name: &str,
876        kind: PersistenceKind,
877        instance: Option<&str>,
878    ) -> HandlerResult<Option<T>> {
879        let key = self.registered_name(name, kind, instance)?;
880        if kind == PersistenceKind::AttributeMap {
881            let is_loaded = self
882                .loaded_attribute_map_instances
883                .contains(&format!("{name}/"))
884                || self.loaded_attribute_map_instances.contains(&key);
885            if !is_loaded {
886                return Err(attribute_map_not_loaded(format!(
887                    "AttributeMap instance was not loaded for this invocation: {key}"
888                )));
889            }
890        }
891        if let Some(write) = self.attribute_writes.get(&key) {
892            let value = write.value.as_ref().ok_or_else(|| {
893                HandlerError::new(
894                    "dex_sdk::HandlerError",
895                    format!("Attribute write {key} has no Value"),
896                )
897            })?;
898            if matches!(value.kind, Some(value::Kind::NullValue(_))) {
899                return Ok(None);
900            }
901            return value_mapper::decode_handler(value).map(Some);
902        }
903        self.attributes
904            .get(&key)
905            .map(value_mapper::decode_handler)
906            .transpose()
907    }
908
909    fn set_attribute_value(
910        &mut self,
911        name: &str,
912        kind: PersistenceKind,
913        instance: Option<&str>,
914        value: ProtoValue,
915        write_settings: AttributeWriteSettings,
916    ) -> HandlerResult<()> {
917        let key = self.registered_name(name, kind, instance)?;
918        let is_registered_hard_delete_on_null = self
919            .flow
920            .persistence
921            .get(name)
922            .is_some_and(|definition| definition.is_hard_delete_on_null);
923        require_registered_hard_delete_setting(
924            name,
925            is_registered_hard_delete_on_null,
926            write_settings.is_hard_delete_on_null,
927        )
928        .map_err(|error| HandlerError::new("dex_sdk::HandlerError", error))?;
929        let write = write_settings.attribute_write(key.clone(), value);
930        validate_hard_delete_attribute_write(&write)
931            .map_err(|error| HandlerError::new("dex_sdk::HandlerError", error))?;
932        if write.is_hard_delete_on_null {
933            self.negotiated_protocol_version
934                .require_hard_delete_attribute_support(&key)
935                .map_err(|error| HandlerError::new("dex_sdk::HandlerError", error))?;
936        }
937        self.attribute_writes.insert(key, write);
938        Ok(())
939    }
940
941    fn publish_value(
942        &mut self,
943        name: &str,
944        kind: PersistenceKind,
945        instance: Option<&str>,
946        value: ProtoValue,
947    ) -> HandlerResult<()> {
948        let channel_name = self.registered_name(name, kind, instance)?;
949        self.publications.push(ProtoChannelMessage {
950            channel_name: channel_name.clone(),
951            value: Some(value),
952            message_id: String::new(),
953        });
954        self.channel_infos
955            .entry(channel_name)
956            .and_modify(|info| info.size += 1)
957            .or_insert(ChannelInfo { size: 1 });
958        Ok(())
959    }
960
961    /// Stages best-effort deletion of one singleton Channel message.
962    pub fn delete_channel_message<T>(
963        &mut self,
964        channel: &Channel<T>,
965        message_id: &str,
966    ) -> HandlerResult<()> {
967        self.delete_channel_message_value(
968            channel.name(),
969            PersistenceKind::Channel,
970            None,
971            message_id,
972        )
973    }
974
975    /// Stages best-effort deletion of one Channel-map message.
976    pub fn delete_channel_map_message<T>(
977        &mut self,
978        channel: &ChannelMap<T>,
979        instance: &str,
980        message_id: &str,
981    ) -> HandlerResult<()> {
982        self.delete_channel_message_value(
983            channel.name(),
984            PersistenceKind::ChannelMap,
985            Some(instance),
986            message_id,
987        )
988    }
989
990    fn delete_channel_message_value(
991        &mut self,
992        name: &str,
993        kind: PersistenceKind,
994        instance: Option<&str>,
995        message_id: &str,
996    ) -> HandlerResult<()> {
997        if message_id.is_empty() {
998            return Err(HandlerError::new(
999                "dex_sdk::HandlerError",
1000                "Channel message ID must not be empty",
1001            ));
1002        }
1003        let channel_name = self.registered_name(name, kind, instance)?;
1004        self.channel_deletions.push(ChannelMessageDeletion {
1005            channel_name: channel_name.clone(),
1006            message_id: message_id.to_string(),
1007        });
1008        if let Some(info) = self.channel_infos.get_mut(&channel_name) {
1009            info.size = info.size.saturating_sub(1);
1010        }
1011        Ok(())
1012    }
1013
1014    fn channel_size_value(
1015        &self,
1016        name: &str,
1017        kind: PersistenceKind,
1018        instance: Option<&str>,
1019    ) -> HandlerResult<usize> {
1020        let channel_name = self.registered_name(name, kind, instance)?;
1021        Ok(self
1022            .channel_infos
1023            .get(&channel_name)
1024            .map_or(0, |info| usize::try_from(info.size).unwrap_or_default()))
1025    }
1026
1027    fn channel_results_value<T: Value>(
1028        &self,
1029        name: &str,
1030        kind: PersistenceKind,
1031        instance: Option<&str>,
1032    ) -> HandlerResult<Vec<T>> {
1033        let channel_name = self.registered_name(name, kind, instance)?;
1034        let Some(results) = &self.condition_results else {
1035            return Ok(Vec::new());
1036        };
1037        let mut decoded = Vec::new();
1038        for result in &results.channel_results {
1039            if result.channel_name == channel_name
1040                && result.condition_status == ConditionStatus::Completed as i32
1041            {
1042                for value in &result.values {
1043                    decoded.push(value_mapper::decode_handler(value)?);
1044                }
1045            }
1046        }
1047        Ok(decoded)
1048    }
1049
1050    fn pending_channel_messages_value<T: Value>(
1051        &self,
1052        name: &str,
1053        kind: PersistenceKind,
1054        instance: Option<&str>,
1055    ) -> HandlerResult<Vec<ChannelMessage<T>>> {
1056        let physical_name = self.registered_name(name, kind, instance)?;
1057        let is_loaded = match kind {
1058            PersistenceKind::Channel => self.loaded_channel_names.contains(&physical_name),
1059            PersistenceKind::ChannelMap => {
1060                self.loaded_channel_map_instances
1061                    .contains(&format!("{name}/"))
1062                    || self.loaded_channel_map_instances.contains(&physical_name)
1063            }
1064            _ => false,
1065        };
1066        if !is_loaded {
1067            return Err(channel_messages_not_loaded(format!(
1068                "Channel messages were not loaded for this invocation: {physical_name}"
1069            )));
1070        }
1071        self.loaded_channel_messages
1072            .get(&physical_name)
1073            .map_or(&[][..], |values| values.messages.as_slice())
1074            .iter()
1075            .map(|message| {
1076                let value = message.value.as_ref().ok_or_else(|| {
1077                    HandlerError::new(
1078                        "dex_sdk::HandlerError",
1079                        format!("Channel message {} has no Value", message.message_id),
1080                    )
1081                })?;
1082                Ok(ChannelMessage {
1083                    message_id: message.message_id.clone(),
1084                    value: value_mapper::decode_handler(value)?,
1085                })
1086            })
1087            .collect()
1088    }
1089
1090    fn registered_name(
1091        &self,
1092        name: &str,
1093        kind: PersistenceKind,
1094        instance: Option<&str>,
1095    ) -> HandlerResult<String> {
1096        let definition = self.flow.persistence.get(name).ok_or_else(|| {
1097            HandlerError::new(
1098                "dex_sdk::HandlerError",
1099                format!("persistence definition does not belong to Flow: {name}"),
1100            )
1101        })?;
1102        if definition.kind != kind {
1103            return Err(HandlerError::new(
1104                "dex_sdk::HandlerError",
1105                format!("persistence definition kind does not match: {name}"),
1106            ));
1107        }
1108        match kind {
1109            PersistenceKind::AttributeMap | PersistenceKind::ChannelMap => {
1110                let instance = instance.ok_or_else(|| {
1111                    HandlerError::new(
1112                        "dex_sdk::HandlerError",
1113                        format!("{name} requires an instance"),
1114                    )
1115                })?;
1116                validate_map_instance(instance)
1117                    .map_err(|error| HandlerError::new("dex_sdk::HandlerError", error))?;
1118                Ok(physical_name(name, instance))
1119            }
1120            PersistenceKind::Attribute | PersistenceKind::Channel if instance.is_none() => {
1121                Ok(name.to_string())
1122            }
1123            _ => Err(HandlerError::new(
1124                "dex_sdk::HandlerError",
1125                format!("static persistence definition cannot use an instance: {name}"),
1126            )),
1127        }
1128    }
1129}
1130
1131fn attribute_map_not_loaded(message: impl Into<String>) -> HandlerError {
1132    HandlerError::new("dex_sdk::AttributeMapNotLoadedError", message)
1133}
1134
1135fn channel_messages_not_loaded(message: impl Into<String>) -> HandlerError {
1136    HandlerError::new("dex_sdk::ChannelMessagesNotLoadedError", message)
1137}
1138
1139fn sorted_instance_keys(
1140    prefix: &str,
1141    physical_keys: HashSet<String>,
1142) -> HandlerResult<Vec<String>> {
1143    let mut keys = physical_keys
1144        .iter()
1145        .map(|key| {
1146            decode_instance(key, prefix)
1147                .map_err(|error| HandlerError::new("dex_sdk::HandlerError", error))
1148        })
1149        .collect::<HandlerResult<Vec<_>>>()?;
1150    keys.sort();
1151    Ok(keys)
1152}
1153
1154#[derive(Clone)]
1155pub(crate) struct InvocationCancellation {
1156    inner: Arc<InvocationCancellationInner>,
1157}
1158
1159struct InvocationCancellationInner {
1160    cancelled: AtomicBool,
1161    mutex: Mutex<()>,
1162    condition: Condvar,
1163    notification: tokio::sync::Notify,
1164}
1165
1166impl InvocationCancellation {
1167    pub(crate) fn new() -> Self {
1168        Self {
1169            inner: Arc::new(InvocationCancellationInner {
1170                cancelled: AtomicBool::new(false),
1171                mutex: Mutex::new(()),
1172                condition: Condvar::new(),
1173                notification: tokio::sync::Notify::new(),
1174            }),
1175        }
1176    }
1177
1178    fn wait(&self) {
1179        let mut guard = self.inner.mutex.lock().expect("cancellation lock");
1180        while !self.is_cancelled() {
1181            guard = self.inner.condition.wait(guard).expect("cancellation lock");
1182        }
1183    }
1184
1185    pub(crate) fn is_cancelled(&self) -> bool {
1186        self.inner.cancelled.load(Ordering::Acquire)
1187    }
1188
1189    pub(crate) fn cancel(&self) {
1190        let _guard = self.inner.mutex.lock().expect("cancellation lock");
1191        self.inner.cancelled.store(true, Ordering::Release);
1192        self.inner.condition.notify_all();
1193        self.inner.notification.notify_waiters();
1194    }
1195
1196    pub(crate) async fn cancelled(&self) {
1197        let notified = self.inner.notification.notified();
1198        if self.is_cancelled() {
1199            return;
1200        }
1201        notified.await;
1202    }
1203}
1204
1205fn system_time(timestamp: i64) -> SystemTime {
1206    if timestamp >= 0 {
1207        SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp as u64)
1208    } else {
1209        SystemTime::UNIX_EPOCH - Duration::from_secs(timestamp.unsigned_abs())
1210    }
1211}
1212
1213fn map_values(kind: &str, entries: Vec<Kv>) -> HandlerResult<HashMap<String, ProtoValue>> {
1214    let mut mapped = HashMap::new();
1215    for entry in entries {
1216        require_name(&entry.key, kind)?;
1217        let value = entry.value.ok_or_else(|| {
1218            HandlerError::new(
1219                "dex_sdk::HandlerError",
1220                format!("{kind} {} has no Value", entry.key),
1221            )
1222        })?;
1223        if mapped.insert(entry.key.clone(), value).is_some() {
1224            return Err(HandlerError::new(
1225                "dex_sdk::HandlerError",
1226                format!("duplicate {kind} {}", entry.key),
1227            ));
1228        }
1229    }
1230    Ok(mapped)
1231}
1232
1233fn require_name(value: &str, kind: &str) -> HandlerResult<()> {
1234    if value.is_empty() {
1235        Err(HandlerError::new(
1236            "dex_sdk::HandlerError",
1237            format!("{kind} is required"),
1238        ))
1239    } else {
1240        Ok(())
1241    }
1242}
1243
1244#[cfg(test)]
1245#[path = "context_tests.rs"]
1246mod tests;