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