Skip to main content

dex_sdk/client/
attribute_reads.rs

1// Copyright (c) 2026 Super Durable, Inc.
2//
3// Licensed under the Sustainable Use 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-Sustainable-Use-1.0
8
9use std::collections::{HashMap, HashSet};
10use std::sync::LazyLock;
11
12use dex_protocol::dex::{GetAttributesRequest, Kv, Value as ProtoValue};
13
14use super::{Client, invalid, map_physical_name};
15use crate::persistence::PersistenceDefinition;
16use crate::sdk_error::{FlowTargetRequirement, ServiceError};
17use crate::{Attribute, AttributeMapInstance, SdkError, SdkResult, Value, value_mapper};
18
19const GET_ATTRIBUTES_OPERATION: &str = "get_attributes";
20
21/// Selects one Attribute or AttributeMap instance for [`Client::get_attributes`].
22///
23/// Since 1.31.0.
24///
25/// [`Attribute<T>`] and [`AttributeMapInstance<T>`] implement this trait.
26/// References to them and `LazyLock` statics that hold them also implement it.
27/// Create an [`AttributeMapInstance`] with [`AttributeMap::instance`](crate::AttributeMap::instance).
28/// Applications cannot implement this trait.
29/// See [`Client::get_attributes`] for an example.
30///
31/// An [`AttributeMapLoad`](crate::AttributeMapLoad) from
32/// [`AttributeMap::load`](crate::AttributeMap::load) is not a read, so it does not compile:
33///
34/// ```compile_fail
35/// use dex_sdk::{AttributeMap, Client};
36///
37/// fn read_note(client: &Client, notes: &AttributeMap<String>) -> dex_sdk::SdkResult<()> {
38///     client.get_attributes("order-1", (&notes.load("n1"),))?;
39///     Ok(())
40/// }
41/// ```
42#[diagnostic::on_unimplemented(
43    message = "`{Self}` is not an Attribute read",
44    label = "pass an Attribute or `AttributeMap::instance(key)`",
45    note = "`AttributeMap::load` selects an instance for a handler Context, not for `Client::get_attributes`"
46)]
47pub trait AttributeRead: sealed::SealedAttributeRead {
48    /// The decoded value type of the selected Attribute or AttributeMap instance.
49    type Value: Value;
50
51    #[doc(hidden)]
52    fn attribute_read_target(&self) -> sealed::AttributeReadTarget;
53}
54
55/// Lists the Attribute reads of one [`Client::get_attributes`] call as a tuple.
56///
57/// Since 1.31.0.
58///
59/// Tuples of 1 to 12 [`AttributeRead`] values implement this trait.
60/// Each tuple element is usually a reference, such as `&ORDER_STATUS` or `&NOTES.instance("n1")`.
61/// The call returns a tuple of the same arity whose elements are `Option<T>`.
62/// Pass a one-element tuple, such as `(&ORDER_STATUS,)`, to read one Attribute.
63/// Pass more than 12 reads by making several calls.
64/// The empty tuple `()` does not implement this trait, so a call needs at least one read.
65/// Applications cannot implement this trait.
66/// See [`Client::get_attributes`] for an example.
67#[diagnostic::on_unimplemented(
68    message = "`{Self}` is not a tuple of 1 to 12 Attribute reads",
69    label = "pass a tuple such as `(&ORDER_STATUS, &NOTES.instance(\"n1\"))`",
70    note = "use a one-element tuple such as `(&ORDER_STATUS,)` to read one Attribute"
71)]
72pub trait AttributeReads: sealed::SealedAttributeReads {
73    /// The tuple of decoded values, with one `Option<T>` per read in read order.
74    type Values;
75
76    #[doc(hidden)]
77    fn attribute_read_targets(&self) -> Vec<sealed::AttributeReadTarget>;
78
79    #[doc(hidden)]
80    fn decode_attribute_values(
81        decoder: &mut sealed::AttributeValueDecoder,
82    ) -> SdkResult<Self::Values>;
83}
84
85mod sealed {
86    use super::*;
87
88    pub trait SealedAttributeRead {}
89
90    pub trait SealedAttributeReads {}
91
92    pub struct AttributeReadTarget {
93        pub(super) definition: PersistenceDefinition,
94        pub(super) instance: Option<String>,
95    }
96
97    pub struct AttributeValueDecoder {
98        pub(super) flow_id: String,
99        pub(super) values: std::vec::IntoIter<(String, Option<ProtoValue>)>,
100    }
101
102    impl AttributeValueDecoder {
103        pub(super) fn decode_next<T: Value>(&mut self) -> SdkResult<Option<T>> {
104            let (key, value) = self
105                .values
106                .next()
107                .expect("the decoder holds one value slot per read");
108            value
109                .map(|value| {
110                    value_mapper::decode_without_value_detail(&value).map_err(|error| {
111                        error.with_call_context(
112                            GET_ATTRIBUTES_OPERATION,
113                            &self.flow_id,
114                            format!(
115                                "get_attributes Flow {:?} Attribute {key:?} value cannot be \
116                                 decoded as {}",
117                                self.flow_id,
118                                std::any::type_name::<T>()
119                            ),
120                        )
121                    })
122                })
123                .transpose()
124        }
125    }
126}
127
128impl<T: Value> sealed::SealedAttributeRead for Attribute<T> {}
129
130impl<T: Value> AttributeRead for Attribute<T> {
131    type Value = T;
132
133    fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
134        sealed::AttributeReadTarget {
135            definition: PersistenceDefinition::for_attribute(self),
136            instance: None,
137        }
138    }
139}
140
141impl<T: Value> sealed::SealedAttributeRead for AttributeMapInstance<T> {}
142
143impl<T: Value> AttributeRead for AttributeMapInstance<T> {
144    type Value = T;
145
146    fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
147        sealed::AttributeReadTarget {
148            definition: PersistenceDefinition::for_attribute_map(self.attribute_map()),
149            instance: Some(self.instance().to_string()),
150        }
151    }
152}
153
154impl<Read: AttributeRead> sealed::SealedAttributeRead for &Read {}
155
156#[diagnostic::do_not_recommend]
157impl<Read: AttributeRead> AttributeRead for &Read {
158    type Value = Read::Value;
159
160    fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
161        (**self).attribute_read_target()
162    }
163}
164
165impl<Read: AttributeRead, Init: FnOnce() -> Read> sealed::SealedAttributeRead
166    for LazyLock<Read, Init>
167{
168}
169
170#[diagnostic::do_not_recommend]
171impl<Read: AttributeRead, Init: FnOnce() -> Read> AttributeRead for LazyLock<Read, Init> {
172    type Value = Read::Value;
173
174    fn attribute_read_target(&self) -> sealed::AttributeReadTarget {
175        LazyLock::force(self).attribute_read_target()
176    }
177}
178
179macro_rules! impl_attribute_reads {
180    ($(($index:tt, $read:ident)),+) => {
181        impl<$($read: AttributeRead),+> sealed::SealedAttributeReads for ($($read,)+) {}
182
183        #[diagnostic::do_not_recommend]
184        impl<$($read: AttributeRead),+> AttributeReads for ($($read,)+) {
185            type Values = ($(Option<$read::Value>,)+);
186
187            fn attribute_read_targets(&self) -> Vec<sealed::AttributeReadTarget> {
188                vec![$(self.$index.attribute_read_target()),+]
189            }
190
191            fn decode_attribute_values(
192                decoder: &mut sealed::AttributeValueDecoder,
193            ) -> SdkResult<Self::Values> {
194                Ok(($(decoder.decode_next::<$read::Value>()?,)+))
195            }
196        }
197    };
198}
199
200impl_attribute_reads!((0, Read0));
201impl_attribute_reads!((0, Read0), (1, Read1));
202impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2));
203impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2), (3, Read3));
204impl_attribute_reads!((0, Read0), (1, Read1), (2, Read2), (3, Read3), (4, Read4));
205impl_attribute_reads!(
206    (0, Read0),
207    (1, Read1),
208    (2, Read2),
209    (3, Read3),
210    (4, Read4),
211    (5, Read5)
212);
213impl_attribute_reads!(
214    (0, Read0),
215    (1, Read1),
216    (2, Read2),
217    (3, Read3),
218    (4, Read4),
219    (5, Read5),
220    (6, Read6)
221);
222impl_attribute_reads!(
223    (0, Read0),
224    (1, Read1),
225    (2, Read2),
226    (3, Read3),
227    (4, Read4),
228    (5, Read5),
229    (6, Read6),
230    (7, Read7)
231);
232impl_attribute_reads!(
233    (0, Read0),
234    (1, Read1),
235    (2, Read2),
236    (3, Read3),
237    (4, Read4),
238    (5, Read5),
239    (6, Read6),
240    (7, Read7),
241    (8, Read8)
242);
243impl_attribute_reads!(
244    (0, Read0),
245    (1, Read1),
246    (2, Read2),
247    (3, Read3),
248    (4, Read4),
249    (5, Read5),
250    (6, Read6),
251    (7, Read7),
252    (8, Read8),
253    (9, Read9)
254);
255impl_attribute_reads!(
256    (0, Read0),
257    (1, Read1),
258    (2, Read2),
259    (3, Read3),
260    (4, Read4),
261    (5, Read5),
262    (6, Read6),
263    (7, Read7),
264    (8, Read8),
265    (9, Read9),
266    (10, Read10)
267);
268impl_attribute_reads!(
269    (0, Read0),
270    (1, Read1),
271    (2, Read2),
272    (3, Read3),
273    (4, Read4),
274    (5, Read5),
275    (6, Read6),
276    (7, Read7),
277    (8, Read8),
278    (9, Read9),
279    (10, Read10),
280    (11, Read11)
281);
282
283impl Client {
284    /// Reads committed Attribute values from the current or latest run of a Flow.
285    ///
286    /// Since 1.31.0.
287    ///
288    /// Use this method to read Flow state from outside the Flow without defining an RPC.
289    /// Pass a tuple of reads.
290    /// Each read is an [`Attribute`] or an [`AttributeMapInstance`] from
291    /// [`AttributeMap::instance`](crate::AttributeMap::instance), usually by reference.
292    /// The method returns a tuple of the same arity.
293    /// Each element is `Some(value)` when the value is set and `None` when it is not set.
294    /// A set value that equals the type's default, such as an empty string or `0`, is still
295    /// `Some`.
296    /// Pass a one-element tuple, such as `(&ORDER_STATUS,)`, to read one value.
297    /// A call accepts 1 to 12 reads.
298    /// Read more values with several calls.
299    /// Values from different calls can come from different commits.
300    ///
301    /// The Client sends every key in one server request.
302    /// All values of one call therefore come from the same commit.
303    /// No Worker runs, so the call works while no Worker is available.
304    /// The call reads the current run, or the latest run when no run is open.
305    /// A closed run stays readable until it is purged.
306    ///
307    /// The call sees only committed state.
308    /// It does not see writes that a running RPC or Step has staged but not yet committed.
309    /// An RPC handler that reads through its Context, in contrast, also sees its own staged
310    /// writes.
311    ///
312    /// Hard-delete Attributes and AttributeMaps, defined with
313    /// [`Attribute::hard_delete_on_null`], are readable.
314    /// Dex resolves their stored values before it responds.
315    /// A deleted hard-delete value reads as `None`.
316    /// The Client resolves large-value references through the shared
317    /// [`BlobCache`](crate::BlobCache) and the server, as other Client reads do.
318    /// It decodes every value before it returns.
319    /// On any error the call returns no values.
320    /// The method blocks the calling thread until the server responds and every value is
321    /// decoded.
322    ///
323    /// Every read must match a definition declared by some Flow in the Client's
324    /// [`Registry`](crate::Registry).
325    /// A definition matches when it has the same name, the same kind (Attribute or
326    /// AttributeMap), the same Rust value type, and the same index, `sync_to_attribute_store`,
327    /// and `hard_delete_on_null` settings.
328    /// Channels and Streams with the same name are ignored.
329    /// The server does not check that the target Flow's type declares the definition.
330    /// A same-named Attribute of another type in the target Flow fails to decode.
331    ///
332    /// # Examples
333    ///
334    /// ```no_run
335    /// use dex_sdk::{Attribute, AttributeMap, Client, SdkError};
336    /// use std::sync::LazyLock;
337    ///
338    /// #[derive(serde::Deserialize, serde::Serialize)]
339    /// struct Note {
340    ///     text: String,
341    /// }
342    ///
343    /// static ORDER_STATUS: LazyLock<Attribute<String>> =
344    ///     LazyLock::new(|| Attribute::new("order-status"));
345    /// static NOTES: LazyLock<AttributeMap<Note>> = LazyLock::new(|| AttributeMap::new("notes"));
346    ///
347    /// fn describe_order(client: &Client, order_id: &str) -> Result<String, SdkError> {
348    ///     let (status, note) =
349    ///         match client.get_attributes(order_id, (&ORDER_STATUS, &NOTES.instance("n1"))) {
350    ///             Ok(values) => values,
351    ///             Err(SdkError::FlowNotFound { .. }) => return Ok(format!("{order_id} does not exist")),
352    ///             Err(error) => return Err(error),
353    ///         };
354    ///     let status = status.unwrap_or_else(|| "new".to_string());
355    ///     let note = note.map_or_else(|| "no note yet".to_string(), |note| note.text);
356    ///     Ok(format!("{order_id}: {status} ({note})"))
357    /// }
358    /// ```
359    ///
360    /// # Parameters
361    ///
362    /// - `flow_id` is the Flow ID. It must be non-blank.
363    /// - `reads` is a tuple of 1 to 12 [`AttributeRead`] values. Two reads of the same Attribute
364    ///   or AttributeMap instance are rejected.
365    ///
366    /// Returns one `Option<T>` per read, in read order, where `T` is the read's value type.
367    ///
368    /// # Errors
369    ///
370    /// Every error message except the one for a blank Flow ID names the Flow ID.
371    /// An error about one read or value also names its Attribute name or physical key.
372    /// Service-backed errors report `get_attributes` as their
373    /// [`ServiceError::operation`](crate::ServiceError::operation).
374    ///
375    /// - [`SdkError::InvalidArgument`] for an empty or whitespace-only Flow ID, an empty map
376    ///   instance key, a map instance key that contains `/`, or a duplicate read. The Client does
377    ///   not contact Dex.
378    /// - [`SdkError::FlowDefinition`] when no Flow in the Registry declares a matching definition.
379    ///   The Client does not contact Dex.
380    /// - [`SdkError::FlowNotFound`] when the Flow does not exist or its latest run is being purged
381    ///   when the values are read.
382    /// - [`SdkError::ValueMapping`] when a stored value cannot be decoded into its value type, or
383    ///   a large-value reference cannot be resolved through the blob cache. A decode error gives
384    ///   the target type and a reason, such as a missing field name, but never quotes the value.
385    ///   A purge of the run, or a concurrent overwrite of a large value, between the read and
386    ///   the large-value resolution also returns this error. Retry the call to read fresh state.
387    /// - [`SdkError::Service`] for an invalid server response, a failed large-value request, or
388    ///   another server or transport failure. Other service-backed variants are possible when
389    ///   Dex reports a more specific status.
390    pub fn get_attributes<Reads: AttributeReads>(
391        &self,
392        flow_id: &str,
393        reads: Reads,
394    ) -> SdkResult<Reads::Values> {
395        let keys = self.validate_attribute_reads(flow_id, reads.attribute_read_targets())?;
396        let values = self.fetch_attribute_values(flow_id, &keys)?;
397        let mut decoder = sealed::AttributeValueDecoder {
398            flow_id: flow_id.to_string(),
399            values: keys.into_iter().zip(values).collect::<Vec<_>>().into_iter(),
400        };
401        Reads::decode_attribute_values(&mut decoder)
402    }
403
404    /// Returns the physical key of each read in read order, or the first invalid read's error.
405    fn validate_attribute_reads(
406        &self,
407        flow_id: &str,
408        targets: Vec<sealed::AttributeReadTarget>,
409    ) -> SdkResult<Vec<String>> {
410        if flow_id.trim().is_empty() {
411            return Err(invalid("get_attributes requires a non-blank Flow ID"));
412        }
413        let mut keys = Vec::with_capacity(targets.len());
414        let mut seen_keys = HashSet::with_capacity(targets.len());
415        for target in targets {
416            let name = &target.definition.name;
417            let read_context = || format!("get_attributes Flow {flow_id:?} Attribute {name:?}");
418            self.registry
419                .require_registered_attribute(&target.definition)
420                .map_err(|error| {
421                    error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, read_context())
422                })?;
423            let key = match &target.instance {
424                Some(instance) => map_physical_name(name, instance).map_err(|error| {
425                    error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, read_context())
426                })?,
427                None => name.clone(),
428            };
429            if !seen_keys.insert(key.clone()) {
430                return Err(invalid(format!(
431                    "get_attributes Flow {flow_id:?} has duplicate Attribute read {key:?}"
432                )));
433            }
434            keys.push(key);
435        }
436        Ok(keys)
437    }
438
439    /// Returns one hydrated value slot per key, in key order; `None` means the key is not set.
440    fn fetch_attribute_values(
441        &self,
442        flow_id: &str,
443        keys: &[String],
444    ) -> SdkResult<Vec<Option<ProtoValue>>> {
445        let call_context = format!("get_attributes Flow {flow_id:?} Attributes {keys:?}");
446        let with_context = |error: SdkError| {
447            error.with_call_context(GET_ATTRIBUTES_OPERATION, flow_id, call_context.clone())
448        };
449        self.require_server_protocol(GET_ATTRIBUTES_OPERATION)?;
450        let mut service = self.service.clone();
451        let response = self
452            .runtime
453            .block_on(service.get_attributes(GetAttributesRequest {
454                flow_id: flow_id.to_string(),
455                run_id: String::new(),
456                keys: keys.to_vec(),
457                all_keys: false,
458            }))
459            .map_err(|status| {
460                with_context(SdkError::from_status(
461                    status,
462                    GET_ATTRIBUTES_OPERATION,
463                    Some(flow_id),
464                    FlowTargetRequirement::Existing,
465                ))
466            })?
467            .into_inner();
468        let response_values =
469            index_response_values(keys, response.attributes).map_err(with_context)?;
470        let (set_keys, set_values): (Vec<String>, Vec<ProtoValue>) =
471            response_values.into_iter().unzip();
472        let hydrated_values = self
473            .runtime
474            .block_on(self.hydrator.hydrate_all(flow_id, set_values))
475            .map_err(|error| {
476                error
477                    .with_flow_target_requirement(FlowTargetRequirement::Existing)
478                    .with_call_context(
479                        GET_ATTRIBUTES_OPERATION,
480                        flow_id,
481                        format!("{call_context} LoadBlobs"),
482                    )
483            })?;
484        let mut hydrated_values_by_key = set_keys
485            .into_iter()
486            .zip(hydrated_values)
487            .collect::<HashMap<_, _>>();
488        Ok(keys
489            .iter()
490            .map(|key| hydrated_values_by_key.remove(key))
491            .collect())
492    }
493}
494
495/// Returns the value of every set key, or an error for an unrequested, repeated, or valueless
496/// response entry.
497fn index_response_values(
498    keys: &[String],
499    attributes: Vec<Kv>,
500) -> SdkResult<HashMap<String, ProtoValue>> {
501    let requested_keys = keys.iter().map(String::as_str).collect::<HashSet<_>>();
502    let mut response_values = HashMap::with_capacity(attributes.len());
503    for attribute in attributes {
504        let key = attribute.key;
505        let response_error = |problem: &str| SdkError::Service {
506            service: ServiceError::local(
507                GET_ATTRIBUTES_OPERATION,
508                format!("GetAttributes returned {problem} {key:?}"),
509            ),
510        };
511        if !requested_keys.contains(key.as_str()) {
512            return Err(response_error("unexpected Attribute"));
513        }
514        if response_values.contains_key(&key) {
515            return Err(response_error("duplicate Attribute"));
516        }
517        let Some(value) = attribute.value else {
518            return Err(response_error("no value for Attribute"));
519        };
520        response_values.insert(key, value);
521    }
522    Ok(response_values)
523}
524
525#[cfg(test)]
526mod tests {
527    use dex_protocol::dex::{EncodedObject, Kv, Value as ProtoValue, value};
528
529    use super::index_response_values;
530    use super::sealed::AttributeValueDecoder;
531    use crate::SdkError;
532
533    #[derive(Debug, serde::Deserialize, serde::Serialize)]
534    #[serde(deny_unknown_fields)]
535    struct Credential {
536        token: String,
537    }
538
539    #[test]
540    fn decode_errors_keep_value_free_detail_and_omit_the_stored_value() {
541        let mut decoder = decoder(vec![
542            string_value("sekrit"),
543            json_value(br#"{}"#),
544            json_value(br#"{"token":"sekrit","extra":"sekrit"}"#),
545            json_value(br#"{"token":"sekrit""#),
546        ]);
547
548        let invalid_type = decode_error(decoder.decode_next::<i64>());
549        assert!(invalid_type.contains("\"order-1\""), "{invalid_type}");
550        assert!(invalid_type.contains("\"key-0\""), "{invalid_type}");
551        assert!(invalid_type.contains("i64"), "{invalid_type}");
552        assert!(invalid_type.contains("data error"), "{invalid_type}");
553
554        let missing_field = decode_error(decoder.decode_next::<Credential>());
555        assert!(missing_field.contains("\"key-1\""), "{missing_field}");
556        assert!(
557            missing_field.contains("missing field `token`"),
558            "{missing_field}"
559        );
560
561        let unknown_field = decode_error(decoder.decode_next::<Credential>());
562        assert!(
563            unknown_field.contains("data error: unknown field"),
564            "{unknown_field}"
565        );
566        assert!(!unknown_field.contains("expected"), "{unknown_field}");
567        assert!(!unknown_field.contains("extra"), "{unknown_field}");
568
569        let syntax = decode_error(decoder.decode_next::<Credential>());
570        assert!(syntax.contains("unexpected end of input"), "{syntax}");
571        assert!(syntax.contains("at line 1 column"), "{syntax}");
572
573        for message in [invalid_type, missing_field, unknown_field, syntax] {
574            assert!(!message.contains("sekrit"), "{message}");
575        }
576    }
577
578    #[test]
579    fn response_values_are_indexed_by_requested_key() {
580        let keys = vec!["status".to_string(), "notes/n1".to_string()];
581
582        let values = index_response_values(&keys, vec![kv("notes/n1", Some(string_value("note")))])
583            .expect("a subset of the requested keys is valid");
584
585        assert_eq!(1, values.len());
586        assert_eq!(Some(&string_value("note")), values.get("notes/n1"));
587    }
588
589    #[test]
590    fn response_values_reject_unexpected_duplicate_and_valueless_keys() {
591        let keys = vec!["status".to_string()];
592        for (attributes, expected) in [
593            (
594                vec![kv("other", Some(string_value("x")))],
595                "GetAttributes returned unexpected Attribute \"other\"",
596            ),
597            (
598                vec![
599                    kv("status", Some(string_value("x"))),
600                    kv("status", Some(string_value("y"))),
601                ],
602                "GetAttributes returned duplicate Attribute \"status\"",
603            ),
604            (
605                vec![kv("status", None)],
606                "GetAttributes returned no value for Attribute \"status\"",
607            ),
608        ] {
609            let error = index_response_values(&keys, attributes)
610                .expect_err("an invalid response is rejected");
611            assert!(matches!(error, SdkError::Service { .. }));
612            assert_eq!(expected, error.to_string());
613        }
614    }
615
616    fn decoder(values: Vec<ProtoValue>) -> AttributeValueDecoder {
617        AttributeValueDecoder {
618            flow_id: "order-1".to_string(),
619            values: values
620                .into_iter()
621                .enumerate()
622                .map(|(index, value)| (format!("key-{index}"), Some(value)))
623                .collect::<Vec<_>>()
624                .into_iter(),
625        }
626    }
627
628    fn decode_error<T: std::fmt::Debug>(result: crate::SdkResult<Option<T>>) -> String {
629        let error = result.expect_err("the value does not decode");
630        assert!(matches!(error, SdkError::ValueMapping { .. }), "{error:?}");
631        error.to_string()
632    }
633
634    fn kv(key: &str, value: Option<ProtoValue>) -> Kv {
635        Kv {
636            key: key.to_string(),
637            value,
638        }
639    }
640
641    fn string_value(text: &str) -> ProtoValue {
642        ProtoValue {
643            kind: Some(value::Kind::StringValue(text.to_string())),
644        }
645    }
646
647    fn json_value(payload: &[u8]) -> ProtoValue {
648        ProtoValue {
649            kind: Some(value::Kind::ObjValue(EncodedObject {
650                encoding: "json".to_string(),
651                payload: payload.to_vec(),
652            })),
653        }
654    }
655}