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