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