use std::collections::BTreeMap;
use std::fmt::Write;
use thiserror::Error;
use crate::schema::Primitive;
use crate::ty::visitor::{ResolutionError, TypeResolver, TypeVisitor};
use crate::ty::{
byte_display, ByteDisplay, Enum, FixedPointDisplay, IntegerDisplay, IntegerType, LinkingScheme,
Struct, Tuple,
};
type Delimiters = (&'static str, &'static str);
pub type Result<T, E = FormatError> = core::result::Result<T, E>;
pub const MAX_INPUT_CHUNK: usize = 65;
#[derive(Debug, Error, Clone)]
pub enum FormatError {
#[error("Core error: {0}")]
Core(#[from] core::fmt::Error),
#[error("The byte sequence could not be formatted for display: {0}")]
InvalidBytes(#[from] byte_display::ByteFormatError),
#[error("The input is not a valid utf-8 string: {0}")]
InvalidString(#[from] core::str::Utf8Error),
#[error("Invalid discriminant `{discriminant}` for {type_name}")]
InvalidDiscriminant { type_name: String, discriminant: u8 },
#[error(transparent)]
UnresolvedType(#[from] ResolutionError),
#[error("A discriminant is required for items of type `{type_name}` but the input ended without providing one.")]
MissingDiscriminant { type_name: String },
#[error("The input claimed to provide an integer {claimed_size} bytes wide, but the maximum allowed size is 16 bytes.")]
IntegerTooLarge { claimed_size: u8 },
#[error("The input claimed to provide an integer {claimed_size} bytes wide, but only provided {bytes_available} additional bytes of input.")]
MissingIntegerInput {
claimed_size: u8,
bytes_available: u8,
},
#[error("The input claimed to provide a byte array {claimed_size} bytes wide, but only provided {bytes_available} additional bytes of input.")]
MissingBytesInput {
claimed_size: usize,
bytes_available: usize,
},
#[error("The input's attributes reference a sibling field with a byte offset, but offset was outside the range of the field's value.")]
FieldReferenceOffsetOutOfBounds(usize),
#[error("Fixed-point integer formatting specified more decimals than is plausible for an integer: {0}")]
TooManyDecimalsForInt(u8),
#[error("The input should have contained a vector but did not provide one.")]
MissingVecLength,
#[error("The input should have contained a string but did not provide one.")]
MissingStringLength,
#[error("The provided input had leftover bytes that weren't displayed.")]
UnusedInput,
#[error(
"A structs's display template did not provide sufficient slots to display its fields."
)]
InsufficientTemplateSlots,
#[error("A struct's display template had more slots than the struct had fields.")]
UnusedTemplateSlots,
}
pub struct Output<'a, W> {
f: &'a mut W,
silent: bool,
peeking: u32,
}
impl<'a, W> Output<'a, W> {
pub fn new(f: &'a mut W) -> Self {
Self {
f,
silent: false,
peeking: 0,
}
}
pub fn start_peek(&mut self) {
self.peeking = self.peeking.checked_add(1).unwrap();
}
pub fn end_peek(&mut self) {
self.peeking = self
.peeking
.checked_sub(1)
.expect("Underflow when ending peek. This is a bug in the schema display logic.")
}
pub fn peeking(&self) -> bool {
self.peeking > 0
}
}
impl<W: core::fmt::Write> core::fmt::Write for Output<'_, W> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.peeking() {
return Ok(());
}
if self.silent {
return Ok(());
}
self.f.write_str(s)
}
}
pub struct Input<'a> {
buf: &'a mut &'a [u8],
peeking: bool,
peek_origins_stack: Vec<usize>,
peek_cursor: usize,
}
impl<'a> Input<'a> {
pub fn new(buf: &'a mut &'a [u8]) -> Self {
Self {
buf,
peeking: false,
peek_origins_stack: Vec::new(),
peek_cursor: 0,
}
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub(crate) fn check_remaining_bytes(&self, mut len: usize) -> Result<(), FormatError> {
if self.peeking {
len += self.peek_cursor;
}
if self.buf.len() < len {
return Err(FormatError::MissingBytesInput {
claimed_size: len,
bytes_available: self.buf.len(),
});
}
Ok(())
}
pub fn advance(&mut self, len: usize) -> Result<&[u8], FormatError> {
self.check_remaining_bytes(len)?;
if self.peeking {
let end_cursor = self.peek_cursor + len;
let slice = &self.buf[self.peek_cursor..end_cursor];
self.peek_cursor = end_cursor;
Ok(slice)
} else {
let (leading, rest) = self.buf.split_at(len);
*self.buf = rest;
Ok(leading)
}
}
pub fn local_peek_bytes(&self, len: usize) -> Result<&[u8], FormatError> {
self.check_remaining_bytes(len)?;
let end = self.peek_cursor + len;
Ok(&self.buf[self.peek_cursor..end])
}
pub fn local_peek_byte(&self, offset: usize) -> Result<u8, FormatError> {
self.check_remaining_bytes(offset)?;
let offset = self.peek_cursor + offset;
Ok(self.buf[offset])
}
pub fn start_peek(&mut self) {
self.peeking = true;
self.peek_origins_stack.push(self.peek_cursor);
}
pub fn peek_cursor(&self) -> usize {
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.")
}
pub fn end_peek(&mut self) -> bool {
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.");
if self.peek_origins_stack.is_empty() {
self.peeking = false;
self.peek_cursor = 0;
true
} else {
false
}
}
}
fn tuple_displays_as_enum_contents(context: &Context) -> bool {
context.parent_type == ParentType::Enum(IsHideTag::No)
|| context.parent_type == ParentType::Tuple(IsVirtual::Yes, IsTrivial::Yes)
}
pub struct DisplayVisitor<'a, 'fmt, W> {
input: Input<'a>,
output: Output<'fmt, W>,
}
impl<'a, 'fmt, W> DisplayVisitor<'a, 'fmt, W> {
pub fn new(input: &'a mut &'a [u8], f: &'fmt mut W) -> Self {
Self {
input: Input::new(input),
output: Output::new(f),
}
}
pub fn has_displayed_whole_input(&self) -> bool {
self.input.is_empty()
}
fn start_peek(&mut self) {
self.output.start_peek();
self.input.start_peek();
}
fn end_peek(&mut self) -> bool {
self.output.end_peek();
self.input.end_peek()
}
fn tuple_delimiters<L: LinkingScheme>(
&self,
tuple: &Tuple<L>,
context: &Context,
schema: &impl TypeResolver<LinkingScheme = L>,
) -> Delimiters {
let first_child_is_primitive = schema
.resolve_or_err(&tuple.fields[0].value)
.is_ok_and(|v| v.is_primitive());
if tuple.fields.len() == 1
&& !(tuple_displays_as_enum_contents(context) && first_child_is_primitive)
{
("", "")
} else {
("(", ")")
}
}
fn enum_delimiters<L: LinkingScheme>(&mut self, _e: &Enum<L>, context: &Context) -> Delimiters {
match context.parent_type {
ParentType::Tuple(_, IsTrivial::Yes)
| ParentType::Enum(_)
| ParentType::Vec
| ParentType::Map => (".", ""),
ParentType::Tuple(_, _) | ParentType::Struct(_) | ParentType::None => ("", ""),
}
}
fn struct_delimiters<L: LinkingScheme>(&self, s: &Struct<L>, context: &Context) -> Delimiters {
if s.fields.is_empty() {
return ("", "");
}
match context.parent_type {
ParentType::Tuple(IsVirtual::Yes, _) => (" { ", " }"),
ParentType::Struct(_)
| ParentType::None
| ParentType::Tuple(_, _)
| ParentType::Vec
| ParentType::Map => ("{ ", " }"),
ParentType::Enum(_) => (" { ", " }"),
}
}
fn struct_default_template<L: LinkingScheme>(
&self,
s: &Struct<L>,
context: &Context,
schema: &impl TypeResolver<LinkingScheme = L>,
) -> String {
let mut template = String::new();
let (opener, closer) = self.struct_delimiters(s, context);
template.push_str(opener);
if s.fields.is_empty() {
template.push_str(&s.type_name);
} else {
for (i, field) in s.fields.iter().enumerate() {
if field.silent {
continue;
}
if schema
.resolve_or_err(&field.value)
.is_ok_and(|inner| inner.is_skip())
{
continue;
}
if i > 0 {
template.push_str(self.item_separator());
}
template.push_str(&field.display_name);
template.push_str(": {}");
}
}
template.push_str(closer);
template
}
fn tuple_default_template<L: LinkingScheme>(
&self,
t: &Tuple<L>,
context: &Context,
schema: &impl TypeResolver<LinkingScheme = L>,
) -> String {
let mut template = String::new();
let (opener, closer) = self.tuple_delimiters(t, context, schema);
template.push_str(opener);
for (i, field) in t.fields.iter().enumerate() {
if field.silent {
continue;
}
if i > 0 {
template.push_str(self.item_separator());
}
template.push_str("{}");
}
template.push_str(closer);
template
}
fn option_none_delimiters(&self) -> Delimiters {
("None", "")
}
fn option_some_delimiters(&self) -> Delimiters {
("", "")
}
fn map_delimiters(&mut self, context: &Context) -> Delimiters {
match context.parent_type {
ParentType::Tuple(IsVirtual::Yes, _) => (" { ", " }"),
ParentType::Struct(_)
| ParentType::None
| ParentType::Tuple(_, _)
| ParentType::Vec
| ParentType::Enum(_)
| ParentType::Map => ("{ ", " }"),
}
}
fn vec_delimiters(&mut self, context: &Context) -> Delimiters {
match context.parent_type {
ParentType::Tuple(IsVirtual::Yes, _) => (" [", "]"),
ParentType::None
| ParentType::Struct(_)
| ParentType::Tuple(_, _)
| ParentType::Vec
| ParentType::Enum(_)
| ParentType::Map => ("[", "]"),
}
}
fn item_separator(&self) -> &'static str {
", "
}
}
impl<W: Write> DisplayVisitor<'_, '_, W> {
pub fn read_usize_borsh(&mut self) -> Result<usize, FormatError> {
if self.input.len() < 4 {
return Err(FormatError::MissingIntegerInput {
claimed_size: 4,
bytes_available: self.input.len() as u8,
});
}
let len = u32::from_le_bytes(
self.input
.advance(4)?
.try_into()
.expect("Converting [u8;4] to u32 is infallible"),
) as usize;
Ok(len)
}
pub fn display_byte_sequence(
&mut self,
len: usize,
display: ByteDisplay,
_context: Context,
) -> Result<(), FormatError> {
self.input.check_remaining_bytes(len)?;
if len > MAX_INPUT_CHUNK {
display.format(self.input.advance(MAX_INPUT_CHUNK)?, &mut self.output)?;
self.output.write_fmt(format_args!(
" (trailing {} bytes truncated)",
len - MAX_INPUT_CHUNK
))?;
self.input.advance(len - MAX_INPUT_CHUNK)?;
} else {
display.format(self.input.advance(len)?, &mut self.output)?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Context {
parent_type: ParentType,
is_peek_pass: bool,
peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsVirtual {
Yes,
No,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsTrivial {
Yes,
No,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IsHideTag {
Yes,
No,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParentType {
None,
Struct(IsVirtual),
Tuple(IsVirtual, IsTrivial),
Enum(IsHideTag),
Vec,
Map,
}
impl Default for Context {
fn default() -> Self {
Self {
parent_type: ParentType::None,
is_peek_pass: false,
peek_bytes: BTreeMap::new(),
}
}
}
fn format_fixed_point(number_str: String, decimals: u8) -> String {
let (number_str, is_negative) = match number_str.strip_prefix('-') {
Some(str) => (str.to_string(), true),
None => (number_str, false),
};
let mut number_str = format!("{:0>pad$}", number_str, pad = (decimals + 1) as usize);
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");
number_str.insert(decimal_idx, '.');
let mut number_str = number_str
.trim_end_matches('0')
.trim_end_matches('.')
.to_string();
if is_negative {
number_str.insert(0, '-');
}
number_str
}
macro_rules! display_int {
($t:ident, $input:expr, $disp:expr, $f:expr, $ctx:expr) => {{
let size = IntegerType::$t.size();
if $input.len() < size {
return Err(FormatError::MissingIntegerInput {
claimed_size: size as u8,
bytes_available: $input.len() as u8,
});
}
let buf = $input.advance(size)?;
match $disp {
IntegerDisplay::Hex => {
write!($f, "{:#x}", <$t>::from_le_bytes(buf.try_into().unwrap()))?
}
IntegerDisplay::Decimal => {
write!($f, "{}", <$t>::from_le_bytes(buf.try_into().unwrap()))?
}
IntegerDisplay::FixedPoint(decimal_spec) => {
let decimals = match decimal_spec {
FixedPointDisplay::Decimals(d) => d,
FixedPointDisplay::FromSiblingField {
field_index,
byte_offset,
} => {
if $ctx.is_peek_pass {
0
} else {
*$ctx
.peek_bytes
.get(&field_index)
.expect("Fixed point display attempted to get field that was not provided in context - this is a bug in the schema display implementation")
.get(&byte_offset)
.expect("Fixed point display attempted to get byte that was not provided in context - this is a bug in the schema display implementation")
}
}
};
if decimals > 39 {
return Err(FormatError::TooManyDecimalsForInt (decimals));
}
let t = <$t>::from_le_bytes(buf.try_into().unwrap());
write!($f, "{}", format_fixed_point(t.to_string(), decimals))?
}
}
Ok(())
}};
}
impl<W: Write, L: LinkingScheme, M> TypeVisitor<L, M> for DisplayVisitor<'_, '_, W> {
type Arg = Context;
type ReturnType = Result<(), FormatError>;
fn visit_enum(
&mut self,
e: &Enum<L>,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
if self.input.is_empty() && !e.variants.is_empty() {
return Err(FormatError::MissingDiscriminant {
type_name: e.type_name.clone(),
});
}
if matches!(context.parent_type, ParentType::Tuple(IsVirtual::No, _))
|| context.parent_type == ParentType::Vec
{
self.output.write_str(&e.type_name)?;
}
let (open, close) = self.enum_delimiters(e, &context);
self.output.write_str(open)?;
let discriminant = self.input.advance(1)?[0];
let mut variants_by_discriminant =
e.variants.iter().filter(|v| v.discriminant == discriminant);
let variant = variants_by_discriminant
.next()
.ok_or(FormatError::InvalidDiscriminant {
type_name: e.type_name.clone(),
discriminant,
})?;
assert!(variants_by_discriminant.next().is_none(), "Found two enum variants with the same discriminant - the schema is malformed, cannot proceed!");
if variant.template.is_none() && !e.hide_tag {
write!(self.output, "{}", variant.name)?;
}
let is_hide_tag = if e.hide_tag {
IsHideTag::Yes
} else {
IsHideTag::No
};
context.parent_type = ParentType::Enum(is_hide_tag);
if let Some(maybe_resolved) = &variant.value {
let inner = schema.resolve_or_err(maybe_resolved)?;
inner.visit(schema, self, context)?;
}
self.output.write_str(close)?;
Ok(())
}
fn visit_struct(
&mut self,
s: &Struct<L>,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
let template = s
.template
.clone()
.unwrap_or_else(|| self.struct_default_template(s, &context, schema));
let mut template = template.as_str();
context.parent_type = ParentType::Struct(IsVirtual::No);
if s.peekable && !context.is_peek_pass {
let mut field_offsets: Vec<usize> = Vec::new();
let mut bytes_needed: Vec<(usize, usize)> = Vec::new();
self.start_peek();
context.is_peek_pass = true;
for field in s.fields.iter() {
field_offsets.push(self.input.peek_cursor());
let inner_ty = schema.resolve_or_err(&field.value)?;
inner_ty.visit(schema, self, context.clone())?;
bytes_needed.extend(inner_ty.parent_byte_references());
}
field_offsets.push(self.input.peek_cursor());
let still_peeking = !self.end_peek();
context.is_peek_pass = still_peeking;
let mut peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>> = BTreeMap::new();
for (field, offset) in bytes_needed {
let field_end = *field_offsets
.get(field + 1)
.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.");
let byte_offset = field_offsets.get(field).unwrap() + offset;
if byte_offset > field_end {
return Err(FormatError::FieldReferenceOffsetOutOfBounds(byte_offset));
}
let byte = self.input.local_peek_byte(byte_offset)?;
let field_bytes = peek_bytes.entry(field).or_default();
field_bytes.insert(offset, byte);
}
context.peek_bytes = peek_bytes;
}
for field in &s.fields {
let was_silent = self.output.silent;
if field.silent {
self.output.silent = true;
}
let inner_ty = schema.resolve_or_err(&field.value)?;
if !field.silent && !inner_ty.is_skip() {
let Some((before_next_field, rest)) = template.split_once("{}") else {
return Err(FormatError::InsufficientTemplateSlots);
};
self.output.write_str(before_next_field)?;
template = rest;
}
inner_ty.visit(schema, self, context.clone())?;
self.output.silent = was_silent;
}
if template.contains("{}") {
return Err(FormatError::UnusedTemplateSlots);
}
self.output.write_str(template)?;
Ok(())
}
fn visit_tuple(
&mut self,
t: &Tuple<L>,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
let template = t
.template
.clone()
.unwrap_or_else(|| self.tuple_default_template(t, &context, schema));
let mut template = template.as_str();
let is_virtual = if tuple_displays_as_enum_contents(&context) {
IsVirtual::Yes
} else {
IsVirtual::No
};
let trivial = if t.fields.len() == 1 {
IsTrivial::Yes
} else {
IsTrivial::No
};
context.parent_type = ParentType::Tuple(is_virtual, trivial);
if t.peekable && !context.is_peek_pass {
let mut field_offsets: Vec<usize> = Vec::new();
let mut bytes_needed: Vec<(usize, usize)> = Vec::new();
self.start_peek();
context.is_peek_pass = true;
for field in t.fields.iter() {
field_offsets.push(self.input.peek_cursor());
let inner_ty = schema.resolve_or_err(&field.value)?;
inner_ty.visit(schema, self, context.clone())?;
bytes_needed.extend(inner_ty.parent_byte_references());
}
field_offsets.push(self.input.peek_cursor());
let still_peeking = !self.end_peek();
context.is_peek_pass = still_peeking;
let mut peek_bytes: BTreeMap<usize, BTreeMap<usize, u8>> = BTreeMap::new();
for (field, offset) in bytes_needed {
let field_end = *field_offsets
.get(field + 1)
.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.");
let byte_offset = field_offsets.get(field).unwrap() + offset;
if byte_offset > field_end {
return Err(FormatError::FieldReferenceOffsetOutOfBounds(byte_offset));
}
let byte = self.input.local_peek_byte(byte_offset)?;
let field_bytes = peek_bytes.entry(field).or_default();
field_bytes.insert(offset, byte);
}
context.peek_bytes = peek_bytes;
}
for field in &t.fields {
let was_silent = self.output.silent;
if field.silent {
self.output.silent = true;
}
if !field.silent {
let Some((before_next_field, rest)) = template.split_once("{}") else {
return Err(FormatError::InsufficientTemplateSlots);
};
self.output.write_str(before_next_field)?;
template = rest;
}
schema
.resolve_or_err(&field.value)?
.visit(schema, self, context.clone())?;
self.output.silent = was_silent;
}
if template.contains("{}") {
return Err(FormatError::UnusedTemplateSlots);
}
self.output.write_str(template)?;
Ok(())
}
fn visit_option(
&mut self,
value: &L::TypeLink,
schema: &impl TypeResolver<LinkingScheme = L>,
context: Self::Arg,
) -> Self::ReturnType {
let discriminant = self.input.advance(1)?[0];
match discriminant {
0 => {
let (open, close) = self.option_none_delimiters();
self.output.write_str(open)?;
self.output.write_str(close)?;
}
1 => {
let (open, close) = self.option_some_delimiters();
self.output.write_str(open)?;
schema.resolve_or_err(value)?.visit(schema, self, context)?;
self.output.write_str(close)?;
}
_ => {
return Err(FormatError::InvalidDiscriminant {
type_name: "Option".to_string(),
discriminant,
})
}
}
Ok(())
}
fn visit_primitive(
&mut self,
p: crate::schema::Primitive,
_schema: &impl TypeResolver<LinkingScheme = L>,
context: Context,
) -> Self::ReturnType {
match p {
Primitive::Float32 => {
let value = self.input.advance(4)?;
let value = f32::from_le_bytes(value.try_into().unwrap());
write!(self.output, "{value}")?;
Ok(())
}
Primitive::Float64 => {
let value = self.input.advance(8)?;
let value = f64::from_le_bytes(value.try_into().unwrap());
write!(self.output, "{value}")?;
Ok(())
}
Primitive::Boolean => {
let value = self.input.advance(1)?;
match value[0] {
0 => self.output.write_str("false")?,
1 => self.output.write_str("true")?,
_ => {
return Err(FormatError::InvalidDiscriminant {
type_name: "bool".to_string(),
discriminant: value[0],
});
}
}
Ok(())
}
Primitive::Integer(int, display) => match int {
IntegerType::i8 => display_int!(i8, self.input, display, self.output, context),
IntegerType::i16 => display_int!(i16, self.input, display, self.output, context),
IntegerType::i32 => display_int!(i32, self.input, display, self.output, context),
IntegerType::i64 => display_int!(i64, self.input, display, self.output, context),
IntegerType::i128 => display_int!(i128, self.input, display, self.output, context),
IntegerType::u8 => display_int!(u8, self.input, display, self.output, context),
IntegerType::u16 => display_int!(u16, self.input, display, self.output, context),
IntegerType::u32 => display_int!(u32, self.input, display, self.output, context),
IntegerType::u64 => display_int!(u64, self.input, display, self.output, context),
IntegerType::u128 => display_int!(u128, self.input, display, self.output, context),
},
Primitive::ByteArray { len, display } => {
self.display_byte_sequence(len, display, context)
}
Primitive::ByteVec { display } => {
let len = self
.read_usize_borsh()
.or(Err(FormatError::MissingVecLength))?;
self.display_byte_sequence(len, display, context)
}
Primitive::String => {
let len = self
.read_usize_borsh()
.or(Err(FormatError::MissingStringLength))?;
let content = self.input.advance(len)?;
let content = std::str::from_utf8(content)?;
write!(self.output, "\"{content}\"")?;
Ok(())
}
Primitive::Skip { len } => {
self.input.advance(len)?;
Ok(())
}
}
}
fn visit_array(
&mut self,
len: &usize,
value: &L::TypeLink,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
let inner = schema.resolve_or_err(value)?;
let (open, close) = self.vec_delimiters(&context);
self.output.write_str(open)?;
context.parent_type = ParentType::Vec;
for i in 0..*len {
if i > 0 {
self.output.write_str(", ")?;
}
inner.visit(schema, self, context.clone())?;
}
self.output.write_str(close)?;
Ok(())
}
fn visit_vec(
&mut self,
value: &L::TypeLink,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
let len = self.read_usize_borsh()?;
let inner = schema.resolve_or_err(value)?;
let (open, close) = self.vec_delimiters(&context);
self.output.write_str(open)?;
context.parent_type = ParentType::Vec;
for i in 0..len {
if i > 0 {
self.output.write_str(", ")?;
}
inner.visit(schema, self, context.clone())?;
}
self.output.write_str(close)?;
Ok(())
}
fn visit_map(
&mut self,
key: &L::TypeLink,
value: &L::TypeLink,
schema: &impl TypeResolver<LinkingScheme = L>,
mut context: Context,
) -> Self::ReturnType {
let len = self.read_usize_borsh()?;
let key = schema.resolve_or_err(key)?;
let value = schema.resolve_or_err(value)?;
let (open, close) = self.map_delimiters(&context);
self.output.write_str(open)?;
context.parent_type = ParentType::Map;
for i in 0..len {
if i > 0 {
self.output.write_str(", ")?;
}
key.visit(schema, self, context.clone())?;
self.output.write_str(": ")?;
value.visit(schema, self, context.clone())?;
}
self.output.write_str(close)?;
Ok(())
}
}