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