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