frame_decode/utils/
decode_with_error_tracing.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use alloc::format;
17use alloc::string::String;
18
19/// Decode some bytes given a type ID and type resolver, and a visitor which decides the output value.
20///
21/// If the decoding fails and the `error-tracing` feature is enabled, we try to decode again using
22/// a tracing visitor in order to return a more detailed error message.
23pub fn decode_with_error_tracing<'scale, 'resolver, Resolver, Id, V>(
24    cursor: &mut &'scale [u8],
25    type_id: Id,
26    types: &'resolver Resolver,
27    visitor: V,
28) -> Result<V::Value<'scale, 'resolver>, DecodeErrorTrace>
29where
30    Resolver: scale_type_resolver::TypeResolver<TypeId = Id>,
31    Id: core::fmt::Debug + Clone,
32    V: scale_decode::Visitor<TypeResolver = Resolver>,
33    V::Error: core::fmt::Debug,
34{
35    let initial = *cursor;
36    match scale_decode::visitor::decode_with_visitor(cursor, type_id.clone(), types, visitor) {
37        Ok(value) => Ok(value),
38        // Don't use scale-value; return the error as-is.
39        #[cfg(not(feature = "error-tracing"))]
40        Err(e) => {
41            *cursor = initial;
42
43            Err(DecodeErrorTrace {
44                original_error: format!("{e:?}"),
45                tracing_error: String::new(),
46            })
47        }
48        // Use scale-value tracing visitor to return a better error
49        #[cfg(feature = "error-tracing")]
50        Err(e) => {
51            // Reset cursor incase it's been consumed by the above call, and decode using the
52            // tracing visitor to hopefully return a better error.
53            *cursor = initial;
54            let res = scale_value::scale::tracing::decode_as_type(cursor, type_id.clone(), types)
55                .map(|v| v.map_context(|id| format!("{id:?}")))
56                .map_err(|te| DecodeErrorTrace {
57                    original_error: format!("{e:?}"),
58                    tracing_error: alloc::string::ToString::to_string(&te),
59                })?;
60
61            // If the above succeeds (we're expecting it to fail), then print the value out here.
62            use core::fmt::Write;
63            let mut res_string = String::new();
64            write!(
65                &mut res_string,
66                "Failed to decode value with custom visitor (but tracing decoded it):\n\n"
67            )
68            .unwrap();
69
70            scale_value::stringify::to_writer_custom()
71                .pretty()
72                .format_context(|type_id, w: &mut &mut String| write!(w, "{type_id}"))
73                .add_custom_formatter(|v, w| {
74                    scale_value::stringify::custom_formatters::format_hex(v, w)
75                })
76                .add_custom_formatter(|v, w| {
77                    // don't space unnamed composites over multiple lines if lots of primitive values.
78                    if let scale_value::ValueDef::Composite(scale_value::Composite::Unnamed(vals)) =
79                        &v.value
80                    {
81                        let are_primitive = vals
82                            .iter()
83                            .all(|val| matches!(val.value, scale_value::ValueDef::Primitive(_)));
84                        if are_primitive {
85                            return Some(write!(w, "{v}"));
86                        }
87                    }
88                    None
89                })
90                .write(&res, &mut res_string)
91                .expect("writing to string should always succeed");
92
93            Err(DecodeErrorTrace {
94                original_error: format!("{e:?}"),
95                tracing_error: res_string,
96            })
97        }
98    }
99}
100
101/// A tracing decode error.
102#[derive(Clone, Debug, thiserror::Error)]
103pub struct DecodeErrorTrace {
104    original_error: String,
105    tracing_error: String,
106}
107
108impl core::fmt::Display for DecodeErrorTrace {
109    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110        let DecodeErrorTrace {
111            original_error,
112            tracing_error,
113        } = self;
114
115        write!(f, "{original_error}")?;
116        if !tracing_error.is_empty() {
117            write!(f, ":\n\n{tracing_error}")?;
118        }
119        Ok(())
120    }
121}