From 7ef9dd9ab2a3302ab1a0e9e1608447964b520259 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 17 Jul 2024 17:29:01 +0100 Subject: [PATCH 1/5] WIP Tracing visitor and improving the Value display impl --- src/lib.rs | 27 ++ src/scale_impls/mod.rs | 2 + src/scale_impls/tracing_decoder/error.rs | 144 +++++++++ src/scale_impls/tracing_decoder/mod.rs | 6 + src/scale_impls/tracing_decoder/path.rs | 45 +++ src/scale_impls/tracing_decoder/visitor.rs | 357 +++++++++++++++++++++ src/string_impls/string_helpers.rs | 79 +++++ src/string_impls/to_string.rs | 202 ++++++++---- 8 files changed, 804 insertions(+), 58 deletions(-) create mode 100644 src/scale_impls/tracing_decoder/error.rs create mode 100644 src/scale_impls/tracing_decoder/mod.rs create mode 100644 src/scale_impls/tracing_decoder/path.rs create mode 100644 src/scale_impls/tracing_decoder/visitor.rs diff --git a/src/lib.rs b/src/lib.rs index 51a1470..57d3e6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,6 +241,33 @@ pub mod scale { ) -> Result<(), EncodeError> { value.encode_as_type_to(ty_id, types, buf) } + + /// A visitor and function to decode some bytes into a [`crate::Value`] while tracing the current + /// decoding state so that a more detailed error can be returned in the event of a failure. + pub mod tracing { + pub use crate::scale_impls::{TraceDecodingError, TraceDecodingVisitor}; + + /// Decode a value using the [`TraceDecodingVisitor`], which internally keeps track of the current decoding state, and as + /// a result hands back a much more detailed error than [`crate::scale::decode_as_type()`] if decoding fails. + /// + /// One approach is to use the standard visitor for decoding on the "happy path", and if you need more information about + /// the decode error, to try decoding the same bytes again using this function to obtain more information about what failed. + pub fn decode_as_type( + data: &mut &[u8], + ty_id: R::TypeId, + types: &R, + ) -> Result, TraceDecodingError>> + where + R: scale_type_resolver::TypeResolver, + { + scale_decode::visitor::decode_with_visitor( + data, + ty_id, + types, + TraceDecodingVisitor::new(), + ) + } + } } /// Converting a [`crate::Value`] to or from strings. diff --git a/src/scale_impls/mod.rs b/src/scale_impls/mod.rs index 8f5147b..5e22785 100644 --- a/src/scale_impls/mod.rs +++ b/src/scale_impls/mod.rs @@ -15,5 +15,7 @@ mod decode; mod encode; +mod tracing_decoder; pub use decode::{decode_composite_as_fields, decode_value_as_type, DecodeError}; +pub use tracing_decoder::{TraceDecodingError, TraceDecodingVisitor}; diff --git a/src/scale_impls/tracing_decoder/error.rs b/src/scale_impls/tracing_decoder/error.rs new file mode 100644 index 0000000..3a8dd85 --- /dev/null +++ b/src/scale_impls/tracing_decoder/error.rs @@ -0,0 +1,144 @@ +use super::path::Path; +use crate::scale::DecodeError; +use crate::{Composite, Value, ValueDef}; + +/// An error encountered when decoding some bytes using the [`crate::scale::tracing`] module. +#[derive(Clone, Debug)] +pub struct TraceDecodingError { + inner: TraceDecodingErrorInner, +} + +impl TraceDecodingError { + pub(crate) fn map_decoded_so_far( + self, + f: impl FnOnce(Val) -> NewVal, + ) -> TraceDecodingError { + match self.inner { + TraceDecodingErrorInner::FromDecodeError(e) => { + TraceDecodingErrorInner::FromDecodeError(e).into() + } + TraceDecodingErrorInner::FromVisitor(e) => { + TraceDecodingErrorInner::FromVisitor(VisitorError { + at: e.at, + decode_error: e.decode_error, + decoded_so_far: f(e.decoded_so_far), + }) + .into() + } + } + } + pub(crate) fn with_outer_context( + self, + outer_path: impl FnOnce() -> Path, + default_outer_value: impl FnOnce() -> NewVal, + into_outer_value: impl FnOnce(Val) -> NewVal, + ) -> TraceDecodingError { + match self.inner { + TraceDecodingErrorInner::FromDecodeError(e) => { + TraceDecodingErrorInner::FromVisitor(VisitorError { + at: outer_path(), + decoded_so_far: default_outer_value(), + decode_error: e, + }) + .into() + } + TraceDecodingErrorInner::FromVisitor(e) => { + TraceDecodingErrorInner::FromVisitor(VisitorError { + at: e.at, + decoded_so_far: into_outer_value(e.decoded_so_far), + decode_error: e.decode_error, + }) + .into() + } + } + } +} + +impl From> for TraceDecodingError { + fn from(value: TraceDecodingErrorInner) -> Self { + TraceDecodingError { inner: value } + } +} + +#[derive(Clone, Debug)] +enum TraceDecodingErrorInner { + FromDecodeError(DecodeError), + FromVisitor(VisitorError), +} + +#[derive(Clone, Debug)] +struct VisitorError { + at: Path, + decoded_so_far: Val, + decode_error: DecodeError, +} + +impl core::fmt::Display for TraceDecodingError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match &self.inner { + TraceDecodingErrorInner::FromDecodeError(e) => { + write!(f, "Error decoding value: {e}") + } + TraceDecodingErrorInner::FromVisitor(e) => { + write!( + f, + "Error decoding value at {}: {}\nDecoded so far: {}", + e.at, e.decode_error, e.decoded_so_far + ) + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TraceDecodingError {} + +impl From for TraceDecodingError { + fn from(value: DecodeError) -> Self { + TraceDecodingErrorInner::FromDecodeError(value).into() + } +} + +impl From for TraceDecodingError { + fn from(value: codec::Error) -> Self { + TraceDecodingErrorInner::FromDecodeError(value.into()).into() + } +} + +// fn display_value_with_typeid(f: &mut core::fmt::Formatter<'_>, value: &Value) -> core::fmt::Result { +// let type_id = &value.context; +// match &value.value { +// ValueDef::Variant(var) => { +// write!(f, "{} [{type_id}] ", var.name)?; +// display_composite_with_typeid(f, &var.values)?; +// } +// ValueDef::Composite(_) => { + +// } +// ValueDef::BitSequence(_) => { + +// } +// ValueDef::Primitive(_) => { + +// } +// } +// Ok(()) +// } + +// fn display_composite_with_typeid(f: &mut core::fmt::Formatter<'_>, value: &Composite) -> core::fmt::Result { +// match value { +// Composite::Named(named) => { +// write!(f, "{{\n"); +// for (key, val) in named { +// write!(f, " {key}: ")?; +// display_value_with_typeid(f, val)?; +// write!("\n") +// } +// }, +// Composite::Unnamed(unnamed) => { + +// } +// } + +// Ok(()) +// } diff --git a/src/scale_impls/tracing_decoder/mod.rs b/src/scale_impls/tracing_decoder/mod.rs new file mode 100644 index 0000000..750a2f0 --- /dev/null +++ b/src/scale_impls/tracing_decoder/mod.rs @@ -0,0 +1,6 @@ +mod error; +mod path; +mod visitor; + +pub use error::TraceDecodingError; +pub use visitor::TraceDecodingVisitor; diff --git a/src/scale_impls/tracing_decoder/path.rs b/src/scale_impls/tracing_decoder/path.rs new file mode 100644 index 0000000..5394dcf --- /dev/null +++ b/src/scale_impls/tracing_decoder/path.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; + +#[derive(Clone, Debug)] +pub struct Path(Vec); + +impl Path { + pub fn new() -> Path { + Path(vec![]) + } + pub fn at(&self, segment: PathSegment) -> Path { + let mut p = self.0.clone(); + p.push(segment); + Path(p) + } + pub fn at_idx(&self, idx: usize) -> Path { + self.at(PathSegment::Index(idx)) + } + pub fn at_field(&self, field: String) -> Path { + self.at(PathSegment::Field(field)) + } + pub fn at_variant(&self, variant: String) -> Path { + self.at(PathSegment::Variant(variant)) + } +} + +impl core::fmt::Display for Path { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for segment in &self.0 { + write!(f, ".")?; + match segment { + PathSegment::Index(idx) => write!(f, "[{idx}]")?, + PathSegment::Field(field) => write!(f, "{field}")?, + PathSegment::Variant(variant) => write!(f, "{variant}")?, + } + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum PathSegment { + Field(String), + Index(usize), + Variant(String), +} diff --git a/src/scale_impls/tracing_decoder/visitor.rs b/src/scale_impls/tracing_decoder/visitor.rs new file mode 100644 index 0000000..25dd758 --- /dev/null +++ b/src/scale_impls/tracing_decoder/visitor.rs @@ -0,0 +1,357 @@ +use crate::prelude::*; + +use super::error::TraceDecodingError; +use super::path::{Path, PathSegment}; +use crate::{Composite, Primitive, Value, ValueDef, Variant}; +use core::marker::PhantomData; +use scale_decode::visitor::TypeIdFor; +use scale_type_resolver::TypeResolver; + +/// A visitor that will attempt to decode some bytes into a [`crate::Value`], +/// returning a detailed error of where the decoding fails if it does. +pub struct TraceDecodingVisitor { + path: Path, + marker: PhantomData, +} + +impl TraceDecodingVisitor { + fn at(&self, segment: PathSegment) -> Self { + TraceDecodingVisitor { path: self.path.at(segment), marker: PhantomData } + } + fn at_idx(&self, idx: usize) -> Self { + self.at(PathSegment::Index(idx)) + } + fn at_field(&self, field: String) -> Self { + self.at(PathSegment::Field(field)) + } +} + +impl TraceDecodingVisitor { + /// Construct a new [`TraceDecodingVisitor`]. + pub fn new() -> Self { + TraceDecodingVisitor { path: Path::new(), marker: PhantomData } + } +} + +macro_rules! to_unnamed_composite { + ($self:ident, $value:ident) => {{ + let mut f = move || { + let mut idx = 0; + let mut vals = Vec::with_capacity($value.remaining()); + + while let Some(val) = $value.decode_item($self.at_idx(idx)) { + match val { + Err(e) => { + let merged_error = e.with_outer_context( + || $self.path.at_idx(idx), + || Composite::Unnamed(vals.clone()), + |inner_value| { + let mut vals = vals.clone(); + vals.push(inner_value); + Composite::Unnamed(vals) + }, + ); + return Err(merged_error); + } + Ok(v) => { + vals.push(v); + } + } + + idx += 1; + } + + Ok::<_, TraceDecodingError<_>>(Composite::Unnamed(vals)) + }; + + f() + }}; +} + +macro_rules! to_unnamed_composite_value { + ($self:ident, $value:ident, $type_id:ident) => {{ + let composite = to_unnamed_composite!($self, $value).map_err(|e| { + e.map_decoded_so_far(|c| Value { + value: ValueDef::Composite(c), + context: $type_id.clone(), + }) + })?; + + Ok(Value { value: ValueDef::Composite(composite), context: $type_id }) + }}; +} + +impl scale_decode::Visitor for TraceDecodingVisitor +where + Resolver: TypeResolver, +{ + type Value<'scale, 'resolver> = Value>; + type Error = TraceDecodingError>>; + type TypeResolver = Resolver; + + fn visit_bool<'scale, 'resolver>( + self, + value: bool, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::Bool(value)), type_id)) + } + + fn visit_char<'scale, 'resolver>( + self, + value: char, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::Char(value)), type_id)) + } + + fn visit_u8<'scale, 'resolver>( + self, + value: u8, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u128(value as u128, type_id) + } + + fn visit_u16<'scale, 'resolver>( + self, + value: u16, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u128(value as u128, type_id) + } + + fn visit_u32<'scale, 'resolver>( + self, + value: u32, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u128(value as u128, type_id) + } + + fn visit_u64<'scale, 'resolver>( + self, + value: u64, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_u128(value as u128, type_id) + } + + fn visit_u128<'scale, 'resolver>( + self, + value: u128, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::U128(value)), type_id)) + } + + fn visit_u256<'info>( + self, + value: &[u8; 32], + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::U256(*value)), type_id)) + } + + fn visit_i8<'scale, 'resolver>( + self, + value: i8, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_i128(value as i128, type_id) + } + + fn visit_i16<'scale, 'resolver>( + self, + value: i16, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_i128(value as i128, type_id) + } + + fn visit_i32<'scale, 'resolver>( + self, + value: i32, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_i128(value as i128, type_id) + } + + fn visit_i64<'scale, 'resolver>( + self, + value: i64, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + self.visit_i128(value as i128, type_id) + } + + fn visit_i128<'scale, 'resolver>( + self, + value: i128, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::I128(value)), type_id)) + } + + fn visit_i256<'info>( + self, + value: &[u8; 32], + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context(ValueDef::Primitive(Primitive::I256(*value)), type_id)) + } + + fn visit_bitsequence<'scale, 'info>( + self, + value: &mut scale_decode::visitor::types::BitSequence<'scale>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + let bits: Result<_, _> = value.decode()?.collect(); + Ok(Value::with_context(ValueDef::BitSequence(bits?), type_id)) + } + + fn visit_str<'scale, 'info>( + self, + value: &mut scale_decode::visitor::types::Str<'scale>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + Ok(Value::with_context( + ValueDef::Primitive(Primitive::String(value.as_str()?.to_owned())), + type_id, + )) + } + + fn visit_sequence<'scale, 'resolver>( + self, + value: &mut scale_decode::visitor::types::Sequence<'scale, 'resolver, Self::TypeResolver>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + to_unnamed_composite_value!(self, value, type_id) + } + + fn visit_array<'scale, 'resolver>( + self, + value: &mut scale_decode::visitor::types::Array<'scale, 'resolver, Self::TypeResolver>, + type_id: scale_decode::visitor::TypeIdFor, + ) -> Result, Self::Error> { + to_unnamed_composite_value!(self, value, type_id) + } + + fn visit_tuple<'scale, 'resolver>( + self, + value: &mut scale_decode::visitor::types::Tuple<'scale, 'resolver, Self::TypeResolver>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + to_unnamed_composite_value!(self, value, type_id) + } + + fn visit_variant<'scale, 'resolver>( + self, + value: &mut scale_decode::visitor::types::Variant<'scale, 'resolver, Self::TypeResolver>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + let variant_name = value.name(); + let path = self.path.at_variant(variant_name.to_owned()); + let values = visit_composite(&self, value.fields()); + match values { + Err(e) => { + let merged_error = e.with_outer_context( + || path.clone(), + || Value { + value: ValueDef::Variant(Variant { + name: variant_name.to_owned(), + values: Composite::Unnamed(vec![]), + }), + context: type_id.clone(), + }, + |inner_value| Value { + value: ValueDef::Variant(Variant { + name: variant_name.to_owned(), + values: inner_value, + }), + context: type_id.clone(), + }, + ); + Err(merged_error) + } + Ok(values) => Ok(Value { + value: ValueDef::Variant(Variant { name: variant_name.to_owned(), values: values }), + context: type_id, + }), + } + } + + fn visit_composite<'scale, 'resolver>( + self, + value: &mut scale_decode::visitor::types::Composite<'scale, 'resolver, Self::TypeResolver>, + type_id: TypeIdFor, + ) -> Result, Self::Error> { + let composite_vals = visit_composite(&self, value).map_err(|e| { + e.map_decoded_so_far(|c| Value { + value: ValueDef::Composite(c), + context: type_id.clone(), + }) + })?; + + Ok(Value { value: ValueDef::Composite(composite_vals), context: type_id }) + } +} + +fn visit_composite( + this: &TraceDecodingVisitor, + value: &mut scale_decode::visitor::types::Composite<'_, '_, R>, +) -> Result, TraceDecodingError>> +where + R: TypeResolver, +{ + let len = value.remaining(); + + // if no fields, we'll always assume unnamed. + let named = len > 0 && !value.has_unnamed_fields(); + + // if unnamed, treat like array/tuple/sequence. + if !named { + return to_unnamed_composite!(this, value); + } + + // otherwise, treat as a named struct. + let mut vals = Vec::with_capacity(len); + let mut name = value.peek_name().unwrap_or(""); + + while let Some(val) = value.decode_item(this.at_field(name.to_owned())) { + match val { + Err(e) => { + let merged_error = e.with_outer_context( + || this.path.at_field(name.to_owned()), + || Composite::Named(vals.clone()), + |inner_value| { + let mut vals = vals.clone(); + vals.push((name.to_owned(), inner_value)); + Composite::Named(vals) + }, + ); + return Err(merged_error); + } + Ok(v) => { + vals.push((name.to_owned(), v)); + } + } + + name = value.peek_name().unwrap_or(""); + } + + Ok(Composite::Named(vals)) +} + +/* +Error decoding value at .[1].[0].proof.[69]: Not enough data to fill buffer. +Decoded so far: (Staking (bond { controller: (2... + +The type so far + +Staking [StakingCall] ( + bond [BondStaking] { + controller [AccountId]: + } +) +*/ diff --git a/src/string_impls/string_helpers.rs b/src/string_impls/string_helpers.rs index 1569ea1..5797d25 100644 --- a/src/string_impls/string_helpers.rs +++ b/src/string_impls/string_helpers.rs @@ -48,3 +48,82 @@ pub fn from_escape_code(c: char) -> Option { }; Some(unescaped) } + +/// We write a Value to some writer via this type, which controls +/// the level of indentation and whether to format in a compact or +/// pretty style. +pub(crate) struct Formatter { + writer: W, + style: FormatStyle, + print_context: Option core::fmt::Result>>, +} + +impl Formatter { + pub(crate) fn compact(writer: W) -> Self { + Formatter { writer, style: FormatStyle::Compact, print_context: None } + } + pub(crate) fn spaced(writer: W) -> Self { + Formatter { writer, style: FormatStyle::Indented(0), print_context: None } + } + pub(crate) fn context core::fmt::Result + 'static>(mut self, f: F) -> Self { + self.print_context = Some(Box::new(f)); + self + } + pub(crate) fn indent_by(&mut self, indent: usize) { + self.style = match &self.style { + FormatStyle::Compact => FormatStyle::Compact, + FormatStyle::Indented(n) => FormatStyle::Indented(n + indent), + }; + } + pub(crate) fn unindent_by(&mut self, indent: usize) { + self.style = match &self.style { + FormatStyle::Compact => FormatStyle::Compact, + FormatStyle::Indented(n) => FormatStyle::Indented(n.saturating_sub(indent)), + }; + } + pub(crate) fn newline_or_nothing(&mut self) -> core::fmt::Result { + match self.style { + FormatStyle::Compact => Ok(()), + FormatStyle::Indented(n) => write_newline(&mut self.writer, n), + } + } + pub(crate) fn newline_or_space(&mut self) -> core::fmt::Result { + match self.style { + FormatStyle::Compact => self.writer.write_char(' '), + FormatStyle::Indented(n) => write_newline(&mut self.writer, n), + } + } + pub(crate) fn should_print_context(&self) -> bool { + self.print_context.is_some() + } + pub(crate) fn print_context(&mut self, ctx: &T) -> core::fmt::Result { + if let Some(f) = &self.print_context { + f(ctx, &mut self.writer) + } else { + Ok(()) + } + } +} + +impl core::fmt::Write for Formatter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + // For now, we don't apply any formatting to this, and expect + // things to manually call `newline` etc to have formatting. + self.writer.write_str(s) + } +} + +/// this defines whether the above formatter will write "newlines" in a +/// compact style or more spaced out with indentation. +enum FormatStyle { + Indented(usize), + Compact, +} + +pub fn write_newline(writer: &mut impl core::fmt::Write, indent: usize) -> core::fmt::Result { + writer.write_char('\n')?; + for _ in 0..indent { + writer.write_char(' ')?; + } + Ok(()) +} diff --git a/src/string_impls/to_string.rs b/src/string_impls/to_string.rs index 39f4f81..a4a1a82 100644 --- a/src/string_impls/to_string.rs +++ b/src/string_impls/to_string.rs @@ -17,6 +17,9 @@ use super::string_helpers; use crate::prelude::*; use crate::value_type::{BitSequence, Composite, Primitive, Value, ValueDef, Variant}; use core::fmt::{Display, Write}; +use string_helpers::Formatter; + +const INDENT_STEP: usize = 2; impl Display for Value { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -26,83 +29,127 @@ impl Display for Value { impl Display for ValueDef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; match self { - ValueDef::Composite(c) => c.fmt(f), - ValueDef::Variant(v) => v.fmt(f), - ValueDef::BitSequence(b) => fmt_bitsequence(b, f), - ValueDef::Primitive(p) => p.fmt(f), + ValueDef::Composite(c) => fmt_composite(c, &mut f), + ValueDef::Variant(v) => fmt_variant(v, &mut f), + ValueDef::BitSequence(b) => fmt_bitsequence(b, &mut f), + ValueDef::Primitive(p) => fmt_primitive(p, &mut f), } } } impl Display for Composite { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Composite::Named(vals) => { - f.write_str("{ ")?; - for (idx, (name, val)) in vals.iter().enumerate() { - if idx != 0 { - f.write_str(", ")?; - } - if is_ident(name) { - f.write_str(name)?; - } else { - fmt_string(name, f)?; - } - f.write_str(": ")?; - val.fmt(f)?; - } - f.write_str(" }")?; - } - Composite::Unnamed(vals) => { - f.write_char('(')?; - for (idx, val) in vals.iter().enumerate() { - if idx != 0 { - f.write_str(", ")?; - } - val.fmt(f)?; - } - f.write_char(')')?; - } - } - Ok(()) + let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + fmt_composite(self, &mut f) } } impl Display for Variant { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if is_ident(&self.name) { - f.write_str(&self.name)?; - } else { - // If the variant name isn't a valid ident, we parse it into - // a special "v" prefixed string to allow arbitrary content while - // keeping it easy to parse variant names with minimal lookahead. - // Most use cases should never see or care about this. - f.write_char('v')?; - fmt_string(&self.name, f)?; - } - f.write_char(' ')?; - self.values.fmt(f) + let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + fmt_variant(self, &mut f) } } impl Display for Primitive { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Primitive::Bool(true) => f.write_str("true"), - Primitive::Bool(false) => f.write_str("false"), - Primitive::Char(c) => fmt_char(*c, f), - Primitive::I128(n) => n.fmt(f), - Primitive::U128(n) => n.fmt(f), - Primitive::String(s) => fmt_string(s, f), - // We don't currently have a sane way to parse into these or - // format out of them: - Primitive::U256(_) | Primitive::I256(_) => Err(core::fmt::Error), + let mut f: Formatter<_, ()> = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + fmt_primitive(self, &mut f) + } +} + +fn fmt_value(v: &Value, f: &mut Formatter) -> core::fmt::Result { + if f.should_print_context() { + f.write_char('<')?; + f.print_context(&v.context)?; + f.write_str("> ")?; + } + + match &v.value { + ValueDef::Composite(c) => fmt_composite(c, f), + ValueDef::Variant(v) => fmt_variant(v, f), + ValueDef::BitSequence(b) => fmt_bitsequence(b, f), + ValueDef::Primitive(p) => fmt_primitive(p, f), + } +} + +fn fmt_variant(v: &Variant, f: &mut Formatter) -> core::fmt::Result { + if is_ident(&v.name) { + f.write_str(&v.name)?; + } else { + // If the variant name isn't a valid ident, we parse it into + // a special "v" prefixed string to allow arbitrary content while + // keeping it easy to parse variant names with minimal lookahead. + // Most use cases should never see or care about this. + f.write_char('v')?; + fmt_string(&v.name, f)?; + } + f.write_char(' ')?; + fmt_composite(&v.values, f) +} + +fn fmt_composite( + v: &Composite, + f: &mut Formatter, +) -> core::fmt::Result { + match v { + Composite::Named(vals) => { + f.write_str("{")?; + f.indent_by(INDENT_STEP); + f.newline_or_space()?; + for (idx, (name, val)) in vals.iter().enumerate() { + if idx != 0 { + f.write_str(",")?; + f.newline_or_space()?; + } + if is_ident(name) { + f.write_str(name)?; + } else { + fmt_string(name, f)?; + } + f.write_str(": ")?; + fmt_value(val, f)?; + } + f.unindent_by(INDENT_STEP); + f.newline_or_space()?; + f.write_str("}")?; + } + Composite::Unnamed(vals) => { + f.write_char('(')?; + f.indent_by(INDENT_STEP); + f.newline_or_nothing()?; + for (idx, val) in vals.iter().enumerate() { + if idx != 0 { + f.write_str(",")?; + f.newline_or_space()?; + } + fmt_value(val, f)?; + } + f.unindent_by(INDENT_STEP); + f.newline_or_nothing()?; + f.write_char(')')?; } } + Ok(()) } -fn fmt_string(s: &str, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +fn fmt_primitive(p: &Primitive, f: &mut Formatter) -> core::fmt::Result { + match p { + Primitive::Bool(true) => f.write_str("true"), + Primitive::Bool(false) => f.write_str("false"), + Primitive::Char(c) => fmt_char(*c, f), + Primitive::I128(n) => write!(f, "{n}"), + Primitive::U128(n) => write!(f, "{n}"), + Primitive::String(s) => fmt_string(s, f), + // We don't currently have a sane way to parse into these or + // format out of them: + Primitive::U256(_) | Primitive::I256(_) => Err(core::fmt::Error), + } +} + +fn fmt_string(s: &str, f: &mut Formatter) -> core::fmt::Result { f.write_char('"')?; for char in s.chars() { match string_helpers::to_escape_code(char) { @@ -116,7 +163,7 @@ fn fmt_string(s: &str, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_char('"') } -fn fmt_char(c: char, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +fn fmt_char(c: char, f: &mut Formatter) -> core::fmt::Result { f.write_char('\'')?; match string_helpers::to_escape_code(c) { Some(escaped) => { @@ -128,7 +175,10 @@ fn fmt_char(c: char, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_char('\'') } -fn fmt_bitsequence(b: &BitSequence, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +fn fmt_bitsequence( + b: &BitSequence, + f: &mut Formatter, +) -> core::fmt::Result { f.write_char('<')?; for bit in b.iter() { match bit { @@ -187,6 +237,42 @@ mod test { } } + #[test] + fn expanded_output_works() { + let v = value!({ + hello: true, + sequence: (1,2,3), + variant: MyVariant (1,2,3), + inner: { + foo: "hello" + } + }); + + //// The manual way to do this would be: + // let mut s = String::new(); + // fmt_value(&v, &mut Formatter::spaced(&mut s)).unwrap(); + + assert_eq!( + format!("{v:#}"), + "{ + hello: true, + sequence: ( + 1, + 2, + 3 + ), + variant: MyVariant ( + 1, + 2, + 3 + ), + inner: { + foo: \"hello\" + } +}" + ); + } + #[test] fn pretty_variant_ident_used_when_possible() { let expected = [ From 58fa2defc6ec757bf8a6ac9f09659136fd65951e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 18 Jul 2024 16:57:13 +0100 Subject: [PATCH 2/5] Finish formatter and get it working in tracing decoder with type info --- src/scale_impls/tracing_decoder/error.rs | 125 +++++++++++++------- src/string_impls/formatter.rs | 142 +++++++++++++++++++++++ src/string_impls/mod.rs | 4 + src/string_impls/string_helpers.rs | 79 ------------- src/string_impls/to_string.rs | 116 +++++++++++------- 5 files changed, 299 insertions(+), 167 deletions(-) create mode 100644 src/string_impls/formatter.rs diff --git a/src/scale_impls/tracing_decoder/error.rs b/src/scale_impls/tracing_decoder/error.rs index 3a8dd85..d1194f0 100644 --- a/src/scale_impls/tracing_decoder/error.rs +++ b/src/scale_impls/tracing_decoder/error.rs @@ -1,6 +1,8 @@ +use core::fmt::Write; + use super::path::Path; use crate::scale::DecodeError; -use crate::{Composite, Value, ValueDef}; +use crate::{Composite, Primitive, Value, ValueDef}; /// An error encountered when decoding some bytes using the [`crate::scale::tracing`] module. #[derive(Clone, Debug)] @@ -73,7 +75,7 @@ struct VisitorError { decode_error: DecodeError, } -impl core::fmt::Display for TraceDecodingError { +impl core::fmt::Display for TraceDecodingError> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match &self.inner { TraceDecodingErrorInner::FromDecodeError(e) => { @@ -82,16 +84,17 @@ impl core::fmt::Display for TraceDecodingError { TraceDecodingErrorInner::FromVisitor(e) => { write!( f, - "Error decoding value at {}: {}\nDecoded so far: {}", - e.at, e.decode_error, e.decoded_so_far - ) + "Error decoding value at {}: {}\nDecoded so far:\n\n", + e.at, e.decode_error, + )?; + display_value_with_typeid(f, &e.decoded_so_far) } } } } #[cfg(feature = "std")] -impl std::error::Error for TraceDecodingError {} +impl std::error::Error for TraceDecodingError> {} impl From for TraceDecodingError { fn from(value: DecodeError) -> Self { @@ -105,40 +108,76 @@ impl From for TraceDecodingError { } } -// fn display_value_with_typeid(f: &mut core::fmt::Formatter<'_>, value: &Value) -> core::fmt::Result { -// let type_id = &value.context; -// match &value.value { -// ValueDef::Variant(var) => { -// write!(f, "{} [{type_id}] ", var.name)?; -// display_composite_with_typeid(f, &var.values)?; -// } -// ValueDef::Composite(_) => { - -// } -// ValueDef::BitSequence(_) => { - -// } -// ValueDef::Primitive(_) => { - -// } -// } -// Ok(()) -// } - -// fn display_composite_with_typeid(f: &mut core::fmt::Formatter<'_>, value: &Composite) -> core::fmt::Result { -// match value { -// Composite::Named(named) => { -// write!(f, "{{\n"); -// for (key, val) in named { -// write!(f, " {key}: ")?; -// display_value_with_typeid(f, val)?; -// write!("\n") -// } -// }, -// Composite::Unnamed(unnamed) => { - -// } -// } - -// Ok(()) -// } +fn display_value_with_typeid( + f: &mut core::fmt::Formatter<'_>, + value: &Value, +) -> core::fmt::Result { + use crate::string_impls::{fmt_value, FormatOpts, Formatter}; + + let format_opts = FormatOpts::new() + .spaced() + .context(|type_id, writer: &mut &mut core::fmt::Formatter| write!(writer, "{type_id:?}")) + .custom_formatter(|value, writer| custom_hex_formatter(value, writer)); + let mut formatter = Formatter::new(f, format_opts); + + fmt_value(value, &mut formatter) +} + +fn custom_hex_formatter( + value: &Value, + writer: W, +) -> Option { + // Print unnamed sequences of u8s as hex strings; ignore anything else. + if let ValueDef::Composite(Composite::Unnamed(vals)) = &value.value { + for val in vals { + if !matches!(val.value, ValueDef::Primitive(Primitive::U128(n)) if n < 256) { + return None; + } + } + Some(value_to_hex(vals, writer)) + } else { + None + } +} + +// Just to avoid needing to import the `hex` dependency just for this. +fn value_to_hex(vals: &Vec>, mut writer: W) -> core::fmt::Result { + writer.write_str("0x")?; + for val in vals { + if let ValueDef::Primitive(Primitive::U128(n)) = &val.value { + let n = *n as u8; + writer.write_char(u4_to_hex(n >> 4))?; + writer.write_char(u4_to_hex(n & 0b00001111))?; + } + } + Ok(()) +} + +fn u4_to_hex(n: u8) -> char { + static HEX: [char; 16] = + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; + *HEX.get(n as usize).expect("Expected a u4 (value between 0..=15") +} + +#[cfg(test)] +mod test { + use super::*; + use crate::value; + + #[test] + fn test_value_to_hex() { + let mut s = String::new(); + custom_hex_formatter(&value! {(0usize,230usize,255usize,15usize,12usize,4usize)}, &mut s) + .expect("decided not to convert to hex") + .expect("can't write to writer without issues"); + + assert_eq!(s, "0x00E6FF0F0C04"); + } + + #[test] + fn test_value_not_to_hex() { + let mut s = String::new(); + // 256 is too big to be a u8, so this value isn't valid hex. + assert_eq!(custom_hex_formatter(&value! {(0usize,230usize,256usize)}, &mut s), None); + } +} diff --git a/src/string_impls/formatter.rs b/src/string_impls/formatter.rs new file mode 100644 index 0000000..047c902 --- /dev/null +++ b/src/string_impls/formatter.rs @@ -0,0 +1,142 @@ +// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io) +// This file is a part of the scale-value crate. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::prelude::*; +use crate::Value; + +/// Options to configure a [`Formatter`]. +pub(crate) struct FormatOpts { + style: FormatStyle, + indent_by: String, + custom_formatter: Option, &mut W) -> Option>>, + print_context: Option core::fmt::Result>>, +} + +impl FormatOpts { + pub(crate) fn new() -> Self { + FormatOpts { + style: FormatStyle::Compact, + indent_by: " ".to_owned(), + custom_formatter: None, + print_context: None, + } + } + pub(crate) fn compact(mut self) -> Self { + self.style = FormatStyle::Compact; + self + } + pub(crate) fn spaced(mut self) -> Self { + self.style = FormatStyle::Indented(0); + self + } + pub(crate) fn context core::fmt::Result + 'static>( + mut self, + f: F, + ) -> Self { + self.print_context = Some(Box::new(f)); + self + } + pub(crate) fn custom_formatter< + F: Fn(&Value, &mut W) -> Option + 'static, + >( + mut self, + f: F, + ) -> Self { + self.custom_formatter = Some(Box::new(f)); + self + } +} + +/// We write a Value to some writer via this type, which controls +/// the level of indentation and whether to format in a compact or +/// pretty style. +pub(crate) struct Formatter { + writer: W, + opts: FormatOpts, +} + +impl Formatter { + pub(crate) fn new(writer: W, opts: FormatOpts) -> Self { + Formatter { writer, opts } + } + pub(crate) fn indent_step(&mut self) { + self.opts.style = match &self.opts.style { + FormatStyle::Compact => FormatStyle::Compact, + FormatStyle::Indented(n) => FormatStyle::Indented(n + 1), + }; + } + pub(crate) fn unindent_step(&mut self) { + self.opts.style = match &self.opts.style { + FormatStyle::Compact => FormatStyle::Compact, + FormatStyle::Indented(n) => FormatStyle::Indented(n.saturating_sub(1)), + }; + } + pub(crate) fn newline_or_nothing(&mut self) -> core::fmt::Result { + match self.opts.style { + FormatStyle::Compact => Ok(()), + FormatStyle::Indented(n) => write_newline(&mut self.writer, &self.opts.indent_by, n), + } + } + pub(crate) fn newline_or_space(&mut self) -> core::fmt::Result { + match self.opts.style { + FormatStyle::Compact => self.writer.write_char(' '), + FormatStyle::Indented(n) => write_newline(&mut self.writer, &self.opts.indent_by, n), + } + } + pub(crate) fn should_print_context(&self) -> bool { + self.opts.print_context.is_some() + } + pub(crate) fn print_context(&mut self, ctx: &T) -> core::fmt::Result { + if let Some(f) = &self.opts.print_context { + f(ctx, &mut self.writer) + } else { + Ok(()) + } + } + pub(crate) fn print_custom_format(&mut self, value: &Value) -> Option { + if let Some(f) = &self.opts.custom_formatter { + f(value, &mut self.writer) + } else { + None + } + } +} + +impl core::fmt::Write for Formatter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + // For now, we don't apply any formatting to this, and expect + // things to manually call `newline` etc to have formatting. + self.writer.write_str(s) + } +} + +/// this defines whether the above formatter will write "newlines" in a +/// compact style or more spaced out with indentation. +enum FormatStyle { + Indented(usize), + Compact, +} + +fn write_newline( + writer: &mut impl core::fmt::Write, + indent_str: &str, + indent: usize, +) -> core::fmt::Result { + writer.write_char('\n')?; + for _ in 0..indent { + writer.write_str(indent_str)?; + } + Ok(()) +} diff --git a/src/string_impls/mod.rs b/src/string_impls/mod.rs index d273b1f..d3ae0d0 100644 --- a/src/string_impls/mod.rs +++ b/src/string_impls/mod.rs @@ -18,6 +18,7 @@ mod custom_parsers; #[cfg(feature = "from-string")] mod from_string; +mod formatter; mod string_helpers; mod to_string; @@ -31,3 +32,6 @@ pub use from_string::{ pub use custom_parsers::parse_ss58; #[cfg(feature = "from-string")] pub use custom_parsers::{parse_hex, ParseHexError}; + +pub(crate) use formatter::{FormatOpts, Formatter}; +pub(crate) use to_string::fmt_value; diff --git a/src/string_impls/string_helpers.rs b/src/string_impls/string_helpers.rs index 5797d25..1569ea1 100644 --- a/src/string_impls/string_helpers.rs +++ b/src/string_impls/string_helpers.rs @@ -48,82 +48,3 @@ pub fn from_escape_code(c: char) -> Option { }; Some(unescaped) } - -/// We write a Value to some writer via this type, which controls -/// the level of indentation and whether to format in a compact or -/// pretty style. -pub(crate) struct Formatter { - writer: W, - style: FormatStyle, - print_context: Option core::fmt::Result>>, -} - -impl Formatter { - pub(crate) fn compact(writer: W) -> Self { - Formatter { writer, style: FormatStyle::Compact, print_context: None } - } - pub(crate) fn spaced(writer: W) -> Self { - Formatter { writer, style: FormatStyle::Indented(0), print_context: None } - } - pub(crate) fn context core::fmt::Result + 'static>(mut self, f: F) -> Self { - self.print_context = Some(Box::new(f)); - self - } - pub(crate) fn indent_by(&mut self, indent: usize) { - self.style = match &self.style { - FormatStyle::Compact => FormatStyle::Compact, - FormatStyle::Indented(n) => FormatStyle::Indented(n + indent), - }; - } - pub(crate) fn unindent_by(&mut self, indent: usize) { - self.style = match &self.style { - FormatStyle::Compact => FormatStyle::Compact, - FormatStyle::Indented(n) => FormatStyle::Indented(n.saturating_sub(indent)), - }; - } - pub(crate) fn newline_or_nothing(&mut self) -> core::fmt::Result { - match self.style { - FormatStyle::Compact => Ok(()), - FormatStyle::Indented(n) => write_newline(&mut self.writer, n), - } - } - pub(crate) fn newline_or_space(&mut self) -> core::fmt::Result { - match self.style { - FormatStyle::Compact => self.writer.write_char(' '), - FormatStyle::Indented(n) => write_newline(&mut self.writer, n), - } - } - pub(crate) fn should_print_context(&self) -> bool { - self.print_context.is_some() - } - pub(crate) fn print_context(&mut self, ctx: &T) -> core::fmt::Result { - if let Some(f) = &self.print_context { - f(ctx, &mut self.writer) - } else { - Ok(()) - } - } -} - -impl core::fmt::Write for Formatter { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - // For now, we don't apply any formatting to this, and expect - // things to manually call `newline` etc to have formatting. - self.writer.write_str(s) - } -} - -/// this defines whether the above formatter will write "newlines" in a -/// compact style or more spaced out with indentation. -enum FormatStyle { - Indented(usize), - Compact, -} - -pub fn write_newline(writer: &mut impl core::fmt::Write, indent: usize) -> core::fmt::Result { - writer.write_char('\n')?; - for _ in 0..indent { - writer.write_char(' ')?; - } - Ok(()) -} diff --git a/src/string_impls/to_string.rs b/src/string_impls/to_string.rs index a4a1a82..95e3795 100644 --- a/src/string_impls/to_string.rs +++ b/src/string_impls/to_string.rs @@ -13,13 +13,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::string_helpers; +use super::formatter::Formatter; +use super::{string_helpers, FormatOpts}; use crate::prelude::*; use crate::value_type::{BitSequence, Composite, Primitive, Value, ValueDef, Variant}; use core::fmt::{Display, Write}; -use string_helpers::Formatter; -const INDENT_STEP: usize = 2; +// Make a default formatter to use in the Display impls. +fn default_formatter(alternate: bool, writer: W) -> Formatter { + let opts = if alternate { FormatOpts::new().spaced() } else { FormatOpts::new().compact() }; + Formatter::new(writer, opts) +} impl Display for Value { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { @@ -29,45 +33,51 @@ impl Display for Value { impl Display for ValueDef { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; - match self { - ValueDef::Composite(c) => fmt_composite(c, &mut f), - ValueDef::Variant(v) => fmt_variant(v, &mut f), - ValueDef::BitSequence(b) => fmt_bitsequence(b, &mut f), - ValueDef::Primitive(p) => fmt_primitive(p, &mut f), - } + let mut f = default_formatter(f.alternate(), f); + fmt_valuedef(self, &mut f) } } impl Display for Composite { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + let mut f = default_formatter(f.alternate(), f); fmt_composite(self, &mut f) } } impl Display for Variant { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut f = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + let mut f = default_formatter(f.alternate(), f); fmt_variant(self, &mut f) } } impl Display for Primitive { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut f: Formatter<_, ()> = if f.alternate() { Formatter::spaced(f) } else { Formatter::compact(f) }; + let mut f = default_formatter::<_, ()>(f.alternate(), f); fmt_primitive(self, &mut f) } } -fn fmt_value(v: &Value, f: &mut Formatter) -> core::fmt::Result { +pub(crate) fn fmt_value( + v: &Value, + f: &mut Formatter, +) -> core::fmt::Result { if f.should_print_context() { f.write_char('<')?; f.print_context(&v.context)?; f.write_str("> ")?; } - match &v.value { + // Print custom output if there is some, else fall back to the normal logic. + f.print_custom_format(v).unwrap_or_else(|| fmt_valuedef(&v.value, f)) +} + +fn fmt_valuedef( + v: &ValueDef, + f: &mut Formatter, +) -> core::fmt::Result { + match v { ValueDef::Composite(c) => fmt_composite(c, f), ValueDef::Variant(v) => fmt_variant(v, f), ValueDef::BitSequence(b) => fmt_bitsequence(b, f), @@ -75,7 +85,10 @@ fn fmt_value(v: &Value, f: &mut Formatter) -> c } } -fn fmt_variant(v: &Variant, f: &mut Formatter) -> core::fmt::Result { +fn fmt_variant( + v: &Variant, + f: &mut Formatter, +) -> core::fmt::Result { if is_ident(&v.name) { f.write_str(&v.name)?; } else { @@ -96,46 +109,57 @@ fn fmt_composite( ) -> core::fmt::Result { match v { Composite::Named(vals) => { - f.write_str("{")?; - f.indent_by(INDENT_STEP); - f.newline_or_space()?; - for (idx, (name, val)) in vals.iter().enumerate() { - if idx != 0 { - f.write_str(",")?; - f.newline_or_space()?; + if vals.is_empty() { + f.write_str("{}")?; + } else { + f.write_str("{")?; + f.indent_step(); + f.newline_or_space()?; + for (idx, (name, val)) in vals.iter().enumerate() { + if idx != 0 { + f.write_str(",")?; + f.newline_or_space()?; + } + if is_ident(name) { + f.write_str(name)?; + } else { + fmt_string(name, f)?; + } + f.write_str(": ")?; + fmt_value(val, f)?; } - if is_ident(name) { - f.write_str(name)?; - } else { - fmt_string(name, f)?; - } - f.write_str(": ")?; - fmt_value(val, f)?; + f.unindent_step(); + f.newline_or_space()?; + f.write_str("}")?; } - f.unindent_by(INDENT_STEP); - f.newline_or_space()?; - f.write_str("}")?; } Composite::Unnamed(vals) => { - f.write_char('(')?; - f.indent_by(INDENT_STEP); - f.newline_or_nothing()?; - for (idx, val) in vals.iter().enumerate() { - if idx != 0 { - f.write_str(",")?; - f.newline_or_space()?; + if vals.is_empty() { + f.write_str("()")?; + } else { + f.write_char('(')?; + f.indent_step(); + f.newline_or_nothing()?; + for (idx, val) in vals.iter().enumerate() { + if idx != 0 { + f.write_str(",")?; + f.newline_or_space()?; + } + fmt_value(val, f)?; } - fmt_value(val, f)?; + f.unindent_step(); + f.newline_or_nothing()?; + f.write_char(')')?; } - f.unindent_by(INDENT_STEP); - f.newline_or_nothing()?; - f.write_char(')')?; } } Ok(()) } -fn fmt_primitive(p: &Primitive, f: &mut Formatter) -> core::fmt::Result { +fn fmt_primitive( + p: &Primitive, + f: &mut Formatter, +) -> core::fmt::Result { match p { Primitive::Bool(true) => f.write_str("true"), Primitive::Bool(false) => f.write_str("false"), @@ -241,6 +265,7 @@ mod test { fn expanded_output_works() { let v = value!({ hello: true, + empty: (), sequence: (1,2,3), variant: MyVariant (1,2,3), inner: { @@ -256,6 +281,7 @@ mod test { format!("{v:#}"), "{ hello: true, + empty: (), sequence: ( 1, 2, From 1e6d55d55e62b8c16be697969e3a79b28e07a19c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 18 Jul 2024 17:11:09 +0100 Subject: [PATCH 3/5] Tidy up and clippy --- src/scale_impls/tracing_decoder/error.rs | 15 +++++++++ src/scale_impls/tracing_decoder/mod.rs | 15 +++++++++ src/scale_impls/tracing_decoder/path.rs | 15 +++++++++ src/scale_impls/tracing_decoder/visitor.rs | 36 +++++++++++++--------- src/string_impls/formatter.rs | 4 ++- 5 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/scale_impls/tracing_decoder/error.rs b/src/scale_impls/tracing_decoder/error.rs index d1194f0..b75c4b5 100644 --- a/src/scale_impls/tracing_decoder/error.rs +++ b/src/scale_impls/tracing_decoder/error.rs @@ -1,3 +1,18 @@ +// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io) +// This file is a part of the scale-value crate. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use core::fmt::Write; use super::path::Path; diff --git a/src/scale_impls/tracing_decoder/mod.rs b/src/scale_impls/tracing_decoder/mod.rs index 750a2f0..b7d7858 100644 --- a/src/scale_impls/tracing_decoder/mod.rs +++ b/src/scale_impls/tracing_decoder/mod.rs @@ -1,3 +1,18 @@ +// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io) +// This file is a part of the scale-value crate. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + mod error; mod path; mod visitor; diff --git a/src/scale_impls/tracing_decoder/path.rs b/src/scale_impls/tracing_decoder/path.rs index 5394dcf..0d4cd80 100644 --- a/src/scale_impls/tracing_decoder/path.rs +++ b/src/scale_impls/tracing_decoder/path.rs @@ -1,3 +1,18 @@ +// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io) +// This file is a part of the scale-value crate. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::prelude::*; #[derive(Clone, Debug)] diff --git a/src/scale_impls/tracing_decoder/visitor.rs b/src/scale_impls/tracing_decoder/visitor.rs index 25dd758..ad83d9d 100644 --- a/src/scale_impls/tracing_decoder/visitor.rs +++ b/src/scale_impls/tracing_decoder/visitor.rs @@ -1,3 +1,18 @@ +// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io) +// This file is a part of the scale-value crate. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::prelude::*; use super::error::TraceDecodingError; @@ -26,6 +41,12 @@ impl TraceDecodingVisitor { } } +impl Default for TraceDecodingVisitor { + fn default() -> Self { + Self::new() + } +} + impl TraceDecodingVisitor { /// Construct a new [`TraceDecodingVisitor`]. pub fn new() -> Self { @@ -275,7 +296,7 @@ where Err(merged_error) } Ok(values) => Ok(Value { - value: ValueDef::Variant(Variant { name: variant_name.to_owned(), values: values }), + value: ValueDef::Variant(Variant { name: variant_name.to_owned(), values }), context: type_id, }), } @@ -342,16 +363,3 @@ where Ok(Composite::Named(vals)) } - -/* -Error decoding value at .[1].[0].proof.[69]: Not enough data to fill buffer. -Decoded so far: (Staking (bond { controller: (2... - -The type so far - -Staking [StakingCall] ( - bond [BondStaking] { - controller [AccountId]: - } -) -*/ diff --git a/src/string_impls/formatter.rs b/src/string_impls/formatter.rs index 047c902..2f7b35b 100644 --- a/src/string_impls/formatter.rs +++ b/src/string_impls/formatter.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io) +// Copyright (C) 2022-2024 Parity Technologies (UK) Ltd. (admin@parity.io) // This file is a part of the scale-value crate. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,9 @@ use crate::Value; pub(crate) struct FormatOpts { style: FormatStyle, indent_by: String, + #[allow(clippy::type_complexity)] custom_formatter: Option, &mut W) -> Option>>, + #[allow(clippy::type_complexity)] print_context: Option core::fmt::Result>>, } From 7e8b7628836bd7e5d57ad1dd20fb0308e04ea072 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 18 Jul 2024 17:13:40 +0100 Subject: [PATCH 4/5] no-std-ise --- src/scale_impls/tracing_decoder/error.rs | 4 ++-- src/scale_impls/tracing_decoder/path.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scale_impls/tracing_decoder/error.rs b/src/scale_impls/tracing_decoder/error.rs index b75c4b5..1bd2f43 100644 --- a/src/scale_impls/tracing_decoder/error.rs +++ b/src/scale_impls/tracing_decoder/error.rs @@ -13,11 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::fmt::Write; - use super::path::Path; +use crate::prelude::*; use crate::scale::DecodeError; use crate::{Composite, Primitive, Value, ValueDef}; +use core::fmt::Write; /// An error encountered when decoding some bytes using the [`crate::scale::tracing`] module. #[derive(Clone, Debug)] diff --git a/src/scale_impls/tracing_decoder/path.rs b/src/scale_impls/tracing_decoder/path.rs index 0d4cd80..f48a9c7 100644 --- a/src/scale_impls/tracing_decoder/path.rs +++ b/src/scale_impls/tracing_decoder/path.rs @@ -39,7 +39,7 @@ impl Path { } impl core::fmt::Display for Path { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { for segment in &self.0 { write!(f, ".")?; match segment { From b2201e3acfe7c135743ba031a8375baf044b5df4 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 24 Jul 2024 11:18:52 +0100 Subject: [PATCH 5/5] Don't expose PathSegment --- src/scale_impls/tracing_decoder/path.rs | 16 ++++++++++------ src/scale_impls/tracing_decoder/visitor.rs | 10 +++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/scale_impls/tracing_decoder/path.rs b/src/scale_impls/tracing_decoder/path.rs index f48a9c7..528f5bc 100644 --- a/src/scale_impls/tracing_decoder/path.rs +++ b/src/scale_impls/tracing_decoder/path.rs @@ -15,6 +15,9 @@ use crate::prelude::*; +// [jsdw] This could be internally turned into a linkedlist or something +// to make the "clone and append" faster. Not too concerned right now though +// since the tracing visitor it's used for isn't built for speed. #[derive(Clone, Debug)] pub struct Path(Vec); @@ -22,11 +25,6 @@ impl Path { pub fn new() -> Path { Path(vec![]) } - pub fn at(&self, segment: PathSegment) -> Path { - let mut p = self.0.clone(); - p.push(segment); - Path(p) - } pub fn at_idx(&self, idx: usize) -> Path { self.at(PathSegment::Index(idx)) } @@ -36,6 +34,12 @@ impl Path { pub fn at_variant(&self, variant: String) -> Path { self.at(PathSegment::Variant(variant)) } + + fn at(&self, segment: PathSegment) -> Path { + let mut p = self.0.clone(); + p.push(segment); + Path(p) + } } impl core::fmt::Display for Path { @@ -53,7 +57,7 @@ impl core::fmt::Display for Path { } #[derive(Clone, Debug)] -pub enum PathSegment { +enum PathSegment { Field(String), Index(usize), Variant(String), diff --git a/src/scale_impls/tracing_decoder/visitor.rs b/src/scale_impls/tracing_decoder/visitor.rs index ad83d9d..474407a 100644 --- a/src/scale_impls/tracing_decoder/visitor.rs +++ b/src/scale_impls/tracing_decoder/visitor.rs @@ -16,7 +16,7 @@ use crate::prelude::*; use super::error::TraceDecodingError; -use super::path::{Path, PathSegment}; +use super::path::Path; use crate::{Composite, Primitive, Value, ValueDef, Variant}; use core::marker::PhantomData; use scale_decode::visitor::TypeIdFor; @@ -30,14 +30,14 @@ pub struct TraceDecodingVisitor { } impl TraceDecodingVisitor { - fn at(&self, segment: PathSegment) -> Self { - TraceDecodingVisitor { path: self.path.at(segment), marker: PhantomData } + fn at_path(&self, path: Path) -> Self { + TraceDecodingVisitor { path, marker: PhantomData } } fn at_idx(&self, idx: usize) -> Self { - self.at(PathSegment::Index(idx)) + self.at_path(self.path.at_idx(idx)) } fn at_field(&self, field: String) -> Self { - self.at(PathSegment::Field(field)) + self.at_path(self.path.at_field(field)) } }