Skip to main content

sov_universal_wallet/visitors/
display.rs

1use std::collections::BTreeMap;
2use std::fmt::Write;
3
4use thiserror::Error;
5
6use crate::schema::Primitive;
7use crate::ty::visitor::{ResolutionError, TypeResolver, TypeVisitor};
8use crate::ty::{
9    byte_display, ByteDisplay, Enum, FixedPointDisplay, IntegerDisplay, IntegerType, LinkingScheme,
10    Struct, Tuple,
11};
12
13type Delimiters = (&'static str, &'static str);
14
15pub type Result<T, E = FormatError> = core::result::Result<T, E>;
16
17/// The largest input size to display (in bytes)
18pub const MAX_INPUT_CHUNK: usize = 65;
19
20#[derive(Debug, Error, Clone)]
21pub enum FormatError {
22    #[error("Core error: {0}")]
23    Core(#[from] core::fmt::Error),
24    #[error("The byte sequence could not be formatted for display: {0}")]
25    InvalidBytes(#[from] byte_display::ByteFormatError),
26    #[error("The input is not a valid utf-8 string: {0}")]
27    InvalidString(#[from] core::str::Utf8Error),
28    #[error("Invalid discriminant `{discriminant}` for {type_name}")]
29    InvalidDiscriminant { type_name: String, discriminant: u8 },
30    #[error(transparent)]
31    UnresolvedType(#[from] ResolutionError),
32    #[error("A discriminant is required for items of type `{type_name}` but the input ended without providing one.")]
33    MissingDiscriminant { type_name: String },
34    #[error("The input claimed to provide an integer {claimed_size} bytes wide, but the maximum allowed size is 16 bytes.")]
35    IntegerTooLarge { claimed_size: u8 },
36    #[error("The input claimed to provide an integer {claimed_size} bytes wide, but only provided {bytes_available} additional bytes of input.")]
37    MissingIntegerInput {
38        claimed_size: u8,
39        bytes_available: u8,
40    },
41    #[error("The input claimed to provide a byte array {claimed_size} bytes wide, but only provided {bytes_available} additional bytes of input.")]
42    MissingBytesInput {
43        claimed_size: usize,
44        bytes_available: usize,
45    },
46    #[error("The input's attributes reference a sibling field with a byte offset, but offset was outside the range of the field's value.")]
47    FieldReferenceOffsetOutOfBounds(usize),
48    #[error("Fixed-point integer formatting specified more decimals than is plausible for an integer: {0}")]
49    TooManyDecimalsForInt(u8),
50    #[error("The input should have contained a vector but did not provide one.")]
51    MissingVecLength,
52    #[error("The input should have contained a string but did not provide one.")]
53    MissingStringLength,
54    #[error("The provided input had leftover bytes that weren't displayed.")]
55    UnusedInput,
56    #[error(
57        "A structs's display template did not provide sufficient slots to display its fields."
58    )]
59    InsufficientTemplateSlots,
60    #[error("A struct's display template had more slots than the struct had fields.")]
61    UnusedTemplateSlots,
62}
63
64pub struct Output<'a, W> {
65    f: &'a mut W,
66    silent: bool,
67    // Incremented every time a peek starts, decremented when it ends.
68    // We are peeking IFF `peeking > 0`.
69    peeking: u32,
70}
71
72impl<'a, W> Output<'a, W> {
73    pub fn new(f: &'a mut W) -> Self {
74        Self {
75            f,
76            silent: false,
77            peeking: 0,
78        }
79    }
80
81    pub fn start_peek(&mut self) {
82        self.peeking = self.peeking.checked_add(1).unwrap();
83    }
84
85    pub fn end_peek(&mut self) {
86        self.peeking = self
87            .peeking
88            .checked_sub(1)
89            .expect("Underflow when ending peek. This is a bug in the schema display logic.")
90    }
91
92    pub fn peeking(&self) -> bool {
93        self.peeking > 0
94    }
95}
96
97impl<W: core::fmt::Write> core::fmt::Write for Output<'_, W> {
98    fn write_str(&mut self, s: &str) -> core::fmt::Result {
99        if self.peeking() {
100            return Ok(());
101        }
102        if self.silent {
103            return Ok(());
104        }
105        self.f.write_str(s)
106    }
107}
108
109pub struct Input<'a> {
110    buf: &'a mut &'a [u8],
111    peeking: bool,
112    /// A stack of origins for nested peek processing, to calculate relative field offsets. First
113    /// element is always 0 for the outermost peek.
114    peek_origins_stack: Vec<usize>,
115    /// The current point we're reading at, while peeking, from the absolute start of the buffer
116    /// (the current start, i.e. from the point we started peeking).
117    peek_cursor: usize,
118}
119
120impl<'a> Input<'a> {
121    pub fn new(buf: &'a mut &'a [u8]) -> Self {
122        Self {
123            buf,
124            peeking: false,
125            peek_origins_stack: Vec::new(),
126            peek_cursor: 0,
127        }
128    }
129
130    pub fn is_empty(&self) -> bool {
131        self.buf.is_empty()
132    }
133
134    pub fn len(&self) -> usize {
135        self.buf.len()
136    }
137
138    pub(crate) fn check_remaining_bytes(&self, mut len: usize) -> Result<(), FormatError> {
139        if self.peeking {
140            len += self.peek_cursor;
141        }
142        if self.buf.len() < len {
143            return Err(FormatError::MissingBytesInput {
144                claimed_size: len,
145                bytes_available: self.buf.len(),
146            });
147        }
148        Ok(())
149    }
150
151    /// Splits the first `len` bytes from the input, returning them as a slice and updating the input buffer.
152    /// Returns an error if there are not enough bytes remaining to fulfill the request.
153    pub fn advance(&mut self, len: usize) -> Result<&[u8], FormatError> {
154        self.check_remaining_bytes(len)?;
155        if self.peeking {
156            let end_cursor = self.peek_cursor + len;
157            let slice = &self.buf[self.peek_cursor..end_cursor];
158            self.peek_cursor = end_cursor;
159            Ok(slice)
160        } else {
161            let (leading, rest) = self.buf.split_at(len);
162            *self.buf = rest;
163            Ok(leading)
164        }
165    }
166
167    /// Similar to `advance()`, but does not advance the input, only returns a reference to the next `len` bytes.
168    ///
169    /// Not to be confused with the struct-wide peeking functionality that switches between
170    /// consuming the buffer and advancing a cursor.
171    pub fn local_peek_bytes(&self, len: usize) -> Result<&[u8], FormatError> {
172        self.check_remaining_bytes(len)?;
173        // When not in a global peek, peek_cursor == 0 so this is a no-op
174        let end = self.peek_cursor + len;
175        Ok(&self.buf[self.peek_cursor..end])
176    }
177
178    /// Similar to `local_peek_bytes()`, but returns a single byte.
179    ///
180    /// Not to be confused with the struct-wide peeking functionality that switches between
181    /// consuming the buffer and advancing a cursor.
182    pub fn local_peek_byte(&self, offset: usize) -> Result<u8, FormatError> {
183        self.check_remaining_bytes(offset)?;
184        // When not in a global peek, peek_cursor == 0 so this is a no-op
185        let offset = self.peek_cursor + offset;
186        Ok(self.buf[offset])
187    }
188
189    pub fn start_peek(&mut self) {
190        self.peeking = true;
191        self.peek_origins_stack.push(self.peek_cursor);
192    }
193
194    /// The peek cursor _from the view of the current innermost peek_.
195    pub fn peek_cursor(&self) -> usize {
196        self.peek_cursor.checked_sub(*self.peek_origins_stack.last().expect("peek_cursor() was called while no peek is ongoing. This is a bug in the schema display logic.")).expect("The peek cursor offset calculation underflowed. This is a bug in the schema display logic.")
197    }
198
199    /// End a peek and proceed with normal input processing.
200    /// This method can only be used after a peek is started. If there was no ongoing peek, the
201    /// method will panic.
202    ///
203    /// Returns true if the peek pass is over, false if there is still an outer peek being
204    /// processed.
205    pub fn end_peek(&mut self) -> bool {
206        self.peek_origins_stack.pop().expect("end_peek() was called but the peek stack was empty. This is a bug in the schema display logic.");
207        // Only actually end peeking if there are no more nested peeks
208        if self.peek_origins_stack.is_empty() {
209            self.peeking = false;
210            self.peek_cursor = 0;
211            true
212        } else {
213            false
214        }
215    }
216}
217
218/// In case an enum variant contains nested trivial tuples, propagate the virtual-ness transitively
219/// to the actual content
220fn tuple_displays_as_enum_contents(context: &Context) -> bool {
221    context.parent_type == ParentType::Enum(IsHideTag::No)
222        || context.parent_type == ParentType::Tuple(IsVirtual::Yes, IsTrivial::Yes)
223}
224
225pub struct DisplayVisitor<'a, 'fmt, W> {
226    input: Input<'a>,
227    output: Output<'fmt, W>,
228}
229
230// To avoid unnecessary nested brackets, we apply an optimization to the display of tuples where
231// tuples with a single variant do not wrap their contents in parentheses by default.
232// This creates a special case when a tuple-variant of an enum has a single field. In that case,
233// we need to "make up" the removed delimiter later on in the rendering process.
234//
235// The "make-up" delimiters are as follows:
236// - Inner enum: "."
237// - Inner tuple: N/A
238// - String-like item: Wrap in parentheses
239// - Other: Add an extra " " after the usual delimiter
240impl<'a, 'fmt, W> DisplayVisitor<'a, 'fmt, W> {
241    pub fn new(input: &'a mut &'a [u8], f: &'fmt mut W) -> Self {
242        Self {
243            input: Input::new(input),
244            output: Output::new(f),
245        }
246    }
247
248    pub fn has_displayed_whole_input(&self) -> bool {
249        self.input.is_empty()
250    }
251
252    fn start_peek(&mut self) {
253        self.output.start_peek();
254        self.input.start_peek();
255    }
256
257    fn end_peek(&mut self) -> bool {
258        self.output.end_peek();
259        self.input.end_peek()
260    }
261
262    fn tuple_delimiters<L: LinkingScheme>(
263        &self,
264        tuple: &Tuple<L>,
265        context: &Context,
266        schema: &impl TypeResolver<LinkingScheme = L>,
267    ) -> Delimiters {
268        let first_child_is_primitive = schema
269            .resolve_or_err(&tuple.fields[0].value)
270            .is_ok_and(|v| v.is_primitive());
271
272        if tuple.fields.len() == 1
273            && !(tuple_displays_as_enum_contents(context) && first_child_is_primitive)
274        {
275            ("", "")
276        } else {
277            ("(", ")")
278        }
279    }
280
281    fn enum_delimiters<L: LinkingScheme>(&mut self, _e: &Enum<L>, context: &Context) -> Delimiters {
282        match context.parent_type {
283            ParentType::Tuple(_, IsTrivial::Yes)
284            | ParentType::Enum(_)
285            | ParentType::Vec
286            | ParentType::Map => (".", ""),
287            ParentType::Tuple(_, _) | ParentType::Struct(_) | ParentType::None => ("", ""),
288        }
289    }
290
291    fn struct_delimiters<L: LinkingScheme>(&self, s: &Struct<L>, context: &Context) -> Delimiters {
292        if s.fields.is_empty() {
293            return ("", "");
294        }
295        match context.parent_type {
296            ParentType::Tuple(IsVirtual::Yes, _) => (" { ", " }"),
297            ParentType::Struct(_)
298            | ParentType::None
299            | ParentType::Tuple(_, _)
300            | ParentType::Vec
301            | ParentType::Map => ("{ ", " }"),
302            ParentType::Enum(_) => (" { ", " }"),
303        }
304    }
305
306    /// Generates a template/format string to display a struct if none was provided as an
307    /// attribute. The default template either displays the typename if the struct has no
308    /// fields, or looks like this (for a hypothetical struct with three fields named as below):
309    /// ```text
310    /// "{ field_one: {}, field_two: {}, field_three: {} }"
311    /// ```
312    /// In other words, simply lists out the field names followed by an unnamed substitution (that
313    /// will be filled in with the field's values, in order), enclosed in braces and
314    /// comma-separated.
315    fn struct_default_template<L: LinkingScheme>(
316        &self,
317        s: &Struct<L>,
318        context: &Context,
319        schema: &impl TypeResolver<LinkingScheme = L>,
320    ) -> String {
321        let mut template = String::new();
322        let (opener, closer) = self.struct_delimiters(s, context);
323        template.push_str(opener);
324        if s.fields.is_empty() {
325            template.push_str(&s.type_name);
326        } else {
327            for (i, field) in s.fields.iter().enumerate() {
328                if field.silent {
329                    continue;
330                }
331                if schema
332                    .resolve_or_err(&field.value)
333                    .is_ok_and(|inner| inner.is_skip())
334                {
335                    continue;
336                }
337                if i > 0 {
338                    template.push_str(self.item_separator());
339                }
340                template.push_str(&field.display_name);
341                template.push_str(": {}");
342            }
343        }
344        template.push_str(closer);
345        template
346    }
347
348    /// Generates a template/format string to display a tuple if none was provided as an
349    /// attribute. The default template looks like this, for example for a tuple with three values:
350    /// ```text
351    /// "({}, {}, {})"
352    /// ```
353    /// In other words, simply lists out the fields as unnamed substitutions (which will be filled in
354    /// with the tuple's values in order, during display), enclosed in brackets and comma-separated.
355    /// The brackets are sometimes omitted - see the implementation of `tuple_delimiters` for the
356    /// logic.
357    fn tuple_default_template<L: LinkingScheme>(
358        &self,
359        t: &Tuple<L>,
360        context: &Context,
361        schema: &impl TypeResolver<LinkingScheme = L>,
362    ) -> String {
363        let mut template = String::new();
364        let (opener, closer) = self.tuple_delimiters(t, context, schema);
365        template.push_str(opener);
366        for (i, field) in t.fields.iter().enumerate() {
367            if field.silent {
368                continue;
369            }
370            if i > 0 {
371                template.push_str(self.item_separator());
372            }
373            template.push_str("{}");
374        }
375        template.push_str(closer);
376        template
377    }
378
379    fn option_none_delimiters(&self) -> Delimiters {
380        ("None", "")
381    }
382
383    fn option_some_delimiters(&self) -> Delimiters {
384        ("", "")
385    }
386
387    fn map_delimiters(&mut self, context: &Context) -> Delimiters {
388        match context.parent_type {
389            ParentType::Tuple(IsVirtual::Yes, _) => (" { ", " }"),
390            ParentType::Struct(_)
391            | ParentType::None
392            | ParentType::Tuple(_, _)
393            | ParentType::Vec
394            | ParentType::Enum(_)
395            | ParentType::Map => ("{ ", " }"),
396        }
397    }
398
399    fn vec_delimiters(&mut self, context: &Context) -> Delimiters {
400        match context.parent_type {
401            ParentType::Tuple(IsVirtual::Yes, _) => (" [", "]"),
402            ParentType::None
403            | ParentType::Struct(_)
404            | ParentType::Tuple(_, _)
405            | ParentType::Vec
406            | ParentType::Enum(_)
407            | ParentType::Map => ("[", "]"),
408        }
409    }
410
411    fn item_separator(&self) -> &'static str {
412        ", "
413    }
414}
415
416impl<W: Write> DisplayVisitor<'_, '_, W> {
417    pub fn read_usize_borsh(&mut self) -> Result<usize, FormatError> {
418        if self.input.len() < 4 {
419            return Err(FormatError::MissingIntegerInput {
420                claimed_size: 4,
421                bytes_available: self.input.len() as u8,
422            });
423        }
424        let len = u32::from_le_bytes(
425            self.input
426                .advance(4)?
427                .try_into()
428                .expect("Converting [u8;4] to u32 is infallible"),
429        ) as usize;
430        Ok(len)
431    }
432
433    pub fn display_byte_sequence(
434        &mut self,
435        len: usize,
436        display: ByteDisplay,
437        _context: Context,
438    ) -> Result<(), FormatError> {
439        self.input.check_remaining_bytes(len)?;
440
441        if len > MAX_INPUT_CHUNK {
442            display.format(self.input.advance(MAX_INPUT_CHUNK)?, &mut self.output)?;
443            self.output.write_fmt(format_args!(
444                " (trailing {} bytes truncated)",
445                len - MAX_INPUT_CHUNK
446            ))?;
447            self.input.advance(len - MAX_INPUT_CHUNK)?;
448        } else {
449            display.format(self.input.advance(len)?, &mut self.output)?;
450        }
451        Ok(())
452    }
453}
454
455#[derive(Clone, Debug)]
456pub struct Context {
457    parent_type: ParentType,
458    /// True if this is a first pre-pass that runs to determine field byte lengths before the
459    /// actual display pass
460    is_peek_pass: bool,
461    /// Bytes referenced by other fields.
462    /// Maps by field index and offset inside the field, because that is how child fields will
463    /// access it.
464    peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>>,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
468pub enum IsVirtual {
469    Yes,
470    No,
471}
472
473/// Tuple wrapping a single value
474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
475pub enum IsTrivial {
476    Yes,
477    No,
478}
479
480/// An enum should display its tags
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482pub enum IsHideTag {
483    Yes,
484    No,
485}
486
487#[derive(Debug, Clone, Copy, PartialEq, Eq)]
488pub enum ParentType {
489    None,
490    Struct(IsVirtual),
491    Tuple(IsVirtual, IsTrivial),
492    Enum(IsHideTag),
493    Vec,
494    Map,
495}
496
497impl Default for Context {
498    fn default() -> Self {
499        Self {
500            parent_type: ParentType::None,
501            is_peek_pass: false,
502            peek_bytes: BTreeMap::new(),
503        }
504    }
505}
506
507/// Format an integer number string as a fixed point with the specified number of decimals.
508/// E.g.
509///  * `format_fixed_point("81", 4)` -> `"0.0081"`
510///  * `format_fixed_point("810", 4)` -> `"0.081"`
511///  * `format_fixed_point("-81", 4)` -> `"-0.0081"`
512///  * `format_fixed_point("24215", 3)` -> `"24.215"`
513///  * `format_fixed_point("24200", 3)` -> `"24.2"`
514///  * `format_fixed_point("24000", 3)` -> `"24"`
515///
516/// We pass the number as a string to be able to dynamically support both u128 and
517/// i128 in the same function (by stripping the first char of the string if it's a '-').
518fn format_fixed_point(number_str: String, decimals: u8) -> String {
519    // 1. Strip the negative sign if necessary, to simplify processing
520    let (number_str, is_negative) = match number_str.strip_prefix('-') {
521        Some(str) => (str.to_string(), true),
522        None => (number_str, false),
523    };
524    // 2. Pad the string to at least decimals + 1, to give a 0 before the '.'
525    let mut number_str = format!("{:0>pad$}", number_str, pad = (decimals + 1) as usize);
526    // 3. Insert the decimal point `decimals` from the right
527    let decimal_idx = number_str.len().checked_sub(decimals.into()).expect("We just formatted the string to be wider than the number of decimals - should never underflow");
528    number_str.insert(decimal_idx, '.');
529    // 3. Trim trailing 0s, and if necessary, the trailing '.' (if the number ended up being whole)
530    let mut number_str = number_str
531        .trim_end_matches('0')
532        .trim_end_matches('.')
533        .to_string();
534    // 4. Finally, restore the negative sign if there was one
535    if is_negative {
536        number_str.insert(0, '-');
537    }
538    number_str
539}
540
541// TODO: this would be nicer for devex if it were a function. The `<$t>::from_le_bytes` is what
542// makes it non-trivial to convert though
543macro_rules! display_int {
544    ($t:ident, $input:expr, $disp:expr, $f:expr, $ctx:expr) => {{
545        let size = IntegerType::$t.size();
546        if $input.len() < size {
547            return Err(FormatError::MissingIntegerInput {
548                claimed_size: size as u8,
549                bytes_available: $input.len() as u8,
550            });
551        }
552        let buf = $input.advance(size)?;
553        match $disp {
554            IntegerDisplay::Hex => {
555                write!($f, "{:#x}", <$t>::from_le_bytes(buf.try_into().unwrap()))?
556            }
557            IntegerDisplay::Decimal => {
558                write!($f, "{}", <$t>::from_le_bytes(buf.try_into().unwrap()))?
559            }
560            IntegerDisplay::FixedPoint(decimal_spec) => {
561                let decimals = match decimal_spec {
562                    FixedPointDisplay::Decimals(d) => d,
563                    FixedPointDisplay::FromSiblingField {
564                        field_index,
565                        byte_offset,
566                    } => {
567                        if $ctx.is_peek_pass {
568                            0
569                        } else {
570                            *$ctx
571                                .peek_bytes
572                                .get(&field_index)
573                                .expect("Fixed point display attempted to get field that was not provided in context - this is a bug in the schema display implementation")
574                                .get(&byte_offset)
575                                .expect("Fixed point display attempted to get byte that was not provided in context - this is a bug in the schema display implementation")
576                        }
577                    }
578                    FixedPointDisplay::FromSiblingFieldWithOverride {
579                        field_index,
580                        byte_offset,
581                        override_match,
582                        override_decimals,
583                    } => {
584                        if $ctx.is_peek_pass {
585                            0
586                        } else {
587                            let field_bytes = $ctx
588                                .peek_bytes
589                                .get(&field_index)
590                                .expect("Fixed point display attempted to get field that was not provided in context - this is a bug in the schema display implementation");
591                            let matches_override = (0..32).all(|i| {
592                                field_bytes.get(&i).copied() == Some(override_match[i])
593                            });
594                            if matches_override {
595                                override_decimals
596                            } else {
597                                *field_bytes
598                                    .get(&byte_offset)
599                                    .expect("Fixed point display attempted to get byte that was not provided in context - this is a bug in the schema display implementation")
600                            }
601                        }
602                    }
603                };
604                // 39 = log_10(128::MAX)
605                if decimals > 39 {
606                    return Err(FormatError::TooManyDecimalsForInt (decimals));
607                }
608                let t = <$t>::from_le_bytes(buf.try_into().unwrap());
609                write!($f, "{}", format_fixed_point(t.to_string(), decimals))?
610            }
611        }
612        Ok(())
613    }};
614}
615
616impl<W: Write, L: LinkingScheme, M> TypeVisitor<L, M> for DisplayVisitor<'_, '_, W> {
617    type Arg = Context;
618    type ReturnType = Result<(), FormatError>;
619    fn visit_enum(
620        &mut self,
621        e: &Enum<L>,
622        schema: &impl TypeResolver<LinkingScheme = L>,
623        mut context: Context,
624    ) -> Self::ReturnType {
625        if self.input.is_empty() && !e.variants.is_empty() {
626            return Err(FormatError::MissingDiscriminant {
627                type_name: e.type_name.clone(),
628            });
629        }
630
631        // If the enum is displayed as part of a tuple, the user doesn't have any context about what the type is,
632        // since there's no field name. In that case we display the full type name.
633        if matches!(context.parent_type, ParentType::Tuple(IsVirtual::No, _))
634            || context.parent_type == ParentType::Vec
635        {
636            self.output.write_str(&e.type_name)?;
637        }
638
639        let (open, close) = self.enum_delimiters(e, &context);
640        self.output.write_str(open)?;
641
642        let discriminant = self.input.advance(1)?[0];
643        let mut variants_by_discriminant =
644            e.variants.iter().filter(|v| v.discriminant == discriminant);
645        let variant = variants_by_discriminant
646            .next()
647            .ok_or(FormatError::InvalidDiscriminant {
648                type_name: e.type_name.clone(),
649                discriminant,
650            })?;
651        assert!(variants_by_discriminant.next().is_none(), "Found two enum variants with the same discriminant - the schema is malformed, cannot proceed!");
652        if variant.template.is_none() && !e.hide_tag {
653            write!(self.output, "{}", variant.name)?;
654        }
655        let is_hide_tag = if e.hide_tag {
656            IsHideTag::Yes
657        } else {
658            IsHideTag::No
659        };
660        context.parent_type = ParentType::Enum(is_hide_tag);
661        if let Some(maybe_resolved) = &variant.value {
662            let inner = schema.resolve_or_err(maybe_resolved)?;
663            inner.visit(schema, self, context)?;
664        }
665        self.output.write_str(close)?;
666        Ok(())
667    }
668
669    fn visit_struct(
670        &mut self,
671        s: &Struct<L>,
672        schema: &impl TypeResolver<LinkingScheme = L>,
673        mut context: Context,
674    ) -> Self::ReturnType {
675        let template = s
676            .template
677            .clone()
678            .unwrap_or_else(|| self.struct_default_template(s, &context, schema));
679        let mut template = template.as_str();
680
681        context.parent_type = ParentType::Struct(IsVirtual::No);
682
683        // If we need to get the field offset indices, we perform a peek pass.
684        // However, recursive peeks are currently not supported. If a parent struct is currently
685        // peeking, skip this: we will perform our pass once it's our turn to display for real.
686        // This is to simplify implementation, as being `peekable` is expected to not be used
687        // often, and nested peeks should be even more rare. If it becomes a problem, this
688        // can be optimised into having a single recursive pre-pass for all structs that need it.
689        if s.peekable && !context.is_peek_pass {
690            // At which byte does every field start?
691            let mut field_offsets: Vec<usize> = Vec::new();
692            // For every field, check which bytes need to be provided in the Context.
693            // Vec<(field idx, byte offset)>
694            let mut bytes_needed: Vec<(usize, usize)> = Vec::new();
695            self.start_peek();
696            context.is_peek_pass = true;
697            for field in s.fields.iter() {
698                field_offsets.push(self.input.peek_cursor());
699                // TODO: if optimisation is required, this resolution can be cached for the "real"
700                // display below to save a clone(). (Need to handle the !peekable case.)
701                let inner_ty = schema.resolve_or_err(&field.value)?;
702                // TODO: if time should be traded for space, the Output can cache the result of
703                // this and reuse it for the "real" display below instead of dropping it
704                inner_ty.visit(schema, self, context.clone())?;
705
706                // resolve all bytes necessary from this field
707                bytes_needed.extend(inner_ty.parent_byte_references());
708            }
709            // Save the end of the entire struct, for bounds checking.
710            field_offsets.push(self.input.peek_cursor());
711            // Rewind the input cursor
712            let still_peeking = !self.end_peek();
713            context.is_peek_pass = still_peeking;
714
715            // Save, into the Context, those bytes which were found to be needed
716            let mut peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>> = BTreeMap::new();
717            for (field, offset) in bytes_needed {
718                // The upper allowed bound on the offset.
719                let field_end = *field_offsets
720                    .get(field + 1)
721                    .expect("A struct's child field attribute referenced a sibling field by index that is out of bounds of the parent struct. This should not be possible in a well-constructed schema.");
722                // unwrap: we just asserted that field_offsets[field+1] exists, so field_offsets[field] must exist too
723                let byte_offset = field_offsets.get(field).unwrap() + offset;
724                if byte_offset > field_end {
725                    return Err(FormatError::FieldReferenceOffsetOutOfBounds(byte_offset));
726                }
727                let byte = self.input.local_peek_byte(byte_offset)?;
728
729                let field_bytes = peek_bytes.entry(field).or_default();
730                field_bytes.insert(offset, byte);
731            }
732            context.peek_bytes = peek_bytes;
733        }
734
735        for field in &s.fields {
736            // Save the previous state of the silent flag so we can restore it after displaying the field.
737            let was_silent = self.output.silent;
738            if field.silent {
739                self.output.silent = true;
740            }
741
742            let inner_ty = schema.resolve_or_err(&field.value)?;
743            if !field.silent && !inner_ty.is_skip() {
744                let Some((before_next_field, rest)) = template.split_once("{}") else {
745                    return Err(FormatError::InsufficientTemplateSlots);
746                };
747                self.output.write_str(before_next_field)?;
748                template = rest;
749            }
750
751            inner_ty.visit(schema, self, context.clone())?;
752            // Restore the silent flag to its previous state
753            self.output.silent = was_silent;
754        }
755        if template.contains("{}") {
756            return Err(FormatError::UnusedTemplateSlots);
757        }
758        self.output.write_str(template)?;
759        Ok(())
760    }
761
762    fn visit_tuple(
763        &mut self,
764        t: &Tuple<L>,
765        schema: &impl TypeResolver<LinkingScheme = L>,
766        mut context: Context,
767    ) -> Self::ReturnType {
768        let template = t
769            .template
770            .clone()
771            .unwrap_or_else(|| self.tuple_default_template(t, &context, schema));
772        let mut template = template.as_str();
773
774        let is_virtual = if tuple_displays_as_enum_contents(&context) {
775            IsVirtual::Yes
776        } else {
777            IsVirtual::No
778        };
779        let trivial = if t.fields.len() == 1 {
780            IsTrivial::Yes
781        } else {
782            IsTrivial::No
783        };
784        context.parent_type = ParentType::Tuple(is_virtual, trivial);
785
786        // In case we require any field offsets, perform a peeking pass before proper display.
787        // Refer to the `visit_struct()` implementation for full documentation.
788        if t.peekable && !context.is_peek_pass {
789            let mut field_offsets: Vec<usize> = Vec::new();
790            // Vec<(field idx, byte offset)>
791            let mut bytes_needed: Vec<(usize, usize)> = Vec::new();
792            self.start_peek();
793            context.is_peek_pass = true;
794            for field in t.fields.iter() {
795                field_offsets.push(self.input.peek_cursor());
796                let inner_ty = schema.resolve_or_err(&field.value)?;
797                inner_ty.visit(schema, self, context.clone())?;
798
799                bytes_needed.extend(inner_ty.parent_byte_references());
800            }
801            field_offsets.push(self.input.peek_cursor());
802            let still_peeking = !self.end_peek();
803            context.is_peek_pass = still_peeking;
804
805            let mut peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>> = BTreeMap::new();
806            for (field, offset) in bytes_needed {
807                let field_end = *field_offsets
808                    .get(field + 1)
809                    .expect("A tuple's child field attribute referenced a sibling field by index that is out of bounds of the parent struct. This should not be possible in a well-constructed schema.");
810                // unwrap: we just asserted that field_offsets[field+1] exists, so field_offsets[field] must exist too
811                let byte_offset = field_offsets.get(field).unwrap() + offset;
812                if byte_offset > field_end {
813                    return Err(FormatError::FieldReferenceOffsetOutOfBounds(byte_offset));
814                }
815                let byte = self.input.local_peek_byte(byte_offset)?;
816
817                let field_bytes = peek_bytes.entry(field).or_default();
818                field_bytes.insert(offset, byte);
819            }
820            context.peek_bytes = peek_bytes;
821        }
822
823        for field in &t.fields {
824            // Save the previous state of the silent flag so we can restore it after displaying the field.
825            let was_silent = self.output.silent;
826            if field.silent {
827                self.output.silent = true;
828            }
829            if !field.silent {
830                let Some((before_next_field, rest)) = template.split_once("{}") else {
831                    return Err(FormatError::InsufficientTemplateSlots);
832                };
833                self.output.write_str(before_next_field)?;
834                template = rest;
835            }
836
837            schema
838                .resolve_or_err(&field.value)?
839                .visit(schema, self, context.clone())?;
840            // Restore the silent flag to its previous state
841            self.output.silent = was_silent;
842        }
843        if template.contains("{}") {
844            return Err(FormatError::UnusedTemplateSlots);
845        }
846        self.output.write_str(template)?;
847        Ok(())
848    }
849
850    fn visit_option(
851        &mut self,
852        value: &L::TypeLink,
853        schema: &impl TypeResolver<LinkingScheme = L>,
854        context: Self::Arg,
855    ) -> Self::ReturnType {
856        let discriminant = self.input.advance(1)?[0];
857
858        match discriminant {
859            0 => {
860                let (open, close) = self.option_none_delimiters();
861                self.output.write_str(open)?;
862                self.output.write_str(close)?;
863            }
864            1 => {
865                let (open, close) = self.option_some_delimiters();
866                self.output.write_str(open)?;
867                schema.resolve_or_err(value)?.visit(schema, self, context)?;
868                self.output.write_str(close)?;
869            }
870            _ => {
871                return Err(FormatError::InvalidDiscriminant {
872                    type_name: "Option".to_string(),
873                    discriminant,
874                })
875            }
876        }
877
878        Ok(())
879    }
880
881    fn visit_primitive(
882        &mut self,
883        p: crate::schema::Primitive,
884        _schema: &impl TypeResolver<LinkingScheme = L>,
885        context: Context,
886    ) -> Self::ReturnType {
887        match p {
888            Primitive::Float32 => {
889                let value = self.input.advance(4)?;
890                let value = f32::from_le_bytes(value.try_into().unwrap());
891                write!(self.output, "{value}")?;
892                Ok(())
893            }
894            Primitive::Float64 => {
895                let value = self.input.advance(8)?;
896                let value = f64::from_le_bytes(value.try_into().unwrap());
897                write!(self.output, "{value}")?;
898                Ok(())
899            }
900            Primitive::Boolean => {
901                let value = self.input.advance(1)?;
902                match value[0] {
903                    0 => self.output.write_str("false")?,
904                    1 => self.output.write_str("true")?,
905                    _ => {
906                        return Err(FormatError::InvalidDiscriminant {
907                            type_name: "bool".to_string(),
908                            discriminant: value[0],
909                        });
910                    }
911                }
912                Ok(())
913            }
914            Primitive::Integer(int, display) => match int {
915                IntegerType::i8 => display_int!(i8, self.input, display, self.output, context),
916                IntegerType::i16 => display_int!(i16, self.input, display, self.output, context),
917                IntegerType::i32 => display_int!(i32, self.input, display, self.output, context),
918                IntegerType::i64 => display_int!(i64, self.input, display, self.output, context),
919                IntegerType::i128 => display_int!(i128, self.input, display, self.output, context),
920                IntegerType::u8 => display_int!(u8, self.input, display, self.output, context),
921                IntegerType::u16 => display_int!(u16, self.input, display, self.output, context),
922                IntegerType::u32 => display_int!(u32, self.input, display, self.output, context),
923                IntegerType::u64 => display_int!(u64, self.input, display, self.output, context),
924                IntegerType::u128 => display_int!(u128, self.input, display, self.output, context),
925            },
926            Primitive::ByteArray { len, display } => {
927                self.display_byte_sequence(len, display, context)
928            }
929            Primitive::ByteVec { display } => {
930                let len = self
931                    .read_usize_borsh()
932                    .or(Err(FormatError::MissingVecLength))?;
933                self.display_byte_sequence(len, display, context)
934            }
935            Primitive::String => {
936                let len = self
937                    .read_usize_borsh()
938                    .or(Err(FormatError::MissingStringLength))?;
939                let content = self.input.advance(len)?;
940                let content = std::str::from_utf8(content)?;
941                write!(self.output, "\"{content}\"")?;
942                Ok(())
943            }
944            Primitive::Skip { len } => {
945                self.input.advance(len)?;
946                Ok(())
947            }
948        }
949    }
950
951    fn visit_array(
952        &mut self,
953        len: &usize,
954        value: &L::TypeLink,
955        schema: &impl TypeResolver<LinkingScheme = L>,
956        mut context: Context,
957    ) -> Self::ReturnType {
958        let inner = schema.resolve_or_err(value)?;
959        let (open, close) = self.vec_delimiters(&context);
960        self.output.write_str(open)?;
961        context.parent_type = ParentType::Vec;
962        for i in 0..*len {
963            if i > 0 {
964                self.output.write_str(", ")?;
965            }
966            inner.visit(schema, self, context.clone())?;
967        }
968        self.output.write_str(close)?;
969        Ok(())
970    }
971
972    fn visit_vec(
973        &mut self,
974        value: &L::TypeLink,
975        schema: &impl TypeResolver<LinkingScheme = L>,
976        mut context: Context,
977    ) -> Self::ReturnType {
978        let len = self.read_usize_borsh()?;
979        let inner = schema.resolve_or_err(value)?;
980        let (open, close) = self.vec_delimiters(&context);
981        self.output.write_str(open)?;
982        context.parent_type = ParentType::Vec;
983        for i in 0..len {
984            if i > 0 {
985                self.output.write_str(", ")?;
986            }
987            inner.visit(schema, self, context.clone())?;
988        }
989        self.output.write_str(close)?;
990        Ok(())
991    }
992
993    fn visit_map(
994        &mut self,
995        key: &L::TypeLink,
996        value: &L::TypeLink,
997        schema: &impl TypeResolver<LinkingScheme = L>,
998        mut context: Context,
999    ) -> Self::ReturnType {
1000        let len = self.read_usize_borsh()?;
1001        let key = schema.resolve_or_err(key)?;
1002        let value = schema.resolve_or_err(value)?;
1003        let (open, close) = self.map_delimiters(&context);
1004        self.output.write_str(open)?;
1005        context.parent_type = ParentType::Map;
1006        for i in 0..len {
1007            if i > 0 {
1008                self.output.write_str(", ")?;
1009            }
1010            key.visit(schema, self, context.clone())?;
1011            self.output.write_str(": ")?;
1012            value.visit(schema, self, context.clone())?;
1013        }
1014        self.output.write_str(close)?;
1015        Ok(())
1016    }
1017}