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