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