1use 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
35pub struct Extract<'s>(Chain<'s>);
37
38pub struct Name<'s>(Literal<'s>);
40
41pub struct Format<'s>(Vec<Strand<'s>>);
43
44pub struct Display<'s> {
46 fields: Vec<Field<'s>>,
47}
48
49struct Field<'s> {
51 key: Sourced<'s, Vec<Strand<'s>>>,
52 val: Sourced<'s, Vec<Strand<'s>>>,
53}
54
55struct Sourced<'s, T> {
57 src: &'s str,
58 val: Result<T, FormatError>,
59}
60
61impl<'s> Extract<'s> {
62 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 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 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 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 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 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 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 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 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 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 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 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 "parent->['df_key'].x",
501 "parent->['df_key'].y",
502 "parent.id->['df_key'].x",
503 "parent=>['dof_key'].x",
505 "parent=>['dof_key'].y",
506 "parent.id=>['dof_key'].id",
507 "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 let obj_bytes = bcs::to_bytes(&0u8).unwrap();
599 let obj_layout = L::U8;
600
601 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 let obj_bytes = bcs::to_bytes(&0u8).unwrap();
656 let obj_layout = L::U8;
657
658 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 "",
753 "foo",
755 "foo.bar",
756 "42",
758 "'hello",
760 "0x1::m::S(",
762 "0x1::m::S(42u64",
763 "vector[1u8, 2u8",
765 "@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 ("scalar_ok", "{num}"),
1236 ("scalar_fail", "{num.field}"),
1237 ("field_fail", "{missing}"),
1239 ("index_ok", "{nums[1u64]}"),
1241 ("index_fail", "{numbers[10u64]}"),
1242 ("combined_ok", "{num}, {nums[0u64]}"),
1244 ("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))), None::<(u64, Option<u64>)>, Some((300u64, None::<u64>)), Some(42u64), None::<u64>, ))
1388 .unwrap();
1389
1390 let formats = [
1391 ("some_inner_data", "{some_inner.data}"),
1393 ("some_inner_optional", "{some_inner.optional_data}"),
1394 ("none_inner_data", "{none_inner.data}"),
1396 ("none_inner_optional", "{none_inner.optional_data}"),
1397 ("partial_inner_data", "{partial_inner.data}"),
1399 ("partial_inner_optional", "{partial_inner.optional_data}"),
1400 ("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 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 #[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, ..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 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 ("4th", "{map[0x42::m::Key(4u64, 'fourth')].data}"),
1789 ("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 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 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 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 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 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 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}