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