Skip to main content

dex_sdk/
context.rs

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