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