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        self.require_server_protocol(GET_ATTRIBUTES_OPERATION)?;
444        let mut service = self.service.clone();
445        let response = self
446            .runtime
447            .block_on(service.get_attributes(GetAttributesRequest {
448                flow_id: flow_id.to_string(),
449                run_id: String::new(),
450                keys: keys.to_vec(),
451                all_keys: false,
452            }))
453            .map_err(|status| {
454                with_context(SdkError::from_status(
455                    status,
456                    GET_ATTRIBUTES_OPERATION,
457                    Some(flow_id),
458                    FlowTargetRequirement::Existing,
459                ))
460            })?
461            .into_inner();
462        let response_values =
463            index_response_values(keys, response.attributes).map_err(with_context)?;
464        let (set_keys, set_values): (Vec<String>, Vec<ProtoValue>) =
465            response_values.into_iter().unzip();
466        let hydrated_values = self
467            .runtime
468            .block_on(self.hydrator.hydrate_all(flow_id, set_values))
469            .map_err(|error| {
470                error
471                    .with_flow_target_requirement(FlowTargetRequirement::Existing)
472                    .with_call_context(
473                        GET_ATTRIBUTES_OPERATION,
474                        flow_id,
475                        format!("{call_context} LoadBlobs"),
476                    )
477            })?;
478        let mut hydrated_values_by_key = set_keys
479            .into_iter()
480            .zip(hydrated_values)
481            .collect::<HashMap<_, _>>();
482        Ok(keys
483            .iter()
484            .map(|key| hydrated_values_by_key.remove(key))
485            .collect())
486    }
487}
488
489/// Returns the value of every set key, or an error for an unrequested, repeated, or valueless
490/// response entry.
491fn index_response_values(
492    keys: &[String],
493    attributes: Vec<Kv>,
494) -> SdkResult<HashMap<String, ProtoValue>> {
495    let requested_keys = keys.iter().map(String::as_str).collect::<HashSet<_>>();
496    let mut response_values = HashMap::with_capacity(attributes.len());
497    for attribute in attributes {
498        let key = attribute.key;
499        let response_error = |problem: &str| SdkError::Service {
500            service: ServiceError::local(
501                GET_ATTRIBUTES_OPERATION,
502                format!("GetAttributes returned {problem} {key:?}"),
503            ),
504        };
505        if !requested_keys.contains(key.as_str()) {
506            return Err(response_error("unexpected Attribute"));
507        }
508        if response_values.contains_key(&key) {
509            return Err(response_error("duplicate Attribute"));
510        }
511        let Some(value) = attribute.value else {
512            return Err(response_error("no value for Attribute"));
513        };
514        response_values.insert(key, value);
515    }
516    Ok(response_values)
517}
518
519#[cfg(test)]
520mod tests {
521    use dex_protocol::dex::{EncodedObject, Kv, Value as ProtoValue, value};
522
523    use super::index_response_values;
524    use super::sealed::AttributeValueDecoder;
525    use crate::SdkError;
526
527    #[derive(Debug, serde::Deserialize, serde::Serialize)]
528    #[serde(deny_unknown_fields)]
529    struct Credential {
530        token: String,
531    }
532
533    #[test]
534    fn decode_errors_keep_value_free_detail_and_omit_the_stored_value() {
535        let mut decoder = decoder(vec![
536            string_value("sekrit"),
537            json_value(br#"{}"#),
538            json_value(br#"{"token":"sekrit","extra":"sekrit"}"#),
539            json_value(br#"{"token":"sekrit""#),
540        ]);
541
542        let invalid_type = decode_error(decoder.decode_next::<i64>());
543        assert!(invalid_type.contains("\"order-1\""), "{invalid_type}");
544        assert!(invalid_type.contains("\"key-0\""), "{invalid_type}");
545        assert!(invalid_type.contains("i64"), "{invalid_type}");
546        assert!(invalid_type.contains("data error"), "{invalid_type}");
547
548        let missing_field = decode_error(decoder.decode_next::<Credential>());
549        assert!(missing_field.contains("\"key-1\""), "{missing_field}");
550        assert!(
551            missing_field.contains("missing field `token`"),
552            "{missing_field}"
553        );
554
555        let unknown_field = decode_error(decoder.decode_next::<Credential>());
556        assert!(
557            unknown_field.contains("data error: unknown field"),
558            "{unknown_field}"
559        );
560        assert!(!unknown_field.contains("expected"), "{unknown_field}");
561        assert!(!unknown_field.contains("extra"), "{unknown_field}");
562
563        let syntax = decode_error(decoder.decode_next::<Credential>());
564        assert!(syntax.contains("unexpected end of input"), "{syntax}");
565        assert!(syntax.contains("at line 1 column"), "{syntax}");
566
567        for message in [invalid_type, missing_field, unknown_field, syntax] {
568            assert!(!message.contains("sekrit"), "{message}");
569        }
570    }
571
572    #[test]
573    fn response_values_are_indexed_by_requested_key() {
574        let keys = vec!["status".to_string(), "notes/n1".to_string()];
575
576        let values = index_response_values(&keys, vec![kv("notes/n1", Some(string_value("note")))])
577            .expect("a subset of the requested keys is valid");
578
579        assert_eq!(1, values.len());
580        assert_eq!(Some(&string_value("note")), values.get("notes/n1"));
581    }
582
583    #[test]
584    fn response_values_reject_unexpected_duplicate_and_valueless_keys() {
585        let keys = vec!["status".to_string()];
586        for (attributes, expected) in [
587            (
588                vec![kv("other", Some(string_value("x")))],
589                "GetAttributes returned unexpected Attribute \"other\"",
590            ),
591            (
592                vec![
593                    kv("status", Some(string_value("x"))),
594                    kv("status", Some(string_value("y"))),
595                ],
596                "GetAttributes returned duplicate Attribute \"status\"",
597            ),
598            (
599                vec![kv("status", None)],
600                "GetAttributes returned no value for Attribute \"status\"",
601            ),
602        ] {
603            let error = index_response_values(&keys, attributes)
604                .expect_err("an invalid response is rejected");
605            assert!(matches!(error, SdkError::Service { .. }));
606            assert_eq!(expected, error.to_string());
607        }
608    }
609
610    fn decoder(values: Vec<ProtoValue>) -> AttributeValueDecoder {
611        AttributeValueDecoder {
612            flow_id: "order-1".to_string(),
613            values: values
614                .into_iter()
615                .enumerate()
616                .map(|(index, value)| (format!("key-{index}"), Some(value)))
617                .collect::<Vec<_>>()
618                .into_iter(),
619        }
620    }
621
622    fn decode_error<T: std::fmt::Debug>(result: crate::SdkResult<Option<T>>) -> String {
623        let error = result.expect_err("the value does not decode");
624        assert!(matches!(error, SdkError::ValueMapping { .. }), "{error:?}");
625        error.to_string()
626    }
627
628    fn kv(key: &str, value: Option<ProtoValue>) -> Kv {
629        Kv {
630            key: key.to_string(),
631            value,
632        }
633    }
634
635    fn string_value(text: &str) -> ProtoValue {
636        ProtoValue {
637            kind: Some(value::Kind::StringValue(text.to_string())),
638        }
639    }
640
641    fn json_value(payload: &[u8]) -> ProtoValue {
642        ProtoValue {
643            kind: Some(value::Kind::ObjValue(EncodedObject {
644                encoding: "json".to_string(),
645                payload: payload.to_vec(),
646            })),
647        }
648    }
649}