1use super::*;
2use crate::representations::*;
3use crate::rust::prelude::*;
4use crate::traversal::*;
5use crate::*;
6use TypedTraversalEvent::*;
7
8#[derive(Debug, Clone, Copy)]
11pub struct NestedStringDisplayContext<'s, 'a, E: FormattableCustomExtension> {
12 pub schema: &'s Schema<E::CustomSchema>,
13 pub custom_context: E::CustomDisplayContext<'a>,
14 pub print_mode: PrintMode,
15}
16
17impl From<fmt::Error> for FormattingError {
18 fn from(e: fmt::Error) -> Self {
19 FormattingError::Fmt(e)
20 }
21}
22
23pub fn format_payload_as_nested_string<F: fmt::Write, E: FormattableCustomExtension>(
24 f: &mut F,
25 context: &NestedStringDisplayContext<'_, '_, E>,
26 payload: &'_ [u8],
27 type_id: LocalTypeId,
28 depth_limit: usize,
29) -> Result<(), FormattingError> {
30 let mut traverser = traverse_payload_with_types(payload, context.schema, type_id, depth_limit);
31 if let PrintMode::MultiLine {
32 first_line_indent, ..
33 } = &context.print_mode
34 {
35 write!(f, "{:first_line_indent$}", "")?;
36 }
37 format_value_tree(f, &mut traverser, context)?;
38 consume_end_event(&mut traverser)?;
39 Ok(())
40}
41
42#[allow(clippy::too_many_arguments)]
43pub(crate) fn format_partial_payload_as_nested_string<
44 F: fmt::Write,
45 E: FormattableCustomExtension,
46>(
47 f: &mut F,
48 partial_payload: &[u8],
49 expected_start: ExpectedStart<E::CustomValueKind>,
50 check_exact_end: bool,
51 current_depth: usize,
52 context: &NestedStringDisplayContext<'_, '_, E>,
53 type_id: LocalTypeId,
54 depth_limit: usize,
55) -> Result<(), FormattingError> {
56 let mut traverser = traverse_partial_payload_with_types(
57 partial_payload,
58 expected_start,
59 check_exact_end,
60 current_depth,
61 context.schema,
62 type_id,
63 depth_limit,
64 );
65 if let PrintMode::MultiLine {
66 first_line_indent, ..
67 } = &context.print_mode
68 {
69 write!(f, "{:first_line_indent$}", "")?;
70 }
71 format_value_tree(f, &mut traverser, context)?;
72 if check_exact_end {
73 consume_end_event(&mut traverser)?;
74 }
75 Ok(())
76}
77
78fn consume_end_event<E: FormattableCustomExtension>(
79 traverser: &mut TypedTraverser<E>,
80) -> Result<(), FormattingError> {
81 traverser.consume_end_event().map_err(FormattingError::Sbor)
82}
83
84fn consume_container_end<E: FormattableCustomExtension>(
85 traverser: &mut TypedTraverser<E>,
86) -> Result<(), FormattingError> {
87 traverser
88 .consume_container_end_event()
89 .map_err(FormattingError::Sbor)
90}
91
92fn format_value_tree<F: fmt::Write, E: FormattableCustomExtension>(
93 f: &mut F,
94 traverser: &mut TypedTraverser<E>,
95 context: &NestedStringDisplayContext<'_, '_, E>,
96) -> Result<(), FormattingError> {
97 let typed_event = traverser.next_event();
98 match typed_event.event {
99 ContainerStart(type_id, container_header) => {
100 let parent_depth = typed_event.location.typed_container_path.len();
101 match container_header {
102 ContainerHeader::Tuple(header) => {
103 format_tuple(f, traverser, context, type_id, header, parent_depth)
104 }
105 ContainerHeader::EnumVariant(header) => {
106 format_enum_variant(f, traverser, context, type_id, header, parent_depth)
107 }
108 ContainerHeader::Array(header) => {
109 format_array(f, traverser, context, type_id, header, parent_depth)
110 }
111 ContainerHeader::Map(header) => {
112 format_map(f, traverser, context, type_id, header, parent_depth)
113 }
114 }
115 }
116 TerminalValue(type_id, value_ref) => format_terminal_value(f, context, type_id, value_ref),
117 _ => Err(FormattingError::Sbor(
118 typed_event
119 .display_as_unexpected_event("ContainerStart | TerminalValue", context.schema),
120 )),
121 }
122}
123
124fn format_tuple<F: fmt::Write, E: FormattableCustomExtension>(
125 f: &mut F,
126 traverser: &mut TypedTraverser<E>,
127 context: &NestedStringDisplayContext<'_, '_, E>,
128 type_id: LocalTypeId,
129 tuple_header: TupleHeader,
130 parent_depth: usize,
131) -> Result<(), FormattingError> {
132 let tuple_data = context
133 .schema
134 .resolve_matching_tuple_metadata(type_id, tuple_header.length);
135
136 if let Some(type_name) = tuple_data.name {
137 write!(f, "Tuple:{}(", type_name)?;
138 } else {
139 write!(f, "Tuple(")?;
140 }
141
142 let child_count = tuple_header.length;
143
144 match (child_count, context.print_mode) {
145 (0, _) => {
146 write!(f, ")")?;
147 }
148 (_, PrintMode::SingleLine) => {
149 match tuple_data.field_names {
150 Some(field_names) => {
151 for i in 0..tuple_header.length {
152 if i > 0 {
153 write!(f, ", ")?;
154 }
155 write!(f, "{} = ", field_names.get(i).unwrap())?;
156 format_value_tree(f, traverser, context)?;
157 }
158 }
159 _ => {
160 for i in 0..tuple_header.length {
161 if i > 0 {
162 write!(f, ", ")?;
163 }
164 format_value_tree(f, traverser, context)?;
165 }
166 }
167 }
168 write!(f, ")")?;
169 }
170 (
171 _,
172 PrintMode::MultiLine {
173 indent_size: spaces_per_indent,
174 base_indent,
175 ..
176 },
177 ) => {
178 let child_indent_size = base_indent + spaces_per_indent * parent_depth;
179 let child_indent = " ".repeat(child_indent_size);
180 let parent_indent = &child_indent[0..child_indent_size - spaces_per_indent];
181 writeln!(f)?;
182 match tuple_data.field_names {
183 Some(field_names) => {
184 for i in 0..tuple_header.length {
185 write!(f, "{}{} = ", child_indent, field_names.get(i).unwrap())?;
186 format_value_tree(f, traverser, context)?;
187 writeln!(f, ",")?;
188 }
189 }
190 _ => {
191 for _ in 0..tuple_header.length {
192 write!(f, "{}", child_indent)?;
193 format_value_tree(f, traverser, context)?;
194 writeln!(f, ",")?;
195 }
196 }
197 }
198
199 write!(f, "{})", parent_indent)?;
200 }
201 }
202
203 consume_container_end(traverser)?;
204 Ok(())
205}
206
207fn format_enum_variant<F: fmt::Write, E: FormattableCustomExtension>(
208 f: &mut F,
209 traverser: &mut TypedTraverser<E>,
210 context: &NestedStringDisplayContext<'_, '_, E>,
211 type_id: LocalTypeId,
212 variant_header: EnumVariantHeader,
213 parent_depth: usize,
214) -> Result<(), FormattingError> {
215 let enum_data = context.schema.resolve_matching_enum_metadata(
216 type_id,
217 variant_header.variant,
218 variant_header.length,
219 );
220
221 if let Some(type_name) = enum_data.enum_name {
222 write!(f, "Enum:{}", type_name)?;
223 } else {
224 write!(f, "Enum")?;
225 }
226
227 if let Some(variant_name) = enum_data.variant_name {
228 write!(f, "<{}u8:{}>", variant_header.variant, variant_name)?;
229 } else {
230 write!(f, "<{}u8>", variant_header.variant)?;
231 }
232
233 write!(f, "(")?;
234 let field_length = variant_header.length;
235 match (field_length, context.print_mode) {
236 (0, _) | (_, PrintMode::SingleLine) => {
237 match enum_data.field_names {
238 Some(field_names) => {
239 for i in 0..field_length {
240 write!(
241 f,
242 "{}{} = ",
243 (if i == 0 { "" } else { ", " }),
244 field_names.get(i).unwrap()
245 )?;
246 format_value_tree(f, traverser, context)?;
247 }
248 }
249 _ => {
250 for i in 0..field_length {
251 write!(f, "{}", (if i == 0 { "" } else { ", " }))?;
252 format_value_tree(f, traverser, context)?;
253 }
254 }
255 }
256 write!(f, ")")?;
257 }
258 (
259 _,
260 PrintMode::MultiLine {
261 indent_size: spaces_per_indent,
262 base_indent,
263 ..
264 },
265 ) => {
266 let child_indent_size = base_indent + spaces_per_indent * parent_depth;
267 let child_indent = " ".repeat(child_indent_size);
268 let parent_indent = &child_indent[0..child_indent_size - spaces_per_indent];
269 writeln!(f)?;
270 match enum_data.field_names {
271 Some(field_names) => {
272 for i in 0..field_length {
273 write!(f, "{}{} = ", child_indent, field_names.get(i).unwrap())?;
274 format_value_tree(f, traverser, context)?;
275 writeln!(f, ",")?;
276 }
277 }
278 _ => {
279 for _ in 0..field_length {
280 write!(f, "{}", child_indent)?;
281 format_value_tree(f, traverser, context)?;
282 writeln!(f, ",")?;
283 }
284 }
285 }
286
287 write!(f, "{})", parent_indent)?;
288 }
289 }
290
291 consume_container_end(traverser)?;
292 Ok(())
293}
294
295fn format_array<F: fmt::Write, E: FormattableCustomExtension>(
296 f: &mut F,
297 traverser: &mut TypedTraverser<E>,
298 context: &NestedStringDisplayContext<'_, '_, E>,
299 type_id: LocalTypeId,
300 array_header: ArrayHeader<E::CustomValueKind>,
301 parent_depth: usize,
302) -> Result<(), FormattingError> {
303 let array_data = context.schema.resolve_matching_array_metadata(type_id);
304
305 match array_data.array_name {
310 Some(array_name) => {
311 write!(f, "Array:{}<", array_name)?;
312 }
313 None => {
314 write!(f, "Array<")?;
315 }
316 }
317
318 match array_data.element_name {
319 Some(element_name) => {
320 write!(f, "{}:{}>(", array_header.element_value_kind, element_name)?;
321 }
322 None => {
323 write!(f, "{}>(", array_header.element_value_kind)?;
324 }
325 }
326
327 let child_count = array_header.length;
328
329 match (
330 child_count,
331 context.print_mode,
332 array_header.element_value_kind,
333 ) {
334 (0, _, _) => {
335 write!(f, ")")?;
336 }
337 (_, _, ValueKind::U8) => {
338 write!(f, "Hex(\"")?;
339 let typed_event = traverser.next_event();
340 match typed_event.event {
341 TerminalValueBatch(_, TerminalValueBatchRef::U8(bytes)) => {
342 f.write_str(&hex::encode(bytes))?;
343 }
344 _ => Err(FormattingError::Sbor(
345 typed_event.display_as_unexpected_event("TerminalValueBatch", context.schema),
346 ))?,
347 };
348 write!(f, "\"))")?;
349 }
350 (child_count, PrintMode::SingleLine, _) => {
351 for i in 0..child_count {
352 if i > 0 {
353 write!(f, ", ")?;
354 }
355 format_value_tree(f, traverser, context)?;
356 }
357 write!(f, ")")?;
358 }
359 (
360 child_count,
361 PrintMode::MultiLine {
362 indent_size: spaces_per_indent,
363 base_indent,
364 ..
365 },
366 _,
367 ) => {
368 let child_indent_size = base_indent + spaces_per_indent * parent_depth;
369 let child_indent = " ".repeat(child_indent_size);
370 let parent_indent = &child_indent[0..child_indent_size - spaces_per_indent];
371 writeln!(f)?;
372 for _ in 0..child_count {
373 write!(f, "{}", child_indent)?;
374 format_value_tree(f, traverser, context)?;
375 writeln!(f, ",")?;
376 }
377
378 write!(f, "{})", parent_indent)?;
379 }
380 }
381
382 consume_container_end(traverser)?;
383 Ok(())
384}
385
386fn format_map<F: fmt::Write, E: FormattableCustomExtension>(
387 f: &mut F,
388 traverser: &mut TypedTraverser<E>,
389 context: &NestedStringDisplayContext<'_, '_, E>,
390 type_id: LocalTypeId,
391 map_header: MapHeader<E::CustomValueKind>,
392 parent_depth: usize,
393) -> Result<(), FormattingError> {
394 let map_data = context.schema.resolve_matching_map_metadata(type_id);
395
396 match map_data.map_name {
397 Some(array_name) => {
398 write!(f, "Map:{}<", array_name)?;
399 }
400 None => {
401 write!(f, "Map<")?;
402 }
403 }
404
405 match map_data.key_name {
406 Some(key_name) => {
407 write!(f, "{}:{}, ", map_header.key_value_kind, key_name)?;
408 }
409 None => {
410 write!(f, "{}, ", map_header.key_value_kind)?;
411 }
412 }
413
414 match map_data.value_name {
415 Some(value_name) => {
416 write!(f, "{}:{}>(", map_header.value_value_kind, value_name)?;
417 }
418 None => {
419 write!(f, "{}>(", map_header.value_value_kind)?;
420 }
421 }
422
423 let child_count = map_header.length * 2;
424
425 match (child_count, context.print_mode) {
426 (0, _) => {
427 write!(f, ")")?;
428 }
429 (child_count, PrintMode::SingleLine) => {
430 for i in 0..child_count {
431 if i > 0 {
432 write!(f, ", ")?;
433 }
434 format_value_tree(f, traverser, context)?;
435 }
436 write!(f, ")")?;
437 }
438 (
439 child_count,
440 PrintMode::MultiLine {
441 indent_size: spaces_per_indent,
442 base_indent,
443 ..
444 },
445 ) => {
446 let child_indent_size = base_indent + spaces_per_indent * parent_depth;
447 let child_indent = " ".repeat(child_indent_size);
448 let parent_indent = &child_indent[0..child_indent_size - spaces_per_indent];
449 writeln!(f)?;
450 for _ in 0..child_count {
451 write!(f, "{}", child_indent)?;
452 format_value_tree(f, traverser, context)?;
453 writeln!(f, ",")?;
454 }
455
456 write!(f, "{})", parent_indent)?;
457 }
458 }
459
460 consume_container_end(traverser)?;
461 Ok(())
462}
463
464fn format_terminal_value<F: fmt::Write, E: FormattableCustomExtension>(
465 f: &mut F,
466 context: &NestedStringDisplayContext<'_, '_, E>,
467 type_id: LocalTypeId,
468 value_ref: TerminalValueRef<E::CustomTraversal>,
469) -> Result<(), FormattingError> {
470 let type_name = context
471 .schema
472 .resolve_type_metadata(type_id)
473 .and_then(|m| m.get_name());
474 match value_ref {
475 TerminalValueRef::Bool(value) => write!(f, "{value}")?,
476 TerminalValueRef::I8(value) => write!(f, "{value}i8")?,
477 TerminalValueRef::I16(value) => write!(f, "{value}i16")?,
478 TerminalValueRef::I32(value) => write!(f, "{value}i32")?,
479 TerminalValueRef::I64(value) => write!(f, "{value}i64")?,
480 TerminalValueRef::I128(value) => write!(f, "{value}i128")?,
481 TerminalValueRef::U8(value) => write!(f, "{value}u8")?,
482 TerminalValueRef::U16(value) => write!(f, "{value}u16")?,
483 TerminalValueRef::U32(value) => write!(f, "{value}u32")?,
484 TerminalValueRef::U64(value) => write!(f, "{value}u64")?,
485 TerminalValueRef::U128(value) => write!(f, "{value}u128")?,
486 TerminalValueRef::String(value) => write!(f, "{value:?}")?,
491 TerminalValueRef::Custom(ref value) => {
492 match type_name {
493 Some(type_name) => {
494 write!(f, "{}:{}(", value_ref.value_kind(), type_name)?;
495 }
496 None => {
497 write!(f, "{}(", value_ref.value_kind())?;
498 }
499 }
500 E::display_string_content(f, &context.custom_context, value)?;
501 write!(f, ")")?;
502 return Ok(());
503 }
504 }
505 if let Some(type_name) = type_name {
509 write!(f, ":{}", type_name)?;
510 }
511 Ok(())
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use radix_rust::*;
518
519 #[derive(Sbor, Hash, Eq, PartialEq)]
520 #[allow(clippy::enum_variant_names)]
521 enum TestEnum {
522 UnitVariant,
523 SingleFieldVariant { field: u8 },
524 DoubleStructVariant { field1: u8, field2: u8 },
525 }
526
527 #[derive(Sbor)]
528 struct MyUnitStruct;
529
530 #[derive(BasicSbor)]
531 struct MyComplexTupleStruct(
532 Vec<u16>,
533 Vec<u16>,
534 Vec<u8>,
535 Vec<u8>,
536 IndexMap<TestEnum, MyFieldStruct>,
537 BTreeMap<String, MyUnitStruct>,
538 TestEnum,
539 TestEnum,
540 TestEnum,
541 MyFieldStruct,
542 Vec<MyUnitStruct>,
543 BasicValue,
544 );
545
546 #[derive(Sbor)]
547 struct MyFieldStruct {
548 field1: u64,
549 field2: Vec<String>,
550 }
551
552 #[test]
553 fn complex_value_formatting() {
554 let (type_id, schema) =
555 generate_full_schema_from_single_type::<MyComplexTupleStruct, NoCustomSchema>();
556 let value = MyComplexTupleStruct(
557 vec![1, 2, 3],
558 vec![],
559 vec![],
560 vec![1, 2, 3],
561 indexmap! {
562 TestEnum::UnitVariant => MyFieldStruct { field1: 1, field2: vec!["hello".to_string()] },
563 TestEnum::SingleFieldVariant { field: 1 } => MyFieldStruct { field1: 2, field2: vec!["world".to_string()] },
564 TestEnum::DoubleStructVariant { field1: 1, field2: 2 } => MyFieldStruct { field1: 3, field2: vec!["!".to_string()] },
565 },
566 btreemap! {
567 "hello".to_string() => MyUnitStruct,
568 "world".to_string() => MyUnitStruct,
569 },
570 TestEnum::UnitVariant,
571 TestEnum::SingleFieldVariant { field: 1 },
572 TestEnum::DoubleStructVariant {
573 field1: 3,
574 field2: 5,
575 },
576 MyFieldStruct {
577 field1: 21,
578 field2: vec!["hello".to_string(), "world!".to_string()],
579 },
580 vec![MyUnitStruct, MyUnitStruct],
581 Value::Tuple {
582 fields: vec![
583 Value::Enum {
584 discriminator: 32,
585 fields: vec![],
586 },
587 Value::Enum {
588 discriminator: 21,
589 fields: vec![Value::I32 { value: -3 }],
590 },
591 ],
592 },
593 );
594 let payload = basic_encode(&value).unwrap();
595
596 let expected_annotated_single_line = r###"Tuple:MyComplexTupleStruct(Array<U16>(1u16, 2u16, 3u16), Array<U16>(), Array<U8>(), Array<U8>(Hex("010203")), Map<Enum:TestEnum, Tuple:MyFieldStruct>(Enum:TestEnum<0u8:UnitVariant>(), Tuple:MyFieldStruct(field1 = 1u64, field2 = Array<String>("hello")), Enum:TestEnum<1u8:SingleFieldVariant>(field = 1u8), Tuple:MyFieldStruct(field1 = 2u64, field2 = Array<String>("world")), Enum:TestEnum<2u8:DoubleStructVariant>(field1 = 1u8, field2 = 2u8), Tuple:MyFieldStruct(field1 = 3u64, field2 = Array<String>("!"))), Map<String, Tuple:MyUnitStruct>("hello", Tuple:MyUnitStruct(), "world", Tuple:MyUnitStruct()), Enum:TestEnum<0u8:UnitVariant>(), Enum:TestEnum<1u8:SingleFieldVariant>(field = 1u8), Enum:TestEnum<2u8:DoubleStructVariant>(field1 = 3u8, field2 = 5u8), Tuple:MyFieldStruct(field1 = 21u64, field2 = Array<String>("hello", "world!")), Array<Tuple:MyUnitStruct>(Tuple:MyUnitStruct(), Tuple:MyUnitStruct()), Tuple(Enum<32u8>(), Enum<21u8>(-3i32)))"###;
597 let display_context = ValueDisplayParameters::Annotated {
598 display_mode: DisplayMode::NestedString,
599 print_mode: PrintMode::SingleLine,
600 schema: schema.v1(),
601 custom_context: Default::default(),
602 type_id,
603 depth_limit: 64,
604 };
605 let actual_annotated_single_line =
606 BasicRawPayload::new_from_valid_slice_with_checks(&payload)
607 .unwrap()
608 .to_string(display_context);
609 assert_eq!(
611 &actual_annotated_single_line,
612 expected_annotated_single_line,
613 );
614
615 let expected_annotated_multi_line = r###"Tuple:MyComplexTupleStruct(
616 Array<U16>(
617 1u16,
618 2u16,
619 3u16,
620 ),
621 Array<U16>(),
622 Array<U8>(),
623 Array<U8>(Hex("010203")),
624 Map<Enum:TestEnum, Tuple:MyFieldStruct>(
625 Enum:TestEnum<0u8:UnitVariant>(),
626 Tuple:MyFieldStruct(
627 field1 = 1u64,
628 field2 = Array<String>(
629 "hello",
630 ),
631 ),
632 Enum:TestEnum<1u8:SingleFieldVariant>(
633 field = 1u8,
634 ),
635 Tuple:MyFieldStruct(
636 field1 = 2u64,
637 field2 = Array<String>(
638 "world",
639 ),
640 ),
641 Enum:TestEnum<2u8:DoubleStructVariant>(
642 field1 = 1u8,
643 field2 = 2u8,
644 ),
645 Tuple:MyFieldStruct(
646 field1 = 3u64,
647 field2 = Array<String>(
648 "!",
649 ),
650 ),
651 ),
652 Map<String, Tuple:MyUnitStruct>(
653 "hello",
654 Tuple:MyUnitStruct(),
655 "world",
656 Tuple:MyUnitStruct(),
657 ),
658 Enum:TestEnum<0u8:UnitVariant>(),
659 Enum:TestEnum<1u8:SingleFieldVariant>(
660 field = 1u8,
661 ),
662 Enum:TestEnum<2u8:DoubleStructVariant>(
663 field1 = 3u8,
664 field2 = 5u8,
665 ),
666 Tuple:MyFieldStruct(
667 field1 = 21u64,
668 field2 = Array<String>(
669 "hello",
670 "world!",
671 ),
672 ),
673 Array<Tuple:MyUnitStruct>(
674 Tuple:MyUnitStruct(),
675 Tuple:MyUnitStruct(),
676 ),
677 Tuple(
678 Enum<32u8>(),
679 Enum<21u8>(
680 -3i32,
681 ),
682 ),
683 )"###;
684 let display_context = ValueDisplayParameters::Annotated {
685 display_mode: DisplayMode::NestedString,
686 print_mode: PrintMode::MultiLine {
687 indent_size: 4,
688 base_indent: 8,
689 first_line_indent: 0,
690 },
691 schema: schema.v1(),
692 custom_context: Default::default(),
693 type_id,
694 depth_limit: 64,
695 };
696 let actual_annotated_multi_line =
697 BasicRawPayload::new_from_valid_slice_with_checks(&payload)
698 .unwrap()
699 .to_string(display_context);
700 assert_eq!(&actual_annotated_multi_line, expected_annotated_multi_line,);
702 }
703}