Skip to main content

sui_display/v2/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5use std::sync::atomic::AtomicUsize;
6
7use mysten_common::ZipDebugEqIteratorExt;
8
9use futures::future::try_join_all;
10use futures::join;
11use indexmap::IndexMap;
12use sui_types::object::rpc_visitor as RV;
13
14use crate::v2::meter::Meter;
15use crate::v2::parser::Chain;
16use crate::v2::parser::Literal;
17use crate::v2::parser::Parser;
18use crate::v2::parser::Strand;
19mod error;
20mod interpreter;
21mod lexer;
22mod meter;
23mod parser;
24mod peek;
25mod value;
26mod visitor;
27mod writer;
28
29pub use crate::v2::error::Error;
30pub use crate::v2::error::FormatError;
31pub use crate::v2::interpreter::Interpreter;
32pub use crate::v2::meter::Limits;
33pub use crate::v2::value::OwnedSlice;
34pub use crate::v2::value::Store;
35pub use crate::v2::value::Value;
36
37/// A path into a Move value, to extract a sub-slice.
38pub struct Extract<'s>(Chain<'s>);
39
40/// A literal value, representing a dynamic field name.
41pub struct Name<'s>(Literal<'s>);
42
43/// A parsed format string.
44pub struct Format<'s>(Vec<Strand<'s>>);
45
46/// A collection of format strings that are evaluated to a string-to-string mapping.
47pub struct Display<'s> {
48    fields: Vec<Field<'s>>,
49}
50
51/// Parsed key-value pair for a single field in the format.
52struct Field<'s> {
53    key: Sourced<'s, Vec<Strand<'s>>>,
54    val: Sourced<'s, Vec<Strand<'s>>>,
55}
56
57/// Some value associated with the source it came from.
58struct Sourced<'s, T> {
59    src: &'s str,
60    val: Result<T, FormatError>,
61}
62
63impl<'s> Extract<'s> {
64    /// Parse a string as a sequence of nested accessors.
65    ///
66    /// `limits` bounds the dimensions (depth, number of output nodes, max number of object loads)
67    /// that the parsed accessor can consume.
68    pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
69        let mut budget = limits.budget();
70        let mut meter = Meter::new(limits.max_depth, &mut budget);
71        let chain = Parser::chain(src, &mut meter)?;
72
73        Ok(Self(chain))
74    }
75
76    /// Pull the value located at this extractor's path out of the object provided as its `bytes`
77    /// and `layout`, and with support for dynamically fetching additional objects from `store` as
78    /// needed.
79    ///
80    /// It is only valid to extract slices from other slices (not literal values).
81    pub async fn extract<S: Store>(
82        &'s self,
83        interpreter: &'s Interpreter<S>,
84    ) -> Result<Option<Value<'s>>, FormatError> {
85        interpreter.eval_chain(&self.0).await
86    }
87}
88
89impl<'s> Name<'s> {
90    /// Parse a string as a literal value.
91    ///
92    /// `limits` bounds the dimensions (depth, number of output nodes, max number of object loads)
93    /// that the parsed literal can consume.
94    pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
95        let mut budget = limits.budget();
96        let mut meter = Meter::new(limits.max_depth, &mut budget);
97        let literal = Parser::literal(src, &mut meter)?;
98
99        Ok(Self(literal))
100    }
101
102    /// Evaluate the literal representing the dynamic field name, returning a `Value` which can be
103    /// used to derive a dynamic field or dynamic object field ID.
104    pub async fn eval<S: Store>(
105        &'s self,
106        interpreter: &'s Interpreter<S>,
107    ) -> Result<Option<Value<'s>>, FormatError> {
108        interpreter.eval_literal(&self.0).await
109    }
110}
111
112impl<'s> Format<'s> {
113    /// Parse a string as a format.
114    ///
115    /// `limits` bounds the dimensions (depth, number of output nodes, max number of object loads)
116    /// that the parsed format string can consume.
117    pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
118        let mut budget = limits.budget();
119        let mut meter = Meter::new(limits.max_depth, &mut budget);
120        let format = Parser::format(src, &mut meter)?;
121
122        Ok(Self(format))
123    }
124
125    /// Evaluate the format string returning a formatted JSON value.
126    pub async fn format<V: RV::Format>(
127        &'s self,
128        interpreter: &'s Interpreter<impl Store>,
129        max_depth: usize,
130        max_output_size: usize,
131    ) -> Result<V, FormatError> {
132        let used_size = AtomicUsize::new(0);
133        let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
134        let Some(value) = interpreter.eval_strands(&self.0).await? else {
135            return Ok(V::null(&mut meter)?);
136        };
137
138        writer::write(meter, value)
139    }
140}
141
142impl<'s> Display<'s> {
143    /// Convert the contents of a `Display` object into a `Format` by parsing each of its names and
144    /// values as format strings.
145    ///
146    /// `limits` bound the dimensions (depth, number of output nodes, max number of object loads)
147    /// that the parsed format can consume.
148    ///
149    /// This operation supports partial failures (if one of the format strings is invalid), but
150    /// will fail completely if the display overall is detected to exceed the provided `limits`.
151    pub fn parse(
152        limits: Limits,
153        display_fields: impl IntoIterator<Item = (&'s str, &'s str)>,
154    ) -> Result<Self, Error> {
155        let mut fields = Vec::new();
156        let mut budget = limits.budget();
157        let mut meter = Meter::new(limits.max_depth, &mut budget);
158
159        let mut parse = |src: &'s str| {
160            let val = match Parser::format(src, &mut meter) {
161                Err(FormatError::TooBig) => return Err(Error::TooBig),
162                Err(FormatError::TooManyLoads) => return Err(Error::TooManyLoads),
163                Err(e) => Err(e),
164                Ok(ast) => Ok(ast),
165            };
166
167            Ok(Sourced { src, val })
168        };
169
170        for (k, v) in display_fields.into_iter() {
171            let key = parse(k)?;
172            let val = parse(v)?;
173            fields.push(Field { key, val });
174        }
175
176        Ok(Self { fields })
177    }
178
179    /// Render the format with the provided `interpreter`
180    ///
181    /// This operation requires all field names to evaluate successfully to unique strings, and for
182    /// the overall output to be bounded by `max_depth` and `max_output_size`, but otherwise
183    /// supports partial failures (if one of the field values fails to parse or evaluate).
184    pub async fn display<V: RV::Format>(
185        &'s self,
186        max_depth: usize,
187        max_output_size: usize,
188        interpreter: &'s Interpreter<impl Store>,
189    ) -> Result<IndexMap<String, Result<V, FormatError>>, Error> {
190        let used_size = Arc::new(AtomicUsize::new(0));
191        let mut output = IndexMap::new();
192
193        // You think you want to factor a helper out to do the evaluation and error handling, but
194        // trust me, you don't.
195
196        let names = try_join_all(self.fields.iter().map(|kvp| {
197            let used_size = used_size.clone();
198            async move {
199                let strands = match kvp.key.val.as_ref() {
200                    Ok(strands) => strands,
201                    Err(e) => return Ok(Err(e.clone())),
202                };
203
204                let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
205                let evaluated = match interpreter.eval_strands(strands).await {
206                    Ok(Some(v)) => v,
207                    Ok(None) => match V::null(&mut meter) {
208                        Ok(value) => return Ok(Ok(value)),
209                        Err(err) => return Ok(Err(err.into())),
210                    },
211                    Err(e) => return Ok(Err(e)),
212                };
213
214                match writer::write(meter, evaluated) {
215                    Err(FormatError::TooMuchOutput) => Err(Error::TooMuchOutput),
216                    other => Ok(other),
217                }
218            }
219        }));
220
221        let values = try_join_all(self.fields.iter().map(|kvp| {
222            let used_size = used_size.clone();
223            async move {
224                let strands = match kvp.val.val.as_ref() {
225                    Ok(strands) => strands,
226                    Err(e) => return Ok(Err(e.clone())),
227                };
228
229                let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
230                let evaluated = match interpreter.eval_strands(strands).await {
231                    Ok(Some(v)) => v,
232                    Ok(None) => match V::null(&mut meter) {
233                        Ok(value) => return Ok(Ok(value)),
234                        Err(err) => return Ok(Err(err.into())),
235                    },
236                    Err(e) => return Ok(Err(e)),
237                };
238
239                match writer::write(meter, evaluated) {
240                    Err(FormatError::TooMuchOutput) => Err(Error::TooMuchOutput),
241                    other => Ok(other),
242                }
243            }
244        }));
245
246        let (names, values) = join!(names, values);
247
248        let names = names?;
249        let values = values?;
250
251        for ((field, name), value) in self.fields.iter().zip_debug_eq(names).zip_debug_eq(values) {
252            use indexmap::map::Entry;
253
254            let src = field.key.src;
255
256            let n = match name {
257                Ok(v) if v.is_string() => v.as_string().unwrap().to_owned(),
258                Ok(v) if v.is_null() => return Err(Error::NameEmpty(src.to_owned())),
259                Ok(_) => return Err(Error::NameInvalid(src.to_owned())),
260                Err(e) => return Err(Error::NameEvaluation(src.to_owned(), e)),
261            };
262
263            match output.entry(n) {
264                Entry::Occupied(e) => return Err(Error::NameDuplicate(e.key().to_owned())),
265                Entry::Vacant(e) => {
266                    e.insert(value);
267                }
268            }
269        }
270
271        Ok(output)
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use std::str::FromStr;
278    use std::sync::Arc;
279    use std::sync::atomic::AtomicUsize;
280
281    use async_trait::async_trait;
282    use base64::Engine as _;
283    use base64::engine::general_purpose::STANDARD;
284    use insta::assert_debug_snapshot;
285    use insta::assert_json_snapshot;
286    use move_core_types::account_address::AccountAddress;
287    use move_core_types::annotated_value::MoveTypeLayout;
288    use move_core_types::annotated_value::MoveTypeLayout as L;
289    use move_core_types::language_storage::TypeTag;
290    use move_core_types::u256::U256;
291    use serde::Serialize;
292    use sui_types::base_types::move_ascii_str_layout;
293    use sui_types::base_types::move_utf8_str_layout;
294    use sui_types::base_types::url_layout;
295    use sui_types::dynamic_field::DynamicFieldInfo;
296    use sui_types::dynamic_field::derive_dynamic_field_id;
297    use sui_types::id::ID;
298    use sui_types::id::UID;
299    use tokio::sync::Barrier;
300    use tokio::time::Duration;
301
302    use crate::v2::value::tests::MockStore;
303    use crate::v2::value::tests::enum_;
304    use crate::v2::value::tests::optional_;
305    use crate::v2::value::tests::struct_;
306    use crate::v2::value::tests::vec_map;
307    use crate::v2::value::tests::vector_;
308
309    use super::*;
310
311    const ONE_MB: usize = 1024 * 1024;
312
313    /// Helper to parse a path and extract it from the provided object.
314    async fn extract(
315        store: MockStore,
316        bytes: Vec<u8>,
317        layout: MoveTypeLayout,
318        path: &str,
319    ) -> Result<Option<serde_json::Value>, FormatError> {
320        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
321        let used = AtomicUsize::new(0);
322
323        let chain = Extract::parse(Limits::default(), path)?;
324        let Some(value) = chain.extract(&interpreter).await? else {
325            return Ok(None);
326        };
327
328        let meter = writer::Meter::new(&used, usize::MAX, usize::MAX);
329        Ok(Some(value.format_json(meter)?))
330    }
331
332    async fn extract_owned(
333        store: impl Store,
334        bytes: Vec<u8>,
335        layout: MoveTypeLayout,
336        path: &str,
337    ) -> Result<Option<OwnedSlice>, FormatError> {
338        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
339        let chain = Extract::parse(Limits::default(), path)?;
340        let Some(value) = chain.extract(&interpreter).await? else {
341            return Ok(None);
342        };
343
344        Ok(value.into_owned_slice())
345    }
346
347    async fn dynamic_field_id(
348        store: MockStore,
349        bytes: Vec<u8>,
350        layout: MoveTypeLayout,
351        parent: AccountAddress,
352        literal: &str,
353    ) -> Result<Option<AccountAddress>, FormatError> {
354        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
355        let name = Name::parse(Limits::default(), literal)?;
356        let Some(value) = name.eval(&interpreter).await? else {
357            return Ok(None);
358        };
359
360        Ok(Some(value.derive_dynamic_field_id(parent)?.into()))
361    }
362
363    async fn dynamic_object_field_id(
364        store: MockStore,
365        bytes: Vec<u8>,
366        layout: MoveTypeLayout,
367        parent: AccountAddress,
368        literal: &str,
369    ) -> Result<Option<AccountAddress>, FormatError> {
370        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
371        let name = Name::parse(Limits::default(), literal)?;
372        let Some(value) = name.eval(&interpreter).await? else {
373            return Ok(None);
374        };
375
376        Ok(Some(value.derive_dynamic_object_field_id(parent)?.into()))
377    }
378
379    async fn derived_object_id(
380        store: MockStore,
381        bytes: Vec<u8>,
382        layout: MoveTypeLayout,
383        parent: AccountAddress,
384        literal: &str,
385    ) -> Result<Option<AccountAddress>, FormatError> {
386        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
387        let name = Name::parse(Limits::default(), literal)?;
388        let Some(value) = name.eval(&interpreter).await? else {
389            return Ok(None);
390        };
391
392        Ok(Some(value.derive_object_id(parent)?.into()))
393    }
394
395    /// Helper to parse display fields and render them against the provided object.
396    async fn format<'s>(
397        store: impl Store,
398        limits: Limits,
399        bytes: Vec<u8>,
400        layout: MoveTypeLayout,
401        max_depth: usize,
402        max_output_size: usize,
403        fields: impl IntoIterator<Item = (&'s str, &'s str)>,
404    ) -> Result<IndexMap<String, Result<serde_json::Value, FormatError>>, Error> {
405        let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
406        Display::parse(limits, fields)?
407            .display(max_depth, max_output_size, &interpreter)
408            .await
409    }
410
411    #[tokio::test]
412    async fn test_extract_simple() {
413        let bytes = bcs::to_bytes(&(
414            AccountAddress::from_str("0x1234").unwrap(),
415            None::<bool>,
416            Some(true),
417            48u8,
418            vec![1u64, 2u64, 3u64],
419            vec![(4u32, 5u32), (6u32, 7u32), (8u32, 9u32)],
420        ))
421        .unwrap();
422
423        let layout = struct_(
424            "0x1::m::S",
425            vec![
426                ("addr", L::Address),
427                ("none", optional_(L::Bool)),
428                ("some", optional_(L::Bool)),
429                ("posn", struct_("0x1::m::P", vec![("pos0", L::U8)])),
430                ("nums", vector_(L::U64)),
431                ("kvps", vec_map(L::U32, L::U32)),
432            ],
433        );
434
435        let fields = [
436            "addr",
437            "none",
438            "some",
439            "posn.0",
440            "nums[1u64]",
441            "kvps[6u32]",
442            "i.dont.exist",
443        ];
444
445        let mut outputs = Vec::with_capacity(fields.len());
446        for field in fields {
447            outputs.push(
448                extract(MockStore::default(), bytes.clone(), layout.clone(), field)
449                    .await
450                    .unwrap(),
451            );
452        }
453
454        assert_json_snapshot!(outputs, @r###"
455        [
456          "0x0000000000000000000000000000000000000000000000000000000000001234",
457          null,
458          true,
459          48,
460          "2",
461          7,
462          null
463        ]
464        "###);
465    }
466
467    #[tokio::test]
468    async fn test_extract_with_dynamic_loads() {
469        let parent = AccountAddress::from_str("0x5000").unwrap();
470        let child = AccountAddress::from_str("0x5001").unwrap();
471        let bytes = bcs::to_bytes(&parent).unwrap();
472
473        let layout = struct_(
474            "0x1::m::Root",
475            vec![(
476                "parent",
477                struct_(
478                    "0x1::m::Parent",
479                    vec![("id", L::Struct(Box::new(UID::layout())))],
480                ),
481            )],
482        );
483
484        // Add a dynamic field: parent->['df_key'] = (10, 20)
485        // Add a dynamic object field: parent=>['dof_key'] = Child { id, x: 100, y: 200 }
486        let store = MockStore::default()
487            .with_dynamic_field(
488                parent,
489                "df_key",
490                L::Struct(Box::new(move_utf8_str_layout())),
491                (10u64, 20u64),
492                struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
493            )
494            .with_dynamic_object_field(
495                parent,
496                "dof_key",
497                L::Struct(Box::new(move_utf8_str_layout())),
498                (child, 100u64, 200u64),
499                struct_(
500                    "0x1::m::Child",
501                    vec![
502                        ("id", L::Struct(Box::new(UID::layout()))),
503                        ("x", L::U64),
504                        ("y", L::U64),
505                    ],
506                ),
507            );
508
509        let fields = [
510            // Dynamic field access
511            "parent->['df_key'].x",
512            "parent->['df_key'].y",
513            "parent.id->['df_key'].x",
514            // Dynamic object field access
515            "parent=>['dof_key'].x",
516            "parent=>['dof_key'].y",
517            "parent.id=>['dof_key'].id",
518            // Missing dynamic field
519            "parent->['missing']",
520            "parent=>['missing']",
521        ];
522
523        let mut outputs = Vec::with_capacity(fields.len());
524        for field in fields {
525            outputs.push(
526                extract(store.clone(), bytes.clone(), layout.clone(), field)
527                    .await
528                    .unwrap(),
529            );
530        }
531
532        assert_json_snapshot!(outputs, @r###"
533        [
534          "10",
535          "20",
536          "10",
537          "100",
538          "200",
539          "0x0000000000000000000000000000000000000000000000000000000000005001",
540          null,
541          null
542        ]
543        "###);
544    }
545
546    #[tokio::test]
547    async fn test_extract_with_derived_object_loads() {
548        let parent = AccountAddress::from_str("0x5100").unwrap();
549        let child = AccountAddress::from_str("0x5101").unwrap();
550        let bytes = bcs::to_bytes(&parent).unwrap();
551
552        let layout = struct_(
553            "0x1::m::Root",
554            vec![(
555                "parent",
556                struct_(
557                    "0x1::m::Parent",
558                    vec![("id", L::Struct(Box::new(UID::layout())))],
559                ),
560            )],
561        );
562
563        let store = MockStore::default().with_derived_object(
564            parent,
565            "derived_key",
566            L::Struct(Box::new(move_utf8_str_layout())),
567            (child, 111u64, 222u64),
568            struct_(
569                "0x1::m::Child",
570                vec![
571                    ("id", L::Struct(Box::new(UID::layout()))),
572                    ("x", L::U64),
573                    ("y", L::U64),
574                ],
575            ),
576        );
577
578        let fields = [
579            "parent~>['derived_key'].x",
580            "parent~>['derived_key'].y",
581            "parent.id~>['derived_key'].id",
582            "parent~>['missing']",
583        ];
584
585        let mut outputs = Vec::with_capacity(fields.len());
586        for field in fields {
587            outputs.push(
588                extract(store.clone(), bytes.clone(), layout.clone(), field)
589                    .await
590                    .unwrap(),
591            );
592        }
593
594        assert_json_snapshot!(outputs, @r###"
595        [
596          "111",
597          "222",
598          "0x0000000000000000000000000000000000000000000000000000000000005101",
599          null
600        ]
601        "###);
602    }
603
604    #[tokio::test]
605    async fn test_dynamic_field_names() {
606        let parent = AccountAddress::from_str("0x4242").unwrap();
607
608        // Dummy object to interpret against (not used for literal evaluation)
609        let obj_bytes = bcs::to_bytes(&0u8).unwrap();
610        let obj_layout = L::U8;
611
612        // Test cases: (literal, expected_type_tag, expected_bcs_bytes)
613        let cases: Vec<(&str, &str, Vec<u8>)> = vec![
614            (
615                "'hello'",
616                "0x1::string::String",
617                bcs::to_bytes(&"hello").unwrap(),
618            ),
619            ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
620            ("123u128", "u128", bcs::to_bytes(&123u128).unwrap()),
621            (
622                "@0xabc",
623                "address",
624                bcs::to_bytes(&AccountAddress::from_str("0xabc").unwrap()).unwrap(),
625            ),
626            (
627                "0x1::m::Key(99u32, 'test')",
628                "0x1::m::Key",
629                bcs::to_bytes(&(99u32, "test")).unwrap(),
630            ),
631            (
632                "0x1::m::Key<u32, 0x1::string::String>(99u32, 'test')",
633                "0x1::m::Key<u32, 0x1::string::String>",
634                bcs::to_bytes(&(99u32, "test")).unwrap(),
635            ),
636            (
637                "vector[1u8, 2u8, 3u8]",
638                "vector<u8>",
639                bcs::to_bytes(&vec![1u8, 2u8, 3u8]).unwrap(),
640            ),
641        ];
642
643        for (literal, type_, bytes) in cases {
644            let id = dynamic_field_id(
645                MockStore::default(),
646                obj_bytes.clone(),
647                obj_layout.clone(),
648                parent,
649                literal,
650            )
651            .await
652            .unwrap()
653            .unwrap();
654
655            let type_: TypeTag = type_.parse().unwrap();
656            let expected = derive_dynamic_field_id(parent, &type_, &bytes).unwrap();
657            assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
658        }
659    }
660
661    #[tokio::test]
662    async fn test_dynamic_object_field_names() {
663        let parent = AccountAddress::from_str("0x4242").unwrap();
664
665        // Dummy object to interpret against (not used for literal evaluation)
666        let obj_bytes = bcs::to_bytes(&0u8).unwrap();
667        let obj_layout = L::U8;
668
669        // Test cases: (literal, expected_type_tag, expected_bcs_bytes)
670        let cases: Vec<(&str, &str, Vec<u8>)> = vec![
671            (
672                "'hello'",
673                "0x1::string::String",
674                bcs::to_bytes(&"hello").unwrap(),
675            ),
676            ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
677            ("123u128", "u128", bcs::to_bytes(&123u128).unwrap()),
678            (
679                "@0xabc",
680                "address",
681                bcs::to_bytes(&AccountAddress::from_str("0xabc").unwrap()).unwrap(),
682            ),
683            (
684                "0x1::m::Key(99u32, 'test')",
685                "0x1::m::Key",
686                bcs::to_bytes(&(99u32, "test")).unwrap(),
687            ),
688            (
689                "0x1::m::Key<u32, 0x1::string::String>(99u32, 'test')",
690                "0x1::m::Key<u32, 0x1::string::String>",
691                bcs::to_bytes(&(99u32, "test")).unwrap(),
692            ),
693            (
694                "vector[1u8, 2u8, 3u8]",
695                "vector<u8>",
696                bcs::to_bytes(&vec![1u8, 2u8, 3u8]).unwrap(),
697            ),
698        ];
699
700        for (literal, type_, bytes) in cases {
701            let id = dynamic_object_field_id(
702                MockStore::default(),
703                obj_bytes.clone(),
704                obj_layout.clone(),
705                parent,
706                literal,
707            )
708            .await
709            .unwrap()
710            .unwrap();
711
712            let type_: TypeTag = type_.parse().unwrap();
713            let wrapper_type = DynamicFieldInfo::dynamic_object_field_wrapper(type_);
714            let expected = derive_dynamic_field_id(parent, &wrapper_type.into(), &bytes).unwrap();
715            assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
716        }
717    }
718
719    #[tokio::test]
720    async fn test_derived_object_names() {
721        let parent = AccountAddress::from_str("0x4242").unwrap();
722
723        let obj_bytes = bcs::to_bytes(&0u8).unwrap();
724        let obj_layout = L::U8;
725
726        let cases: Vec<(&str, &str, Vec<u8>)> = vec![
727            (
728                "'hello'",
729                "0x1::string::String",
730                bcs::to_bytes(&"hello").unwrap(),
731            ),
732            ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
733            (
734                "0x1::m::Key(99u32, 'test')",
735                "0x1::m::Key",
736                bcs::to_bytes(&(99u32, "test")).unwrap(),
737            ),
738        ];
739
740        for (literal, type_, bytes) in cases {
741            let id = derived_object_id(
742                MockStore::default(),
743                obj_bytes.clone(),
744                obj_layout.clone(),
745                parent,
746                literal,
747            )
748            .await
749            .unwrap()
750            .unwrap();
751
752            let type_: TypeTag = type_.parse().unwrap();
753            let expected =
754                sui_types::derived_object::derive_object_id(parent, &type_, &bytes).unwrap();
755            assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
756        }
757    }
758
759    #[test]
760    fn test_dynamic_field_name_parse_errors() {
761        let cases = [
762            // Empty input
763            "",
764            // Field access (not a literal)
765            "foo",
766            "foo.bar",
767            // Missing type suffix
768            "42",
769            // Unclosed string
770            "'hello",
771            // Unclosed struct
772            "0x1::m::S(",
773            "0x1::m::S(42u64",
774            // Unclosed vector
775            "vector[1u8, 2u8",
776            // Invalid address
777            "@0xGGG",
778        ];
779
780        for literal in cases {
781            assert!(
782                Name::parse(Limits::default(), literal).is_err(),
783                "expected error for: {literal:?}"
784            );
785        }
786    }
787
788    #[tokio::test]
789    async fn test_format_fields_and_scalars() {
790        let bytes = bcs::to_bytes(&(
791            AccountAddress::from_str("0x4243").unwrap(),
792            AccountAddress::from_str("0x4445").unwrap(),
793            AccountAddress::from_str("0x4647").unwrap(),
794            true,
795            48u8,
796            49u16,
797            50u32,
798            51u64,
799            52u128,
800            U256::from(53u64),
801            "hello",
802            "world",
803            "https://example.com",
804        ))
805        .unwrap();
806
807        let fields = vec![
808            ("addr", L::Address),
809            ("id", L::Struct(Box::new(ID::layout()))),
810            ("uid", L::Struct(Box::new(UID::layout()))),
811            ("flag", L::Bool),
812            ("n8", L::U8),
813            ("n16", L::U16),
814            ("n32", L::U32),
815            ("n64", L::U64),
816            ("n128", L::U128),
817            ("n256", L::U256),
818            ("ascii", L::Struct(Box::new(move_ascii_str_layout()))),
819            ("utf8", L::Struct(Box::new(move_ascii_str_layout()))),
820            ("url", L::Struct(Box::new(url_layout()))),
821        ];
822
823        let formats = [
824            "{addr}, {id}, {uid}",
825            "{flag}",
826            "{n8}, {n16}, {n32}, {n64}, {n128}, {n256}",
827            "{ascii}, {utf8}, {url}",
828            "{ascii.bytes}, {utf8.bytes}, {url.url.bytes}",
829            "{@0x5455}",
830            "{false}",
831            "{56u8}, {57u16}, {58u32}, {59u64}, {60u128}, {61u256}",
832            "{'goodbye'}",
833        ];
834
835        let store = MockStore::default();
836        let root = OwnedSlice::new(struct_("0x1::m::S", fields), bytes);
837
838        let mut output: Vec<serde_json::Value> = Vec::with_capacity(formats.len());
839        let interpreter = Interpreter::new(root, store);
840        for s in formats {
841            let format = Format::parse(Limits::default(), s).unwrap();
842            output.push(
843                format
844                    .format(&interpreter, usize::MAX, usize::MAX)
845                    .await
846                    .unwrap(),
847            );
848        }
849
850        assert_json_snapshot!(output, @r###"
851        [
852          "0x0000000000000000000000000000000000000000000000000000000000004243, 0x0000000000000000000000000000000000000000000000000000000000004445, 0x0000000000000000000000000000000000000000000000000000000000004647",
853          "true",
854          "48, 49, 50, 51, 52, 53",
855          "hello, world, https://example.com",
856          "hello, world, https://example.com",
857          "0x0000000000000000000000000000000000000000000000000000000000005455",
858          "false",
859          "56, 57, 58, 59, 60, 61",
860          "goodbye"
861        ]
862        "###);
863    }
864
865    #[tokio::test]
866    async fn test_display_fields_and_scalars() {
867        let bytes = bcs::to_bytes(&(
868            AccountAddress::from_str("0x4243").unwrap(),
869            AccountAddress::from_str("0x4445").unwrap(),
870            AccountAddress::from_str("0x4647").unwrap(),
871            true,
872            48u8,
873            49u16,
874            50u32,
875            51u64,
876            52u128,
877            U256::from(53u64),
878            "hello",
879            "world",
880            "https://example.com",
881        ))
882        .unwrap();
883
884        let fields = vec![
885            ("addr", L::Address),
886            ("id", L::Struct(Box::new(ID::layout()))),
887            ("uid", L::Struct(Box::new(UID::layout()))),
888            ("flag", L::Bool),
889            ("n8", L::U8),
890            ("n16", L::U16),
891            ("n32", L::U32),
892            ("n64", L::U64),
893            ("n128", L::U128),
894            ("n256", L::U256),
895            ("ascii", L::Struct(Box::new(move_ascii_str_layout()))),
896            ("utf8", L::Struct(Box::new(move_ascii_str_layout()))),
897            ("url", L::Struct(Box::new(url_layout()))),
898        ];
899
900        let formats = [
901            ("ser_ids", "{addr}, {id}, {uid}"),
902            ("ser_bool", "{flag}"),
903            ("ser_nums", "{n8}, {n16}, {n32}, {n64}, {n128}, {n256}"),
904            ("ser_strs", "{ascii}, {utf8}, {url}"),
905            ("ser_bytes", "{ascii.bytes}, {utf8.bytes}, {url.url.bytes}"),
906            ("lit_addr", "{@0x5455}"),
907            ("lit_bool", "{false}"),
908            (
909                "lit_nums",
910                "{56u8}, {57u16}, {58u32}, {59u64}, {60u128}, {61u256}",
911            ),
912            ("lit_str", "{'goodbye'}"),
913        ];
914
915        let output = format(
916            MockStore::default(),
917            Limits::default(),
918            bytes,
919            struct_("0x1::m::S", fields),
920            usize::MAX,
921            ONE_MB,
922            formats,
923        )
924        .await
925        .unwrap();
926
927        assert_debug_snapshot!(output, @r###"
928        {
929            "ser_ids": Ok(
930                String("0x0000000000000000000000000000000000000000000000000000000000004243, 0x0000000000000000000000000000000000000000000000000000000000004445, 0x0000000000000000000000000000000000000000000000000000000000004647"),
931            ),
932            "ser_bool": Ok(
933                String("true"),
934            ),
935            "ser_nums": Ok(
936                String("48, 49, 50, 51, 52, 53"),
937            ),
938            "ser_strs": Ok(
939                String("hello, world, https://example.com"),
940            ),
941            "ser_bytes": Ok(
942                String("hello, world, https://example.com"),
943            ),
944            "lit_addr": Ok(
945                String("0x0000000000000000000000000000000000000000000000000000000000005455"),
946            ),
947            "lit_bool": Ok(
948                String("false"),
949            ),
950            "lit_nums": Ok(
951                String("56, 57, 58, 59, 60, 61"),
952            ),
953            "lit_str": Ok(
954                String("goodbye"),
955            ),
956        }
957        "###);
958    }
959
960    #[tokio::test]
961    async fn test_display_vector_access() {
962        let bytes =
963            bcs::to_bytes(&(vec![2u64, 1u64, 0u64], vec!["first", "second", "third"])).unwrap();
964
965        let fields = vec![
966            ("ns", vector_(L::U64)),
967            ("ss", vector_(L::Struct(Box::new(move_ascii_str_layout())))),
968        ];
969
970        let formats = [
971            ("ns", "{{{ns[0u8]}, {ns[1u16]}, {ns[2u32]}}}"),
972            ("ss", "{{{ss[0u64]}, {ss[1u128]}, {ss[2u256]}}}"),
973            ("xs", "{{{ss[ns[0u64]]}, {ss[ns[1u64]]}, {ss[ns[2u64]]}}}"),
974        ];
975
976        let output = format(
977            MockStore::default(),
978            Limits::default(),
979            bytes,
980            struct_("0x1::m::S", fields),
981            usize::MAX,
982            ONE_MB,
983            formats,
984        )
985        .await
986        .unwrap();
987
988        assert_debug_snapshot!(output, @r###"
989        {
990            "ns": Ok(
991                String("{2, 1, 0}"),
992            ),
993            "ss": Ok(
994                String("{first, second, third}"),
995            ),
996            "xs": Ok(
997                String("{third, second, first}"),
998            ),
999        }
1000        "###);
1001    }
1002
1003    #[tokio::test]
1004    async fn test_display_enums() {
1005        #[derive(serde::Serialize)]
1006        enum Status<'s> {
1007            Pending(&'s str),
1008            Active(u32),
1009            Done(u128, u64),
1010        }
1011
1012        let layout = enum_(
1013            "0x1::m::Status",
1014            vec![
1015                (
1016                    "Pending",
1017                    vec![("message", L::Struct(Box::new(move_ascii_str_layout())))],
1018                ),
1019                ("Active", vec![("progress", L::U32)]),
1020                ("Done", vec![("count", L::U128), ("timestamp", L::U64)]),
1021            ],
1022        );
1023
1024        let formats = [
1025            ("pending", "message = {message}"),
1026            ("active", "progress = {progress}"),
1027            ("complete", "count = {count}, timestamp = {timestamp}"),
1028        ];
1029
1030        let mut outputs = vec![];
1031
1032        let pending = bcs::to_bytes(&Status::Pending("waiting")).unwrap();
1033        outputs.push(
1034            format(
1035                MockStore::default(),
1036                Limits::default(),
1037                pending,
1038                layout.clone(),
1039                usize::MAX,
1040                ONE_MB,
1041                formats,
1042            )
1043            .await
1044            .unwrap(),
1045        );
1046
1047        let active = bcs::to_bytes(&Status::Active(42)).unwrap();
1048        outputs.push(
1049            format(
1050                MockStore::default(),
1051                Limits::default(),
1052                active,
1053                layout.clone(),
1054                usize::MAX,
1055                ONE_MB,
1056                formats,
1057            )
1058            .await
1059            .unwrap(),
1060        );
1061
1062        let complete = bcs::to_bytes(&Status::Done(100, 999)).unwrap();
1063        outputs.push(
1064            format(
1065                MockStore::default(),
1066                Limits::default(),
1067                complete,
1068                layout,
1069                usize::MAX,
1070                ONE_MB,
1071                formats,
1072            )
1073            .await
1074            .unwrap(),
1075        );
1076
1077        assert_debug_snapshot!(outputs, @r###"
1078        [
1079            {
1080                "pending": Ok(
1081                    String("message = waiting"),
1082                ),
1083                "active": Ok(
1084                    Null,
1085                ),
1086                "complete": Ok(
1087                    Null,
1088                ),
1089            },
1090            {
1091                "pending": Ok(
1092                    Null,
1093                ),
1094                "active": Ok(
1095                    String("progress = 42"),
1096                ),
1097                "complete": Ok(
1098                    Null,
1099                ),
1100            },
1101            {
1102                "pending": Ok(
1103                    Null,
1104                ),
1105                "active": Ok(
1106                    Null,
1107                ),
1108                "complete": Ok(
1109                    String("count = 100, timestamp = 999"),
1110                ),
1111            },
1112        ]
1113        "###);
1114    }
1115
1116    #[tokio::test]
1117    async fn test_display_nested_access() {
1118        let bytes = bcs::to_bytes(&(
1119            (42u64, "nested"),
1120            vec![(1u32, "first"), (2u32, "second")],
1121            vec![Some((100u64, 200u64, 300u64))],
1122        ))
1123        .unwrap();
1124
1125        let inner = struct_(
1126            "0x1::m::Inner",
1127            vec![
1128                ("value", L::U64),
1129                ("label", L::Struct(Box::new(move_ascii_str_layout()))),
1130            ],
1131        );
1132
1133        let item = struct_(
1134            "0x1::m::Item",
1135            vec![
1136                ("id", L::U32),
1137                ("name", L::Struct(Box::new(move_ascii_str_layout()))),
1138            ],
1139        );
1140
1141        let tuple = struct_(
1142            "0x1::m::Tuple",
1143            vec![("pos0", L::U64), ("pos1", L::U64), ("pos2", L::U64)],
1144        );
1145
1146        let option = enum_(
1147            "0x1::option::Option",
1148            vec![("None", vec![]), ("Some", vec![("pos0", tuple)])],
1149        );
1150
1151        let fields = vec![
1152            ("inner", inner),
1153            ("is", vector_(item)),
1154            ("ts", vector_(option)),
1155        ];
1156
1157        let formats = [
1158            ("inner", "{inner.value}/{inner.label}"),
1159            ("items", "{is[0u64].name}, {is[1u64].id}"),
1160            ("tuples", "({ts[0u64].0.0}, {ts[0u64].0.1}, {ts[0u64].0.2})"),
1161            ("litpos", "{0x2::m::S(is[1u64]).0.name}"),
1162            ("litnamed", "{0x2::m::T { id: is[0u64].id }.id}"),
1163        ];
1164
1165        let output = format(
1166            MockStore::default(),
1167            Limits::default(),
1168            bytes,
1169            struct_("0x1::m::S", fields),
1170            usize::MAX,
1171            ONE_MB,
1172            formats,
1173        )
1174        .await
1175        .unwrap();
1176
1177        assert_debug_snapshot!(output, @r###"
1178        {
1179            "inner": Ok(
1180                String("42/nested"),
1181            ),
1182            "items": Ok(
1183                String("first, 2"),
1184            ),
1185            "tuples": Ok(
1186                String("(100, 200, 300)"),
1187            ),
1188            "litpos": Ok(
1189                String("second"),
1190            ),
1191            "litnamed": Ok(
1192                String("1"),
1193            ),
1194        }
1195        "###);
1196    }
1197
1198    #[tokio::test]
1199    async fn test_display_string_bytes() {
1200        let bytes = bcs::to_bytes("ABC").unwrap();
1201        let layout = L::Struct(Box::new(move_ascii_str_layout()));
1202
1203        let formats = vec![
1204            ("serialized", "{bytes[0u64]}"),
1205            ("string_lit", "{'ABC'.bytes[1u64]}"),
1206            ("bytes_lit", "{b'ABC'[2u64]}"),
1207        ];
1208
1209        let output = format(
1210            MockStore::default(),
1211            Limits::default(),
1212            bytes,
1213            layout,
1214            usize::MAX,
1215            ONE_MB,
1216            formats,
1217        )
1218        .await
1219        .unwrap();
1220
1221        assert_debug_snapshot!(output, @r###"
1222        {
1223            "serialized": Ok(
1224                String("65"),
1225            ),
1226            "string_lit": Ok(
1227                String("66"),
1228            ),
1229            "bytes_lit": Ok(
1230                String("67"),
1231            ),
1232        }
1233        "###);
1234    }
1235
1236    #[tokio::test]
1237    async fn test_display_missing_fields() {
1238        let bytes = bcs::to_bytes(&(42u64, vec![10u64, 20u64, 30u64])).unwrap();
1239        let fields = vec![("num", L::U64), ("nums", vector_(L::U64))];
1240
1241        let formats = [
1242            // Scalars produce empty responses on any field access
1243            ("scalar_ok", "{num}"),
1244            ("scalar_fail", "{num.field}"),
1245            // Structs produce empty responses on missing field access
1246            ("field_fail", "{missing}"),
1247            // Vectors produce empty responses on out-of-bounds access
1248            ("index_ok", "{nums[1u64]}"),
1249            ("index_fail", "{numbers[10u64]}"),
1250            // When accessing multiple fields, all of them must succeed
1251            ("combined_ok", "{num}, {nums[0u64]}"),
1252            // If any one fails, the whole field's value is null
1253            ("combined_fail", "{num}, {missing}, {nums[0u64]}"),
1254        ];
1255
1256        let output = format(
1257            MockStore::default(),
1258            Limits::default(),
1259            bytes,
1260            struct_("0x1::m::S", fields),
1261            usize::MAX,
1262            ONE_MB,
1263            formats,
1264        )
1265        .await
1266        .unwrap();
1267
1268        assert_debug_snapshot!(output, @r###"
1269        {
1270            "scalar_ok": Ok(
1271                String("42"),
1272            ),
1273            "scalar_fail": Ok(
1274                Null,
1275            ),
1276            "field_fail": Ok(
1277                Null,
1278            ),
1279            "index_ok": Ok(
1280                String("20"),
1281            ),
1282            "index_fail": Ok(
1283                Null,
1284            ),
1285            "combined_ok": Ok(
1286                String("42, 10"),
1287            ),
1288            "combined_fail": Ok(
1289                Null,
1290            ),
1291        }
1292        "###);
1293    }
1294
1295    #[tokio::test]
1296    async fn test_display_alternates() {
1297        let bytes = bcs::to_bytes(&42u64).unwrap();
1298        let layout = struct_("0x1::m::S", vec![("bar", L::U64)]);
1299
1300        let formats = [
1301            ("succeeds", "{bar | baz}"),
1302            ("eventually", "{foo | bar | baz}"),
1303            ("never", "{foo | baz | qux}"),
1304            ("fallback", "{foo | 'default'}"),
1305        ];
1306
1307        let output = format(
1308            MockStore::default(),
1309            Limits::default(),
1310            bytes,
1311            layout,
1312            usize::MAX,
1313            ONE_MB,
1314            formats,
1315        )
1316        .await
1317        .unwrap();
1318
1319        assert_debug_snapshot!(output, @r###"
1320        {
1321            "succeeds": Ok(
1322                String("42"),
1323            ),
1324            "eventually": Ok(
1325                String("42"),
1326            ),
1327            "never": Ok(
1328                Null,
1329            ),
1330            "fallback": Ok(
1331                String("default"),
1332            ),
1333        }
1334        "###);
1335    }
1336
1337    #[tokio::test]
1338    async fn test_display_alternate_optional() {
1339        let bytes = bcs::to_bytes(&(Some(100u64), None::<u64>)).unwrap();
1340        let layout = struct_(
1341            "0x1::m::S",
1342            vec![("a", optional_(L::U64)), ("b", optional_(L::U64))],
1343        );
1344
1345        let formats = [("some", "{a | 42u64}"), ("none", "{b | 43u64}")];
1346
1347        let output = format(
1348            MockStore::default(),
1349            Limits::default(),
1350            bytes,
1351            layout,
1352            usize::MAX,
1353            ONE_MB,
1354            formats,
1355        )
1356        .await
1357        .unwrap();
1358
1359        assert_debug_snapshot!(output, @r###"
1360        {
1361            "some": Ok(
1362                String("100"),
1363            ),
1364            "none": Ok(
1365                String("43"),
1366            ),
1367        }
1368        "###);
1369    }
1370
1371    #[tokio::test]
1372    async fn test_display_optional_auto_dereference() {
1373        let inner = struct_(
1374            "0x1::m::Inner",
1375            vec![("data", L::U64), ("optional_data", optional_(L::U64))],
1376        );
1377
1378        let layout = struct_(
1379            "0x1::m::Test",
1380            vec![
1381                ("some_inner", optional_(inner.clone())),
1382                ("none_inner", optional_(inner.clone())),
1383                ("partial_inner", optional_(inner)),
1384                ("some_value", optional_(L::U64)),
1385                ("none_value", optional_(L::U64)),
1386            ],
1387        );
1388
1389        let bytes = bcs::to_bytes(&(
1390            Some((100u64, Some(200u64))), // some_inner
1391            None::<(u64, Option<u64>)>,   // none_inner
1392            Some((300u64, None::<u64>)),  // partial_inner
1393            Some(42u64),                  // some_value
1394            None::<u64>,                  // none_value
1395        ))
1396        .unwrap();
1397
1398        let formats = [
1399            // Accessing through Some option to nested field
1400            ("some_inner_data", "{some_inner.data}"),
1401            ("some_inner_optional", "{some_inner.optional_data}"),
1402            // Accessing through None option should return null
1403            ("none_inner_data", "{none_inner.data}"),
1404            ("none_inner_optional", "{none_inner.optional_data}"),
1405            // Accessing through Some option to None nested optional
1406            ("partial_inner_data", "{partial_inner.data}"),
1407            ("partial_inner_optional", "{partial_inner.optional_data}"),
1408            // Direct optional access
1409            ("some_value", "{some_value}"),
1410            ("none_value", "{none_value}"),
1411        ];
1412
1413        let output = format(
1414            MockStore::default(),
1415            Limits::default(),
1416            bytes,
1417            layout,
1418            usize::MAX,
1419            ONE_MB,
1420            formats,
1421        )
1422        .await
1423        .unwrap();
1424
1425        assert_debug_snapshot!(output, @r###"
1426        {
1427            "some_inner_data": Ok(
1428                String("100"),
1429            ),
1430            "some_inner_optional": Ok(
1431                String("200"),
1432            ),
1433            "none_inner_data": Ok(
1434                Null,
1435            ),
1436            "none_inner_optional": Ok(
1437                Null,
1438            ),
1439            "partial_inner_data": Ok(
1440                String("300"),
1441            ),
1442            "partial_inner_optional": Ok(
1443                Null,
1444            ),
1445            "some_value": Ok(
1446                String("42"),
1447            ),
1448            "none_value": Ok(
1449                Null,
1450            ),
1451        }
1452        "###);
1453    }
1454
1455    #[tokio::test]
1456    async fn test_display_dynamic_fields() {
1457        let parent = AccountAddress::from_str("0x1000").unwrap();
1458        let bytes = bcs::to_bytes(&parent).unwrap();
1459        let layout = struct_(
1460            "0x1::m::Root",
1461            vec![(
1462                "parent",
1463                struct_(
1464                    "0x1::m::Parent",
1465                    vec![("id", L::Struct(Box::new(UID::layout())))],
1466                ),
1467            )],
1468        );
1469
1470        // Add a dynamic field to the store: parent.df["key"] = 42u64
1471        let store = MockStore::default().with_dynamic_field(
1472            parent,
1473            "key",
1474            L::Struct(Box::new(move_utf8_str_layout())),
1475            (42u64, 43u64),
1476            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1477        );
1478
1479        let formats = [
1480            ("via_obj", "{parent->['key'].x}"),
1481            ("via_uid", "{parent.id->['key'].y}"),
1482            ("via_id", "{parent.id.id->['key'].x}"),
1483            ("via_addr", "{parent.id.id.bytes->['key'].y}"),
1484            ("via_lit", "{@0x1000->['key'].x}"),
1485            ("missing", "{parent.id->['missing']}"),
1486        ];
1487
1488        let output = format(
1489            store,
1490            Limits::default(),
1491            bytes,
1492            layout,
1493            usize::MAX,
1494            ONE_MB,
1495            formats,
1496        )
1497        .await
1498        .unwrap();
1499
1500        assert_debug_snapshot!(output, @r###"
1501        {
1502            "via_obj": Ok(
1503                String("42"),
1504            ),
1505            "via_uid": Ok(
1506                String("43"),
1507            ),
1508            "via_id": Ok(
1509                String("42"),
1510            ),
1511            "via_addr": Ok(
1512                String("43"),
1513            ),
1514            "via_lit": Ok(
1515                String("42"),
1516            ),
1517            "missing": Ok(
1518                Null,
1519            ),
1520        }
1521        "###);
1522    }
1523
1524    #[tokio::test]
1525    async fn test_display_dynamic_field_lookup_with_self_key() {
1526        let registry = AccountAddress::from_str("0x1100").unwrap();
1527        let bytes = bcs::to_bytes(&(registry, 7u64)).unwrap();
1528        let layout = struct_(
1529            "0x1::m::Root",
1530            vec![("registry", L::Address), ("nonce", L::U64)],
1531        );
1532
1533        let store = MockStore::default().with_dynamic_field(
1534            registry,
1535            (registry, 7u64),
1536            layout.clone(),
1537            (123u64, 456u64),
1538            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1539        );
1540
1541        let formats = [
1542            ("hit", "{registry->[$self].x}"),
1543            ("miss", "{registry->[$self].z}"),
1544        ];
1545
1546        let output = format(
1547            store,
1548            Limits::default(),
1549            bytes,
1550            layout,
1551            usize::MAX,
1552            ONE_MB,
1553            formats,
1554        )
1555        .await
1556        .unwrap();
1557
1558        assert_debug_snapshot!(output, @r###"
1559        {
1560            "hit": Ok(
1561                String("123"),
1562            ),
1563            "miss": Ok(
1564                Null,
1565            ),
1566        }
1567        "###);
1568    }
1569
1570    #[tokio::test]
1571    async fn test_display_concurrent_dynamic_field_fetch() {
1572        // Define a store that intentionally holds back requests to the store so they operate
1573        // concurrently.
1574        #[derive(Clone)]
1575        struct BlockingStore {
1576            barrier: Arc<Barrier>,
1577            inner: MockStore,
1578        }
1579
1580        #[async_trait]
1581        impl Store for BlockingStore {
1582            async fn latest(
1583                &self,
1584                id: AccountAddress,
1585            ) -> anyhow::Result<Option<(MoveTypeLayout, Vec<u8>)>> {
1586                self.barrier.wait().await;
1587                self.inner.latest(id).await
1588            }
1589        }
1590
1591        let parent = AccountAddress::from_str("0x1200").unwrap();
1592        let bytes = bcs::to_bytes(&parent).unwrap();
1593        let layout = struct_(
1594            "0x1::m::Root",
1595            vec![("id", L::Struct(Box::new(UID::layout())))],
1596        );
1597
1598        let store = BlockingStore {
1599            barrier: Arc::new(Barrier::new(2)),
1600            inner: MockStore::default().with_dynamic_field(
1601                parent,
1602                "key",
1603                L::Struct(Box::new(move_utf8_str_layout())),
1604                42u64,
1605                L::U64,
1606            ),
1607        };
1608
1609        let rendered = tokio::time::timeout(
1610            Duration::from_secs(10),
1611            format(
1612                store,
1613                Limits::default(),
1614                bytes,
1615                layout,
1616                usize::MAX,
1617                ONE_MB,
1618                [("concurrent", "{id->['key']}{id->['key']}")],
1619            ),
1620        )
1621        .await
1622        .expect("back-to-back dynamic field expressions should not block")
1623        .unwrap();
1624
1625        assert_debug_snapshot!(rendered, @r###"
1626        {
1627            "concurrent": Ok(
1628                String("4242"),
1629            ),
1630        }
1631        "###);
1632    }
1633
1634    #[tokio::test]
1635    async fn test_display_dynamic_object_fields() {
1636        let parent = AccountAddress::from_str("0x2000").unwrap();
1637        let child = AccountAddress::from_str("0x2001").unwrap();
1638        let bytes = bcs::to_bytes(&parent).unwrap();
1639        let layout = struct_(
1640            "0x1::m::Root",
1641            vec![(
1642                "parent",
1643                struct_(
1644                    "0x1::m::Parent",
1645                    vec![("id", L::Struct(Box::new(UID::layout())))],
1646                ),
1647            )],
1648        );
1649
1650        let store = MockStore::default().with_dynamic_object_field(
1651            parent,
1652            "key",
1653            L::Struct(Box::new(move_utf8_str_layout())),
1654            (child, 100u64, 200u64),
1655            struct_(
1656                "0x1::m::Child",
1657                vec![
1658                    ("id", L::Struct(Box::new(UID::layout()))),
1659                    ("x", L::U64),
1660                    ("y", L::U64),
1661                ],
1662            ),
1663        );
1664
1665        let formats = [
1666            ("via_obj", "{parent=>['key'].x}"),
1667            ("via_uid", "{parent.id=>['key'].y}"),
1668            ("via_id", "{parent.id.id=>['key'].x}"),
1669            ("via_addr", "{parent.id.id=>['key'].y}"),
1670            ("via_lit", "{@0x2000=>['key'].x}"),
1671            ("missing", "{parent.id=>['missing']}"),
1672        ];
1673
1674        let limits = Limits {
1675            max_loads: 20, // Each DOF access counts as 2 loads
1676            ..Limits::default()
1677        };
1678
1679        let output = format(store, limits, bytes, layout, usize::MAX, ONE_MB, formats)
1680            .await
1681            .unwrap();
1682
1683        assert_debug_snapshot!(output, @r###"
1684        {
1685            "via_obj": Ok(
1686                String("100"),
1687            ),
1688            "via_uid": Ok(
1689                String("200"),
1690            ),
1691            "via_id": Ok(
1692                String("100"),
1693            ),
1694            "via_addr": Ok(
1695                String("200"),
1696            ),
1697            "via_lit": Ok(
1698                String("100"),
1699            ),
1700            "missing": Ok(
1701                Null,
1702            ),
1703        }
1704        "###);
1705    }
1706
1707    #[tokio::test]
1708    async fn test_display_nested_dynamic_fields() {
1709        let parent = AccountAddress::from_str("0x3000").unwrap();
1710        let child = AccountAddress::from_str("0x3001").unwrap();
1711        let bytes = bcs::to_bytes(&parent).unwrap();
1712        let layout = struct_(
1713            "0x1::m::Root",
1714            vec![(
1715                "parent",
1716                struct_(
1717                    "0x1::m::Parent",
1718                    vec![("id", L::Struct(Box::new(UID::layout())))],
1719                ),
1720            )],
1721        );
1722
1723        let store = MockStore::default()
1724            .with_dynamic_object_field(
1725                parent,
1726                "L1",
1727                L::Struct(Box::new(move_utf8_str_layout())),
1728                (child, 100u64),
1729                struct_(
1730                    "0x1::m::Child",
1731                    vec![("id", L::Struct(Box::new(UID::layout()))), ("data", L::U64)],
1732                ),
1733            )
1734            .with_dynamic_field(
1735                child,
1736                "L2",
1737                L::Struct(Box::new(move_utf8_str_layout())),
1738                (10u64, 20u64),
1739                struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1740            );
1741
1742        let formats = [
1743            ("1_data", "{parent=>['L1'].data}"),
1744            ("1_2_x", "{parent=>['L1']->['L2'].x}"),
1745            ("1_2_y", "{parent=>['L1']->['L2'].y}"),
1746        ];
1747
1748        let limits = Limits {
1749            max_loads: 20,
1750            ..Limits::default()
1751        };
1752
1753        let output = format(store, limits, bytes, layout, usize::MAX, ONE_MB, formats)
1754            .await
1755            .unwrap();
1756
1757        assert_debug_snapshot!(output, @r###"
1758        {
1759            "1_data": Ok(
1760                String("100"),
1761            ),
1762            "1_2_x": Ok(
1763                String("10"),
1764            ),
1765            "1_2_y": Ok(
1766                String("20"),
1767            ),
1768        }
1769        "###);
1770    }
1771
1772    #[tokio::test]
1773    async fn test_extract_root_value_remains_scoped() {
1774        let parent = AccountAddress::from_str("0x4000").unwrap();
1775        let bytes = bcs::to_bytes(&parent).unwrap();
1776        let layout = struct_(
1777            "0x1::m::Root",
1778            vec![(
1779                "parent",
1780                struct_(
1781                    "0x1::m::Parent",
1782                    vec![("id", L::Struct(Box::new(UID::layout())))],
1783                ),
1784            )],
1785        );
1786
1787        let store = MockStore::default().with_dynamic_field(
1788            parent,
1789            "key",
1790            L::Struct(Box::new(move_utf8_str_layout())),
1791            (20u64, 21u64),
1792            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1793        );
1794
1795        let slice = extract_owned(store, bytes, layout, "parent.id->['key']")
1796            .await
1797            .unwrap()
1798            .unwrap();
1799
1800        assert!(slice.scoped);
1801        assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1802    }
1803
1804    #[tokio::test]
1805    async fn test_extract_literal_lookup_is_latest() {
1806        let parent = AccountAddress::from_str("0x4100").unwrap();
1807        let bytes = bcs::to_bytes(&false).unwrap();
1808        let layout = L::Bool;
1809
1810        let store = MockStore::default().with_dynamic_field(
1811            parent,
1812            "key",
1813            L::Struct(Box::new(move_utf8_str_layout())),
1814            (20u64, 21u64),
1815            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1816        );
1817
1818        let slice = extract_owned(store, bytes, layout, "@0x4100->['key']")
1819            .await
1820            .unwrap()
1821            .unwrap();
1822
1823        assert!(!slice.scoped);
1824        assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1825    }
1826
1827    #[tokio::test]
1828    async fn test_extract_root_value_via_literal_field_remains_scoped() {
1829        let parent = AccountAddress::from_str("0x4200").unwrap();
1830        let bytes = bcs::to_bytes(&parent).unwrap();
1831        let layout = struct_(
1832            "0x1::m::Root",
1833            vec![(
1834                "parent",
1835                struct_(
1836                    "0x1::m::Parent",
1837                    vec![("id", L::Struct(Box::new(UID::layout())))],
1838                ),
1839            )],
1840        );
1841
1842        let store = MockStore::default().with_dynamic_field(
1843            parent,
1844            "key",
1845            L::Struct(Box::new(move_utf8_str_layout())),
1846            (20u64, 21u64),
1847            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1848        );
1849
1850        let slice = extract_owned(
1851            store,
1852            bytes,
1853            layout,
1854            "0x1::m::Wrapper{p: parent.id.id}.p->['key']",
1855        )
1856        .await
1857        .unwrap()
1858        .unwrap();
1859
1860        assert!(slice.scoped);
1861        assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1862    }
1863
1864    #[tokio::test]
1865    async fn test_extract_derived_object_with_literal_parent_is_latest() {
1866        let literal_parent = AccountAddress::from_str("0x4300").unwrap();
1867        let bytes = bcs::to_bytes(&("derived_key",)).unwrap();
1868        let layout = struct_(
1869            "0x1::m::Root",
1870            vec![("key", L::Struct(Box::new(move_utf8_str_layout())))],
1871        );
1872
1873        let store = MockStore::default().with_derived_object(
1874            literal_parent,
1875            "derived_key",
1876            L::Struct(Box::new(move_utf8_str_layout())),
1877            (20u64, 21u64),
1878            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1879        );
1880
1881        let slice = extract_owned(store, bytes, layout, "@0x4300~>[key]")
1882            .await
1883            .unwrap()
1884            .unwrap();
1885
1886        assert!(!slice.scoped);
1887        assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1888    }
1889
1890    #[tokio::test]
1891    async fn test_extract_derived_object_with_root_parent_remains_scoped() {
1892        let parent = AccountAddress::from_str("0x4400").unwrap();
1893        let bytes = bcs::to_bytes(&parent).unwrap();
1894        let layout = struct_(
1895            "0x1::m::Root",
1896            vec![(
1897                "parent",
1898                struct_(
1899                    "0x1::m::Parent",
1900                    vec![("id", L::Struct(Box::new(UID::layout())))],
1901                ),
1902            )],
1903        );
1904
1905        let store = MockStore::default().with_derived_object(
1906            parent,
1907            "derived_key",
1908            L::Struct(Box::new(move_utf8_str_layout())),
1909            (20u64, 21u64),
1910            struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1911        );
1912
1913        let slice = extract_owned(store, bytes, layout, "parent~>['derived_key']")
1914            .await
1915            .unwrap()
1916            .unwrap();
1917
1918        assert!(slice.scoped);
1919        assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1920    }
1921
1922    #[tokio::test]
1923    async fn test_display_vec_map() {
1924        let key = struct_(
1925            "0x42::m::Key",
1926            vec![
1927                ("id", L::U64),
1928                ("name", L::Struct(Box::new(move_ascii_str_layout()))),
1929            ],
1930        );
1931
1932        let val = struct_("0x42::m::Value", vec![("data", L::U32)]);
1933
1934        // Create test data: VecMap with 3 entries
1935        let bytes = bcs::to_bytes(&vec![
1936            (1u64, "first", 100u32),
1937            (2u64, "second", 200u32),
1938            (3u64, "third", 300u32),
1939        ])
1940        .unwrap();
1941
1942        let layout = struct_("0x1::m::Root", vec![("map", vec_map(key, val))]);
1943
1944        let formats = [
1945            ("1st", "{map[0x42::m::Key(1u64, 'first')].data}"),
1946            ("2nd", "{map[0x42::m::Key(2u64, 'second')].data}"),
1947            ("3rd", "{map[0x42::m::Key(3u64, 'third')].data}"),
1948            // Doesn't exist
1949            ("4th", "{map[0x42::m::Key(4u64, 'fourth')].data}"),
1950            // Indexing a struct that is not a VecMap
1951            ("err", "{map[0x42::m::Key(1u64, 'first')].data['empty']}"),
1952        ];
1953
1954        let output = format(
1955            MockStore::default(),
1956            Limits::default(),
1957            bytes,
1958            layout,
1959            usize::MAX,
1960            ONE_MB,
1961            formats,
1962        )
1963        .await
1964        .unwrap();
1965
1966        assert_debug_snapshot!(output, @r###"
1967        {
1968            "1st": Ok(
1969                String("100"),
1970            ),
1971            "2nd": Ok(
1972                String("200"),
1973            ),
1974            "3rd": Ok(
1975                String("300"),
1976            ),
1977            "4th": Ok(
1978                Null,
1979            ),
1980            "err": Ok(
1981                Null,
1982            ),
1983        }
1984        "###);
1985    }
1986
1987    #[tokio::test]
1988    async fn test_display_timestamp() {
1989        let bytes = bcs::to_bytes(&1681318800000u64).unwrap();
1990        let layout = struct_("0x1::m::S", vec![("timestamp", L::U64)]);
1991
1992        let formats = [
1993            ("epoch", "{0u64:ts}"),
1994            ("field", "{timestamp:ts}"),
1995            ("lit64", "{1683730800000u64:ts}"),
1996            ("lit128", "{1681318800000u128:ts}"),
1997            ("toobig", "{1681318800000000000u128:ts}"),
1998        ];
1999
2000        let output = format(
2001            MockStore::default(),
2002            Limits::default(),
2003            bytes,
2004            layout,
2005            usize::MAX,
2006            ONE_MB,
2007            formats,
2008        )
2009        .await
2010        .unwrap();
2011
2012        assert_debug_snapshot!(output, @r###"
2013        {
2014            "epoch": Ok(
2015                String("1970-01-01T00:00:00Z"),
2016            ),
2017            "field": Ok(
2018                String("2023-04-12T17:00:00Z"),
2019            ),
2020            "lit64": Ok(
2021                String("2023-05-10T15:00:00Z"),
2022            ),
2023            "lit128": Ok(
2024                String("2023-04-12T17:00:00Z"),
2025            ),
2026            "toobig": Err(
2027                TransformInvalid_ {
2028                    offset: 0,
2029                    reason: "expected unix timestamp in milliseconds",
2030                },
2031            ),
2032        }
2033        "###);
2034    }
2035
2036    #[tokio::test]
2037    async fn test_display_hex() {
2038        let bytes = bcs::to_bytes(&(
2039            0x42u8,
2040            0x4243u16,
2041            0x42434445u32,
2042            0x4243444546474849u64,
2043            0x42434445464748494a4b4c4d4e4f5051u128,
2044            U256::from_str_radix(
2045                "42434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061",
2046                16,
2047            )
2048            .unwrap(),
2049            AccountAddress::from_str(
2050                "0x41403f3e3d3c3b3a393837363534333231300f0e0d0c0b0a0908070605040302",
2051            )
2052            .unwrap(),
2053            vec![0x41u8, 0x40, 0x3a],
2054            "ABC",
2055        ))
2056        .unwrap();
2057
2058        let layout = struct_(
2059            "0x1::m::S",
2060            vec![
2061                ("n8", L::U8),
2062                ("n16", L::U16),
2063                ("n32", L::U32),
2064                ("n64", L::U64),
2065                ("n128", L::U128),
2066                ("n256", L::U256),
2067                ("addr", L::Address),
2068                ("bytes", vector_(L::U8)),
2069                ("str", L::Struct(Box::new(move_ascii_str_layout()))),
2070            ],
2071        );
2072
2073        let formats = [
2074            ("n8", "{n8:hex}"),
2075            ("n16", "{n16:hex}"),
2076            ("n32", "{n32:hex}"),
2077            ("n64", "{n64:hex}"),
2078            ("n128", "{n128:hex}"),
2079            ("n256", "{n256:hex}"),
2080            ("addr", "{addr:hex}"),
2081            ("bytes", "{bytes:hex}"),
2082            ("str", "{str:hex}"),
2083            ("str_bytes", "{str.bytes:hex}"),
2084        ];
2085
2086        let output = format(
2087            MockStore::default(),
2088            Limits::default(),
2089            bytes,
2090            layout,
2091            usize::MAX,
2092            ONE_MB,
2093            formats,
2094        )
2095        .await
2096        .unwrap();
2097
2098        assert_debug_snapshot!(output, @r###"
2099        {
2100            "n8": Ok(
2101                String("42"),
2102            ),
2103            "n16": Ok(
2104                String("4243"),
2105            ),
2106            "n32": Ok(
2107                String("42434445"),
2108            ),
2109            "n64": Ok(
2110                String("4243444546474849"),
2111            ),
2112            "n128": Ok(
2113                String("42434445464748494a4b4c4d4e4f5051"),
2114            ),
2115            "n256": Ok(
2116                String("42434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061"),
2117            ),
2118            "addr": Ok(
2119                String("41403f3e3d3c3b3a393837363534333231300f0e0d0c0b0a0908070605040302"),
2120            ),
2121            "bytes": Ok(
2122                String("41403a"),
2123            ),
2124            "str": Ok(
2125                String("414243"),
2126            ),
2127            "str_bytes": Ok(
2128                String("414243"),
2129            ),
2130        }
2131        "###);
2132    }
2133
2134    #[tokio::test]
2135    async fn test_display_url() {
2136        let bytes = bcs::to_bytes(&(
2137            1234u32,
2138            "hello/goodbye world",
2139            "🔥",
2140            vec![0x3eu8, 0x3f, 0x40, 0x41, 0x42, 0x43],
2141        ))
2142        .unwrap();
2143
2144        let layout = struct_(
2145            "0x1::m::S",
2146            vec![
2147                ("num", L::U32),
2148                ("str", L::Struct(Box::new(move_ascii_str_layout()))),
2149                ("emoji", L::Struct(Box::new(move_utf8_str_layout()))),
2150                ("bytes", L::Struct(Box::new(url_layout()))),
2151            ],
2152        );
2153
2154        let formats = [(
2155            "url",
2156            "https://example.com/?num={num:url}&str={str:url}&emoji={emoji:url}&data={bytes:url}",
2157        )];
2158
2159        let output = format(
2160            MockStore::default(),
2161            Limits::default(),
2162            bytes,
2163            layout,
2164            usize::MAX,
2165            ONE_MB,
2166            formats,
2167        )
2168        .await
2169        .unwrap();
2170
2171        assert_debug_snapshot!(output, @r###"
2172        {
2173            "url": Ok(
2174                String("https://example.com/?num=1234&str=hello%2Fgoodbye%20world&emoji=%F0%9F%94%A5&data=%3E%3F%40ABC"),
2175            ),
2176        }
2177        "###);
2178    }
2179
2180    #[tokio::test]
2181    async fn test_display_base64() {
2182        let bytes = bcs::to_bytes(&00u8).unwrap();
2183        let layout = struct_("0x1::m::S", vec![("dummy_field", L::Bool)]);
2184
2185        let formats = [
2186            ("byte", "{0u8:base64}"),
2187            ("byte_nopad", "{0u8:base64(nopad)}"),
2188            ("byte_url", "{0u8:base64(url)}"),
2189            ("byte_url_nopad", "{0u8:base64(url, nopad)}"),
2190            ("long", "{0xf8fbu64:base64}"),
2191            ("long_nopad", "{0xf8fbu64:base64(nopad)}"),
2192            ("long_url", "{0xf8fbu64:base64(url)}"),
2193            ("long_url_nopad", "{0xf8fbu64:base64(nopad, url)}"),
2194            ("str", "{'hello':base64}"),
2195            ("str_nopad", "{'hello':base64(nopad)}"),
2196            ("str_url", "{'hello':base64(url)}"),
2197            ("str_url_nopad", "{'hello':base64(url, nopad)}"),
2198            (
2199                "flatland",
2200                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url)}",
2201            ),
2202            (
2203                "flatland_nopad",
2204                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(nopad)}",
2205            ),
2206            (
2207                "flatland_url",
2208                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url)}",
2209            ),
2210            (
2211                "flatland_url_nopad",
2212                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url, nopad)}",
2213            ),
2214        ];
2215
2216        let output = format(
2217            MockStore::default(),
2218            Limits::default(),
2219            bytes,
2220            layout,
2221            usize::MAX,
2222            ONE_MB,
2223            formats,
2224        )
2225        .await
2226        .unwrap();
2227
2228        assert_debug_snapshot!(output, @r###"
2229        {
2230            "byte": Ok(
2231                String("AA=="),
2232            ),
2233            "byte_nopad": Ok(
2234                String("AA"),
2235            ),
2236            "byte_url": Ok(
2237                String("AA=="),
2238            ),
2239            "byte_url_nopad": Ok(
2240                String("AA"),
2241            ),
2242            "long": Ok(
2243                String("+/gAAAAAAAA="),
2244            ),
2245            "long_nopad": Ok(
2246                String("+/gAAAAAAAA"),
2247            ),
2248            "long_url": Ok(
2249                String("-_gAAAAAAAA="),
2250            ),
2251            "long_url_nopad": Ok(
2252                String("-_gAAAAAAAA"),
2253            ),
2254            "str": Ok(
2255                String("aGVsbG8="),
2256            ),
2257            "str_nopad": Ok(
2258                String("aGVsbG8"),
2259            ),
2260            "str_url": Ok(
2261                String("aGVsbG8="),
2262            ),
2263            "str_url_nopad": Ok(
2264                String("aGVsbG8"),
2265            ),
2266            "flatland": Ok(
2267                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2268            ),
2269            "flatland_nopad": Ok(
2270                String("0tGFaqPKhfWCrycZHVcT6lgF7C+YIrMMzORXFwcsGmE"),
2271            ),
2272            "flatland_url": Ok(
2273                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2274            ),
2275            "flatland_url_nopad": Ok(
2276                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE"),
2277            ),
2278        }
2279        "###);
2280    }
2281
2282    #[tokio::test]
2283    async fn test_display_bcs() {
2284        let bytes = bcs::to_bytes(&(
2285            0x42u8,
2286            0x1234u16,
2287            0x12345678u32,
2288            0x123456789abcdef0u64,
2289            "hello",
2290            vec![1u8, 2, 3],
2291        ))
2292        .unwrap();
2293
2294        let layout = struct_(
2295            "0x1::m::S",
2296            vec![
2297                ("n8", L::U8),
2298                ("n16", L::U16),
2299                ("n32", L::U32),
2300                ("n64", L::U64),
2301                ("str", L::Struct(Box::new(move_utf8_str_layout()))),
2302                ("bytes", vector_(L::U8)),
2303            ],
2304        );
2305
2306        let formats = [
2307            ("s8", "{n8:bcs}"),
2308            ("l8", "{0x43u8:bcs}"),
2309            ("s16", "{n16:bcs}"),
2310            ("l16", "{0x1235u16:bcs}"),
2311            ("s32", "{n32:bcs}"),
2312            ("l32", "{0x12345679u32:bcs}"),
2313            ("s64", "{n64:bcs}"),
2314            ("l64", "{0x123456789abcdef1u64:bcs}"),
2315            ("sstr", "{str:bcs}"),
2316            ("lstr", "{'goodbye':bcs}"),
2317            ("sbytes", "{bytes:bcs}"),
2318            ("lbytes", "{x'010204':bcs}"),
2319            ("hbytes", "{vector[0x41u8, n8, 0x43u8]:bcs}"),
2320            ("lstruct", "{0x1::m::S(n8, n16):bcs}"),
2321            ("lempty", "{0x1::m::Empty():bcs}"),
2322            ("lnone", "{0x1::option::Option<u8>::None#0():bcs}"),
2323            ("lsome", "{0x1::option::Option<u8>::Some#1(0x44u8):bcs}"),
2324        ];
2325
2326        let output = format(
2327            MockStore::default(),
2328            Limits::default(),
2329            bytes,
2330            layout,
2331            usize::MAX,
2332            ONE_MB,
2333            formats,
2334        )
2335        .await
2336        .unwrap();
2337
2338        let actual = |f: &str| output.get(f).unwrap().as_ref().unwrap().as_str().unwrap();
2339        fn expect(x: impl Serialize) -> String {
2340            STANDARD.encode(bcs::to_bytes(&x).unwrap())
2341        }
2342
2343        assert_eq!(actual("s8"), expect(0x42u8));
2344        assert_eq!(actual("l8"), expect(0x43u8));
2345        assert_eq!(actual("s16"), expect(0x1234u16));
2346        assert_eq!(actual("l16"), expect(0x1235u16));
2347        assert_eq!(actual("s32"), expect(0x12345678u32));
2348        assert_eq!(actual("l32"), expect(0x12345679u32));
2349        assert_eq!(actual("s64"), expect(0x123456789abcdef0u64));
2350        assert_eq!(actual("l64"), expect(0x123456789abcdef1u64));
2351        assert_eq!(actual("sstr"), expect("hello"));
2352        assert_eq!(actual("lstr"), expect("goodbye"));
2353        assert_eq!(actual("sbytes"), expect(vec![1u8, 2, 3]));
2354        assert_eq!(actual("lbytes"), expect(vec![1u8, 2, 4]));
2355        assert_eq!(actual("hbytes"), expect(vec![0x41u8, 0x42, 0x43]));
2356        assert_eq!(actual("lstruct"), expect((0x42u8, 0x1234u16)));
2357        assert_eq!(actual("lempty"), expect(false));
2358        assert_eq!(actual("lnone"), expect(None::<u8>));
2359        assert_eq!(actual("lsome"), expect(Some(0x44u8)));
2360    }
2361
2362    #[tokio::test]
2363    async fn test_display_bcs_modifiers() {
2364        let bytes = bcs::to_bytes(&00u8).unwrap();
2365        let layout = struct_("0x1::m::S", vec![("dummy_field", L::Bool)]);
2366
2367        let formats = [
2368            ("byte", "{0u8:bcs}"),
2369            ("byte_nopad", "{0u8:bcs(nopad)}"),
2370            ("byte_url", "{0u8:bcs(url)}"),
2371            ("byte_url_nopad", "{0u8:bcs(url, nopad)}"),
2372            ("long", "{0xf8fbu64:bcs}"),
2373            ("long_nopad", "{0xf8fbu64:bcs(nopad)}"),
2374            ("long_url", "{0xf8fbu64:bcs(url)}"),
2375            ("long_url_nopad", "{0xf8fbu64:bcs(nopad, url)}"),
2376            ("str", "{'hello':bcs}"),
2377            ("str_nopad", "{'hello':bcs(nopad)}"),
2378            ("str_url", "{'hello':bcs(url)}"),
2379            ("str_url_nopad", "{'hello':bcs(url, nopad)}"),
2380            (
2381                "flatland",
2382                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url)}",
2383            ),
2384            (
2385                "flatland_nopad",
2386                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(nopad)}",
2387            ),
2388            (
2389                "flatland_url",
2390                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url)}",
2391            ),
2392            (
2393                "flatland_url_nopad",
2394                "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url, nopad)}",
2395            ),
2396        ];
2397
2398        let output = format(
2399            MockStore::default(),
2400            Limits::default(),
2401            bytes,
2402            layout,
2403            usize::MAX,
2404            ONE_MB,
2405            formats,
2406        )
2407        .await
2408        .unwrap();
2409
2410        assert_debug_snapshot!(output, @r###"
2411        {
2412            "byte": Ok(
2413                String("AA=="),
2414            ),
2415            "byte_nopad": Ok(
2416                String("AA"),
2417            ),
2418            "byte_url": Ok(
2419                String("AA=="),
2420            ),
2421            "byte_url_nopad": Ok(
2422                String("AA"),
2423            ),
2424            "long": Ok(
2425                String("+/gAAAAAAAA="),
2426            ),
2427            "long_nopad": Ok(
2428                String("+/gAAAAAAAA"),
2429            ),
2430            "long_url": Ok(
2431                String("-_gAAAAAAAA="),
2432            ),
2433            "long_url_nopad": Ok(
2434                String("-_gAAAAAAAA"),
2435            ),
2436            "str": Ok(
2437                String("BWhlbGxv"),
2438            ),
2439            "str_nopad": Ok(
2440                String("BWhlbGxv"),
2441            ),
2442            "str_url": Ok(
2443                String("BWhlbGxv"),
2444            ),
2445            "str_url_nopad": Ok(
2446                String("BWhlbGxv"),
2447            ),
2448            "flatland": Ok(
2449                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2450            ),
2451            "flatland_nopad": Ok(
2452                String("0tGFaqPKhfWCrycZHVcT6lgF7C+YIrMMzORXFwcsGmE"),
2453            ),
2454            "flatland_url": Ok(
2455                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2456            ),
2457            "flatland_url_nopad": Ok(
2458                String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE"),
2459            ),
2460        }
2461        "###);
2462    }
2463
2464    #[tokio::test]
2465    async fn test_display_json() {
2466        let bytes = bcs::to_bytes(&(
2467            12u8,
2468            1234u16,
2469            12345678u32,
2470            123456781234567890u64,
2471            "hello",
2472            vec![1u8, 2, 3],
2473            None::<u8>,
2474            Some(vec![4u32, 5u32, 6u32]),
2475            (1u8, 5678u16),
2476            (9u32, 10u64),
2477        ))
2478        .unwrap();
2479
2480        let layout = struct_(
2481            "0x1::m::S",
2482            vec![
2483                ("n8", L::U8),
2484                ("n16", L::U16),
2485                ("n32", L::U32),
2486                ("n64", L::U64),
2487                ("str", L::Struct(Box::new(move_utf8_str_layout()))),
2488                ("bytes", vector_(L::U8)),
2489                (
2490                    "none",
2491                    struct_("0x1::option::Option<u8>", vec![("vec", vector_(L::U8))]),
2492                ),
2493                (
2494                    "some",
2495                    struct_(
2496                        "0x1::option::Option<vector<u32>>",
2497                        vec![("vec", vector_(vector_(L::U32)))],
2498                    ),
2499                ),
2500                (
2501                    "variant",
2502                    enum_(
2503                        "0x1::m::E",
2504                        vec![("A", vec![("x", L::U8)]), ("B", vec![("y", L::U16)])],
2505                    ),
2506                ),
2507                (
2508                    "nested",
2509                    struct_("0x1::m::N", vec![("a", L::U32), ("b", L::U64)]),
2510                ),
2511            ],
2512        );
2513
2514        let formats = [
2515            ("s8", "{n8:json}"),
2516            ("l8", "{34u8:json}"),
2517            ("s16", "{n16:json}"),
2518            ("l16", "{5678u16:json}"),
2519            ("s32", "{n32:json}"),
2520            ("l32", "{87654321u32:json}"),
2521            ("s64", "{n64:json}"),
2522            ("l64", "{9876543210987654321u64:json}"),
2523            ("sstr", "{str:json}"),
2524            ("lstr", "{'goodbye':json}"),
2525            ("sbytes", "{bytes:json}"),
2526            ("lbytes", "{x'040506':json}"),
2527            ("vbytes", "{vector[0x01u8, 0x02u8, 0x03u8]:json}"),
2528            ("snone", "{none:json}"),
2529            ("ssome", "{some:json}"),
2530            ("lvec", "{vector[7u64, 8u64, 9u64]:json}"),
2531            ("svariant", "{variant:json}"),
2532            ("lvariant", "{0x1::m::E::A#0(90u8):json}"),
2533            ("snested", "{nested:json}"),
2534            ("lstruct", "{0x1::m::S { c: n8, d: n16 }:json}"),
2535            ("lempty", "{0x1::m::Empty():json}"),
2536        ];
2537
2538        let output = format(
2539            MockStore::default(),
2540            Limits::default(),
2541            bytes,
2542            layout,
2543            usize::MAX,
2544            ONE_MB,
2545            formats,
2546        )
2547        .await
2548        .unwrap();
2549
2550        assert_debug_snapshot!(output, @r###"
2551        {
2552            "s8": Ok(
2553                Number(12),
2554            ),
2555            "l8": Ok(
2556                Number(34),
2557            ),
2558            "s16": Ok(
2559                Number(1234),
2560            ),
2561            "l16": Ok(
2562                Number(5678),
2563            ),
2564            "s32": Ok(
2565                Number(12345678),
2566            ),
2567            "l32": Ok(
2568                Number(87654321),
2569            ),
2570            "s64": Ok(
2571                String("123456781234567890"),
2572            ),
2573            "l64": Ok(
2574                String("9876543210987654321"),
2575            ),
2576            "sstr": Ok(
2577                String("hello"),
2578            ),
2579            "lstr": Ok(
2580                String("goodbye"),
2581            ),
2582            "sbytes": Ok(
2583                String("AQID"),
2584            ),
2585            "lbytes": Ok(
2586                String("BAUG"),
2587            ),
2588            "vbytes": Ok(
2589                Array [
2590                    Number(1),
2591                    Number(2),
2592                    Number(3),
2593                ],
2594            ),
2595            "snone": Ok(
2596                Null,
2597            ),
2598            "ssome": Ok(
2599                Array [
2600                    Number(4),
2601                    Number(5),
2602                    Number(6),
2603                ],
2604            ),
2605            "lvec": Ok(
2606                Array [
2607                    String("7"),
2608                    String("8"),
2609                    String("9"),
2610                ],
2611            ),
2612            "svariant": Ok(
2613                Object {
2614                    "@variant": String("B"),
2615                    "y": Number(5678),
2616                },
2617            ),
2618            "lvariant": Ok(
2619                Object {
2620                    "@variant": String("A"),
2621                    "pos0": Number(90),
2622                },
2623            ),
2624            "snested": Ok(
2625                Object {
2626                    "a": Number(9),
2627                    "b": String("10"),
2628                },
2629            ),
2630            "lstruct": Ok(
2631                Object {
2632                    "c": Number(12),
2633                    "d": Number(1234),
2634                },
2635            ),
2636            "lempty": Ok(
2637                Object {},
2638            ),
2639        }
2640        "###);
2641    }
2642
2643    #[tokio::test]
2644    async fn test_display_string_hardening() {
2645        let bytes = bcs::to_bytes(&("ascii", "🔥", vec![0xC3u8])).unwrap();
2646        let layout = struct_(
2647            "0x1::m::S",
2648            vec![
2649                ("ascii", L::Struct(Box::new(move_utf8_str_layout()))),
2650                ("utf8", L::Struct(Box::new(move_utf8_str_layout()))),
2651                ("invalid", L::Struct(Box::new(move_utf8_str_layout()))),
2652            ],
2653        );
2654
2655        let formats = [
2656            ("ascii", "{ascii}"),
2657            ("utf8", "{utf8}"),
2658            ("invalid", "{invalid}"),
2659        ];
2660
2661        let output = format(
2662            MockStore::default(),
2663            Limits::default(),
2664            bytes,
2665            layout,
2666            usize::MAX,
2667            ONE_MB,
2668            formats,
2669        )
2670        .await
2671        .unwrap();
2672
2673        assert_debug_snapshot!(output, @r###"
2674        {
2675            "ascii": Ok(
2676                String("ascii"),
2677            ),
2678            "utf8": Ok(
2679                String("🔥"),
2680            ),
2681            "invalid": Err(
2682                TransformInvalid_ {
2683                    offset: 0,
2684                    reason: "expected utf8 bytes",
2685                },
2686            ),
2687        }
2688        "###);
2689    }
2690
2691    #[tokio::test]
2692    async fn test_format_single_bare_expression_falls_back_to_json() {
2693        #[derive(serde::Serialize)]
2694        enum Status<'s> {
2695            Pending(&'s str),
2696        }
2697
2698        let bytes = bcs::to_bytes(&(
2699            (42u64, "hello"),
2700            Status::Pending("ready"),
2701            vec![1u64, 2u64, 3u64],
2702        ))
2703        .unwrap();
2704
2705        let layout = struct_(
2706            "0x1::m::S",
2707            vec![
2708                (
2709                    "st",
2710                    struct_(
2711                        "0x1::m::Inner",
2712                        vec![
2713                            ("count", L::U64),
2714                            ("label", L::Struct(Box::new(move_ascii_str_layout()))),
2715                        ],
2716                    ),
2717                ),
2718                (
2719                    "en",
2720                    enum_(
2721                        "0x1::m::Status",
2722                        vec![(
2723                            "Pending",
2724                            vec![("message", L::Struct(Box::new(move_ascii_str_layout())))],
2725                        )],
2726                    ),
2727                ),
2728                ("vs", vector_(L::U64)),
2729            ],
2730        );
2731
2732        let store = MockStore::default();
2733        let root = OwnedSlice::new(layout, bytes);
2734        let interpreter = Interpreter::new(root, store);
2735
2736        let formats = ["{st}", "{en}", "{vs}"];
2737        let mut output = Vec::with_capacity(formats.len());
2738        for s in formats {
2739            let format = Format::parse(Limits::default(), s).unwrap();
2740            output.push(
2741                format
2742                    .format::<serde_json::Value>(&interpreter, usize::MAX, usize::MAX)
2743                    .await
2744                    .unwrap(),
2745            );
2746        }
2747
2748        assert_json_snapshot!(output, @r###"
2749        [
2750          {
2751            "count": "42",
2752            "label": "hello"
2753          },
2754          {
2755            "@variant": "Pending",
2756            "message": "ready"
2757          },
2758          [
2759            "1",
2760            "2",
2761            "3"
2762          ]
2763        ]
2764        "###);
2765    }
2766
2767    #[tokio::test]
2768    async fn test_display_field_errors() {
2769        let bytes = bcs::to_bytes(&0u8).unwrap();
2770        let layout = struct_("0x1::m::S", vec![("byte", L::U8)]);
2771
2772        let formats = [
2773            ("parsing_error", "{42"),
2774            ("bad_transform", "{byte:invalid}"),
2775            ("too_deep", "{a[b[c[d[e[f]]]]]}"),
2776        ];
2777
2778        let limits = Limits {
2779            max_depth: 5,
2780            ..Limits::default()
2781        };
2782
2783        let output = format(
2784            MockStore::default(),
2785            limits,
2786            bytes,
2787            layout,
2788            usize::MAX,
2789            ONE_MB,
2790            formats,
2791        )
2792        .await
2793        .unwrap();
2794
2795        assert_debug_snapshot!(output, @r###"
2796        {
2797            "parsing_error": Err(
2798                UnexpectedEos {
2799                    expect: ExpectedSet {
2800                        prev: [],
2801                        tried: [
2802                            Literal(
2803                                "u8",
2804                            ),
2805                            Literal(
2806                                "u16",
2807                            ),
2808                            Literal(
2809                                "u32",
2810                            ),
2811                            Literal(
2812                                "u64",
2813                            ),
2814                            Literal(
2815                                "u128",
2816                            ),
2817                            Literal(
2818                                "u256",
2819                            ),
2820                        ],
2821                    },
2822                },
2823            ),
2824            "bad_transform": Err(
2825                UnexpectedToken {
2826                    actual: OwnedLexeme(
2827                        false,
2828                        Ident,
2829                        6,
2830                        "invalid",
2831                    ),
2832                    expect: ExpectedSet {
2833                        prev: [],
2834                        tried: [
2835                            Literal(
2836                                "base64",
2837                            ),
2838                            Literal(
2839                                "bcs",
2840                            ),
2841                            Literal(
2842                                "hex",
2843                            ),
2844                            Literal(
2845                                "json",
2846                            ),
2847                            Literal(
2848                                "str",
2849                            ),
2850                            Literal(
2851                                "ts",
2852                            ),
2853                            Literal(
2854                                "url",
2855                            ),
2856                        ],
2857                    },
2858                },
2859            ),
2860            "too_deep": Err(
2861                TooDeep,
2862            ),
2863        }
2864        "###);
2865    }
2866
2867    #[tokio::test]
2868    async fn test_display_vector_literal_type_mismatch() {
2869        let bytes = bcs::to_bytes(&0u8).unwrap();
2870        let layout = struct_("0x1::m::S", vec![("byte", L::U8)]);
2871
2872        let formats = [
2873            ("between_literals", "{vector[42u8, 42u64]:bcs}"),
2874            ("between_field_and_literal", "{vector[42u64, byte]:bcs}"),
2875            ("between_annotation_and_element", "{vector<u64>[byte]:bcs}"),
2876        ];
2877
2878        let output = format(
2879            MockStore::default(),
2880            Limits::default(),
2881            bytes,
2882            layout,
2883            usize::MAX,
2884            ONE_MB,
2885            formats,
2886        )
2887        .await
2888        .unwrap();
2889
2890        assert_debug_snapshot!(output, @r###"
2891        {
2892            "between_literals": Err(
2893                VectorTypeMismatch {
2894                    offset: 1,
2895                    this: U8,
2896                    that: U64,
2897                },
2898            ),
2899            "between_field_and_literal": Err(
2900                VectorTypeMismatch {
2901                    offset: 1,
2902                    this: U64,
2903                    that: U8,
2904                },
2905            ),
2906            "between_annotation_and_element": Err(
2907                VectorTypeMismatch {
2908                    offset: 1,
2909                    this: U64,
2910                    that: U8,
2911                },
2912            ),
2913        }
2914        "###);
2915    }
2916
2917    #[tokio::test]
2918    async fn test_display_output_node_limits() {
2919        let bytes = bcs::to_bytes(&42u64).unwrap();
2920
2921        let limits = Limits {
2922            max_nodes: 10,
2923            ..Limits::default()
2924        };
2925
2926        // The output node limit is enforced across all fields.
2927        let big_field = [("f", "{a | b | c | d | e | f | g | h | i | j}")];
2928        let two_fields = [("f", "{a | b | c | d | e}"), ("g", "{f | g | h | i | j}")];
2929
2930        let res = format(
2931            MockStore::default(),
2932            limits.clone(),
2933            bytes.clone(),
2934            L::U64,
2935            usize::MAX,
2936            ONE_MB,
2937            big_field,
2938        )
2939        .await;
2940        assert!(matches!(res, Err(Error::TooBig)));
2941
2942        let res = format(
2943            MockStore::default(),
2944            limits,
2945            bytes,
2946            L::U64,
2947            usize::MAX,
2948            ONE_MB,
2949            two_fields,
2950        )
2951        .await;
2952        assert!(matches!(res, Err(Error::TooBig)));
2953    }
2954
2955    #[tokio::test]
2956    async fn test_display_output_size_limits() {
2957        let bytes = bcs::to_bytes(&42u64).unwrap();
2958        let formats = [("x", "012345"), ("y", "67890"), ("z", "ABCDE")];
2959
2960        let res = format(
2961            MockStore::default(),
2962            Limits::default(),
2963            bytes,
2964            L::U64,
2965            usize::MAX,
2966            10,
2967            formats,
2968        )
2969        .await;
2970        assert!(matches!(res, Err(Error::TooMuchOutput)));
2971    }
2972
2973    #[tokio::test]
2974    async fn test_display_move_value_depth_limit() {
2975        let bytes = bcs::to_bytes(&42u64).unwrap();
2976
2977        let formats = [
2978            ("leaf", "{42u64:json}"),
2979            ("shallow", "{0x1::m::S(43u128):json}"),
2980            (
2981                "deep",
2982                "{0x1::m::S(vector[vector[0x1::m::T(44u256)]]):json}",
2983            ),
2984        ];
2985
2986        let output = format(
2987            MockStore::default(),
2988            Limits::default(),
2989            bytes,
2990            L::U64,
2991            3,
2992            ONE_MB,
2993            formats,
2994        )
2995        .await
2996        .unwrap();
2997
2998        assert_debug_snapshot!(output, @r###"
2999        {
3000            "leaf": Ok(
3001                String("42"),
3002            ),
3003            "shallow": Ok(
3004                Object {
3005                    "pos0": String("43"),
3006                },
3007            ),
3008            "deep": Err(
3009                TooDeep,
3010            ),
3011        }
3012        "###);
3013    }
3014
3015    #[tokio::test]
3016    async fn test_display_too_many_loads() {
3017        let bytes = bcs::to_bytes(&42u64).unwrap();
3018
3019        let limits = Limits {
3020            max_loads: 3,
3021            ..Limits::default()
3022        };
3023
3024        // Dynamic field accesses (->[...]) count as 1 load
3025        // Dynamic object field accesses (=>[...]) count as 2 loads
3026        let big_field = [("f", "{a->[b]->[c]->[d]->[e]}")];
3027        let two_fields = [("f1", "{a->[b]}"), ("f2", "{c->[d]}"), ("f3", "{e=>[f]}")];
3028
3029        let res = format(
3030            MockStore::default(),
3031            limits.clone(),
3032            bytes.clone(),
3033            L::U64,
3034            usize::MAX,
3035            ONE_MB,
3036            big_field,
3037        )
3038        .await;
3039        assert!(matches!(res, Err(Error::TooManyLoads)));
3040
3041        let res = format(
3042            MockStore::default(),
3043            limits,
3044            bytes,
3045            L::U64,
3046            usize::MAX,
3047            ONE_MB,
3048            two_fields,
3049        )
3050        .await;
3051        assert!(matches!(res, Err(Error::TooManyLoads)));
3052    }
3053
3054    #[tokio::test]
3055    async fn test_display_name_empty() {
3056        let bytes = bcs::to_bytes(&42u64).unwrap();
3057
3058        // Name evaluates to null when the field doesn't exist
3059        let formats = [("name {missing}", "value")];
3060        let res = format(
3061            MockStore::default(),
3062            Limits::default(),
3063            bytes,
3064            L::U64,
3065            usize::MAX,
3066            ONE_MB,
3067            formats,
3068        )
3069        .await;
3070        assert!(matches!(res, Err(Error::NameEmpty(_))), "{res:?}");
3071    }
3072
3073    #[tokio::test]
3074    async fn test_display_duplicate_name() {
3075        let layout = struct_("0x1::m::S", vec![("a", L::U64), ("b", L::U64)]);
3076
3077        // Static duplicate: same literal name
3078        let formats = [("field", "value1"), ("field", "value2")];
3079        let bytes = bcs::to_bytes(&(42u64, 43u64)).unwrap();
3080        let res = format(
3081            MockStore::default(),
3082            Limits::default(),
3083            bytes,
3084            layout.clone(),
3085            usize::MAX,
3086            ONE_MB,
3087            formats,
3088        )
3089        .await;
3090        assert!(matches!(res, Err(Error::NameDuplicate(_))));
3091
3092        // Dynamic duplicate: both names evaluate to the same value
3093        let formats = [("{a}", "value1"), ("{b}", "value2")];
3094        let bytes = bcs::to_bytes(&(42u64, 42u64)).unwrap();
3095        let res = format(
3096            MockStore::default(),
3097            Limits::default(),
3098            bytes,
3099            layout.clone(),
3100            usize::MAX,
3101            ONE_MB,
3102            formats,
3103        )
3104        .await;
3105        assert!(matches!(res, Err(Error::NameDuplicate(_))));
3106
3107        // Mixed case: a dynamic name collides with a static one
3108        let formats = [("f42", "value1"), ("f{a}", "value2")];
3109        let bytes = bcs::to_bytes(&(42u64, 43u64)).unwrap();
3110        let res = format(
3111            MockStore::default(),
3112            Limits::default(),
3113            bytes,
3114            layout.clone(),
3115            usize::MAX,
3116            ONE_MB,
3117            formats,
3118        )
3119        .await;
3120        assert!(matches!(res, Err(Error::NameDuplicate(_))));
3121    }
3122}