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