-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a "tracing visitor" to make it easier to find decode issues (#52)
* WIP Tracing visitor and improving the Value display impl * Finish formatter and get it working in tracing decoder with type info * Tidy up and clippy * no-std-ise * Don't expose PathSegment
- Loading branch information
Showing
9 changed files
with
987 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
// 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 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)] | ||
pub struct TraceDecodingError<Val> { | ||
inner: TraceDecodingErrorInner<Val>, | ||
} | ||
|
||
impl<Val> TraceDecodingError<Val> { | ||
pub(crate) fn map_decoded_so_far<NewVal>( | ||
self, | ||
f: impl FnOnce(Val) -> NewVal, | ||
) -> TraceDecodingError<NewVal> { | ||
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<NewVal>( | ||
self, | ||
outer_path: impl FnOnce() -> Path, | ||
default_outer_value: impl FnOnce() -> NewVal, | ||
into_outer_value: impl FnOnce(Val) -> NewVal, | ||
) -> TraceDecodingError<NewVal> { | ||
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<Val> From<TraceDecodingErrorInner<Val>> for TraceDecodingError<Val> { | ||
fn from(value: TraceDecodingErrorInner<Val>) -> Self { | ||
TraceDecodingError { inner: value } | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
enum TraceDecodingErrorInner<Val> { | ||
FromDecodeError(DecodeError), | ||
FromVisitor(VisitorError<Val>), | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
struct VisitorError<Val> { | ||
at: Path, | ||
decoded_so_far: Val, | ||
decode_error: DecodeError, | ||
} | ||
|
||
impl<Ctx: core::fmt::Debug> core::fmt::Display for TraceDecodingError<Value<Ctx>> { | ||
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:\n\n", | ||
e.at, e.decode_error, | ||
)?; | ||
display_value_with_typeid(f, &e.decoded_so_far) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "std")] | ||
impl<Ctx: core::fmt::Debug> std::error::Error for TraceDecodingError<Value<Ctx>> {} | ||
|
||
impl<TypeId> From<DecodeError> for TraceDecodingError<TypeId> { | ||
fn from(value: DecodeError) -> Self { | ||
TraceDecodingErrorInner::FromDecodeError(value).into() | ||
} | ||
} | ||
|
||
impl<TypeId> From<codec::Error> for TraceDecodingError<TypeId> { | ||
fn from(value: codec::Error) -> Self { | ||
TraceDecodingErrorInner::FromDecodeError(value.into()).into() | ||
} | ||
} | ||
|
||
fn display_value_with_typeid<Id: core::fmt::Debug>( | ||
f: &mut core::fmt::Formatter<'_>, | ||
value: &Value<Id>, | ||
) -> 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<T, W: core::fmt::Write>( | ||
value: &Value<T>, | ||
writer: W, | ||
) -> Option<core::fmt::Result> { | ||
// 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<T, W: core::fmt::Write>(vals: &Vec<Value<T>>, 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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// 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; | ||
|
||
pub use error::TraceDecodingError; | ||
pub use visitor::TraceDecodingVisitor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// 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::*; | ||
|
||
// [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<PathSegment>); | ||
|
||
impl Path { | ||
pub fn new() -> Path { | ||
Path(vec![]) | ||
} | ||
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)) | ||
} | ||
|
||
fn at(&self, segment: PathSegment) -> Path { | ||
let mut p = self.0.clone(); | ||
p.push(segment); | ||
Path(p) | ||
} | ||
} | ||
|
||
impl core::fmt::Display for Path { | ||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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)] | ||
enum PathSegment { | ||
Field(String), | ||
Index(usize), | ||
Variant(String), | ||
} |
Oops, something went wrong.