frame_decode/utils/
decode_with_error_tracing.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use alloc::format;
use alloc::string::String;

/// Decode some bytes given a type ID and type resolver, and a visitor which decides the output value.
///
/// If the decoding fails and the `error-tracing` feature is enabled, we try to decode again using
/// a tracing visitor in order to return a more detailed error message.
pub fn decode_with_error_tracing<'scale, 'resolver, Resolver, Id, V>(
    cursor: &mut &'scale [u8],
    type_id: Id,
    types: &'resolver Resolver,
    visitor: V,
) -> Result<V::Value<'scale, 'resolver>, DecodeErrorTrace>
where
    Resolver: scale_type_resolver::TypeResolver<TypeId = Id>,
    Id: core::fmt::Debug + Clone,
    V: scale_decode::Visitor<TypeResolver = Resolver>,
    V::Error: core::fmt::Debug,
{
    let initial = *cursor;
    match scale_decode::visitor::decode_with_visitor(cursor, type_id.clone(), types, visitor) {
        Ok(value) => Ok(value),
        // Don't use scale-value; return the error as-is.
        #[cfg(not(feature = "error-tracing"))]
        Err(e) => {
            *cursor = initial;

            Err(DecodeErrorTrace {
                original_error: format!("{e:?}"),
                tracing_error: String::new(),
            })
        }
        // Use scale-value tracing visitor to return a better error
        #[cfg(feature = "error-tracing")]
        Err(e) => {
            // Reset cursor incase it's been consumed by the above call, and decode using the
            // tracing visitor to hopefully return a better error.
            *cursor = initial;
            let res = scale_value::scale::tracing::decode_as_type(cursor, type_id.clone(), types)
                .map(|v| v.map_context(|id| format!("{id:?}")))
                .map_err(|te| DecodeErrorTrace {
                    original_error: format!("{e:?}"),
                    tracing_error: te.to_string(),
                })?;

            // If the above succeeds (we're expecting it to fail), then print the value out here.
            use core::fmt::Write;
            let mut res_string = String::new();
            write!(
                &mut res_string,
                "Failed to decode value with custom visitor (but tracing decoded it):\n\n"
            )
            .unwrap();
            scale_value::stringify::to_writer_custom()
                .pretty()
                .format_context(|type_id, w: &mut &mut String| write!(w, "{type_id}"))
                .add_custom_formatter(|v, w| {
                    scale_value::stringify::custom_formatters::format_hex(v, w)
                })
                .add_custom_formatter(|v, w| {
                    // don't space unnamed composites over multiple lines if lots of primitive values.
                    if let scale_value::ValueDef::Composite(scale_value::Composite::Unnamed(vals)) =
                        &v.value
                    {
                        let are_primitive = vals
                            .iter()
                            .all(|val| matches!(val.value, scale_value::ValueDef::Primitive(_)));
                        if are_primitive {
                            return Some(write!(w, "{v}"));
                        }
                    }
                    None
                })
                .write(&res, &mut res_string)
                .expect("writing to string should always succeed");

            Err(DecodeErrorTrace {
                original_error: format!("{e:?}"),
                tracing_error: res_string,
            })
        }
    }
}

/// A tracing decode error.
#[derive(Clone, Debug)]
pub struct DecodeErrorTrace {
    original_error: String,
    tracing_error: String,
}

impl core::error::Error for DecodeErrorTrace {}

impl core::fmt::Display for DecodeErrorTrace {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let DecodeErrorTrace {
            original_error,
            tracing_error,
        } = self;

        write!(f, "{original_error}")?;
        if !tracing_error.is_empty() {
            write!(f, ":\n\n{tracing_error}")?;
        }
        Ok(())
    }
}