scale_type_resolver/lib.rs
1// Copyright (C) 2024 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-decode 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
16//! `scale-type-resolver` provides a generic [`TypeResolver`] trait which can be implemented for
17//! any type that is capable of being given a type ID and resolving that into information about
18//! how the type is SCALE encoded. This allows libraries like `scale-decode` to be able to decode
19//! SCALE encoded bytes using either a modern type resolver like [`scale_info::PortableRegistry`],
20//! or using entirely custom type resolvers (which we would need in order decode blocks from pre-V14
21//! metadata).
22//!
23//! It's unlikely that you'd depend on this library directly; more likely you'd depend on a library
24//! like `scale-decode` which uses and re-exports the [`TypeResolver`] trait itself.
25//!
26//! This crate is `no_std` by default and doesn't require `alloc` except for tests.
27#![no_std]
28#![deny(missing_docs)]
29
30use core::iter::ExactSizeIterator;
31
32/// An implementation and associated things related to [`scale_info::PortableRegistry`].
33#[cfg(feature = "scale-info")]
34pub mod portable_registry;
35
36/// A concrete [`ResolvedTypeVisitor`] implementation that allows you to provide closures to
37/// configure it. Using this is often a lot easier than implementing [`ResolvedTypeVisitor`] yourself,
38/// but does require an additional dependency and may be a touch less performant.
39#[cfg(feature = "visitor")]
40pub mod visitor;
41
42/// This trait can be implemented for any type that is capable of describing how some type (identified
43/// by a [`TypeResolver::TypeId`]) is represented in terms of SCALE encoded bytes.
44///
45/// The naive way of implementing a trait like this would be to have a function that takes in a type ID
46/// and returns an enum which represents how the type is SCALE encoded. However, building such an enum
47/// to describe more complex types would likely involve allocating, which we'd rather avoid where possible.
48/// Instead, the approach taken here is to provide a set of callbacks to the [`TypeResolver::resolve_type()`]
49/// via a [`ResolvedTypeVisitor`] implementation. Exactly one of these callbacks is fired (and provided the
50/// necessary information) depending on the outcome of resolving a given type. This allows us to be far more
51/// flexible about how we hand back the required information, which avoids allocations.
52///
53/// For an example implementation, see the code in the [`portable_registry`] module.
54pub trait TypeResolver {
55 /// An identifier which points to some type that we'd like information on.
56 type TypeId: TypeId + 'static;
57 /// An error that can be handed back in the process of trying to resolve a type.
58 type Error: core::fmt::Debug + core::fmt::Display;
59
60 /// Given a type ID, this calls a method on the provided [`ResolvedTypeVisitor`] describing the
61 /// outcome of resolving the SCALE encoding information.
62 fn resolve_type<'this, V: ResolvedTypeVisitor<'this, TypeId = Self::TypeId>>(
63 &'this self,
64 type_id: Self::TypeId,
65 visitor: V,
66 ) -> Result<V::Value, Self::Error>;
67}
68
69/// A glorified set of callbacks, exactly one of which will fire depending on the outcome of calling
70/// [`TypeResolver::resolve_type()`]. These don't typically need to be implemented by the user, and
71/// instead are implemented internally in eg `scale-decode` to drive the decoding of types.
72///
73/// The lifetime held by this trait is the lifetime of the type resolver. Various inputs to the callbacks
74/// are thus bound by this lifetime, and it's possible to return values which contain this lifetime.
75pub trait ResolvedTypeVisitor<'resolver>: Sized {
76 /// The type ID type that the [`TypeResolver`] is using.
77 type TypeId: TypeId + 'static;
78 /// Some value that can be returned from the called method.
79 type Value;
80
81 /// Called when the actual method to be called was not implemented.
82 fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value;
83
84 /// Called when the type ID passed to [`TypeResolver::resolve_type()`] was not found.
85 fn visit_not_found(self) -> Self::Value {
86 self.visit_unhandled(UnhandledKind::NotFound)
87 }
88
89 /// Called when the type ID corresponds to a composite type. This is provided an iterator
90 /// over each of the fields and their type IDs that the composite type contains.
91 fn visit_composite<Path, Fields>(self, _path: Path, _fields: Fields) -> Self::Value
92 where
93 Path: PathIter<'resolver>,
94 Fields: FieldIter<'resolver, Self::TypeId>,
95 {
96 self.visit_unhandled(UnhandledKind::Composite)
97 }
98
99 /// Called when the type ID corresponds to a variant type. This is provided the list of
100 /// variants (and for each variant, the fields within it) that the type could be encoded as.
101 fn visit_variant<Path, Fields, Var>(self, _path: Path, _variants: Var) -> Self::Value
102 where
103 Path: PathIter<'resolver>,
104 Fields: FieldIter<'resolver, Self::TypeId>,
105 Var: VariantIter<'resolver, Fields>,
106 {
107 self.visit_unhandled(UnhandledKind::Variant)
108 }
109
110 /// Called when the type ID corresponds to a sequence of values of some type ID (which is
111 /// provided as an argument here). The length of the sequence is compact encoded first.
112 fn visit_sequence<Path>(self, _path: Path, _type_id: Self::TypeId) -> Self::Value
113 where
114 Path: PathIter<'resolver>,
115 {
116 self.visit_unhandled(UnhandledKind::Sequence)
117 }
118
119 /// Called when the type ID corresponds to an array of values of some type ID (which is
120 /// provided as an argument here). The length of the array is known at compile time and
121 /// is also provided.
122 fn visit_array(self, _type_id: Self::TypeId, _len: usize) -> Self::Value {
123 self.visit_unhandled(UnhandledKind::Array)
124 }
125
126 /// Called when the type ID corresponds to a tuple of values whose type IDs are provided here.
127 fn visit_tuple<TypeIds>(self, _type_ids: TypeIds) -> Self::Value
128 where
129 TypeIds: ExactSizeIterator<Item = Self::TypeId>,
130 {
131 self.visit_unhandled(UnhandledKind::Tuple)
132 }
133
134 /// Called when the type ID corresponds to some primitive type. The exact primitive type is
135 /// provided in th form of an enum.
136 fn visit_primitive(self, _primitive: Primitive) -> Self::Value {
137 self.visit_unhandled(UnhandledKind::Primitive)
138 }
139
140 /// Called when the type ID corresponds to a compact encoded type. The type ID of the inner type
141 /// is provided.
142 fn visit_compact(self, _type_id: Self::TypeId) -> Self::Value {
143 self.visit_unhandled(UnhandledKind::Compact)
144 }
145
146 /// Called when the type ID corresponds to a bit sequence, whose store and order types are given.
147 fn visit_bit_sequence(
148 self,
149 _store_format: BitsStoreFormat,
150 _order_format: BitsOrderFormat,
151 ) -> Self::Value {
152 self.visit_unhandled(UnhandledKind::BitSequence)
153 }
154}
155
156/// If any of the [`ResolvedTypeVisitor`] methods are not implemented, then
157/// [`ResolvedTypeVisitor::visit_unhandled()`] is called, and given an instance of this enum to
158/// denote which method was unhandled.
159#[allow(missing_docs)]
160#[derive(Clone, Copy, PartialEq, Eq, Debug)]
161pub enum UnhandledKind {
162 NotFound,
163 Composite,
164 Variant,
165 Sequence,
166 Array,
167 Tuple,
168 Primitive,
169 Compact,
170 BitSequence,
171}
172
173/// A trait representing a type ID.
174pub trait TypeId: Clone + core::fmt::Debug + core::default::Default {}
175impl<T: Clone + core::fmt::Debug + core::default::Default> TypeId for T {}
176
177/// Information about a composite field.
178#[derive(Debug)]
179pub struct Field<'resolver, TypeId> {
180 /// The name of the field, or `None` if the field is unnamed.
181 pub name: Option<&'resolver str>,
182 /// The type ID corresponding to the value for this field.
183 pub id: TypeId,
184}
185
186impl<'resolver, TypeId: Copy> Copy for Field<'resolver, TypeId> {}
187impl<'resolver, TypeId: Clone> Clone for Field<'resolver, TypeId> {
188 fn clone(&self) -> Self {
189 Field {
190 name: self.name,
191 id: self.id.clone(),
192 }
193 }
194}
195
196impl<'resolver, TypeId> Field<'resolver, TypeId> {
197 /// Construct a new field with an ID and optional name.
198 pub fn new(id: TypeId, name: Option<&'resolver str>) -> Self {
199 Field { id, name }
200 }
201 /// Create a new unnamed field.
202 pub fn unnamed(id: TypeId) -> Self {
203 Field { name: None, id }
204 }
205 /// Create a new named field.
206 pub fn named(id: TypeId, name: &'resolver str) -> Self {
207 Field {
208 name: Some(name),
209 id,
210 }
211 }
212}
213
214/// Information about a specific variant type.
215#[derive(Clone, Debug)]
216pub struct Variant<'resolver, Fields> {
217 /// The variant index.
218 pub index: u8,
219 /// The variant name.
220 pub name: &'resolver str,
221 /// The fields contained within this variant.
222 pub fields: Fields,
223}
224
225/// An iterator over the path parts.
226pub trait PathIter<'resolver>: Iterator<Item = &'resolver str> {}
227impl<'resolver, T> PathIter<'resolver> for T where T: Iterator<Item = &'resolver str> {}
228
229/// An iterator over a set of fields.
230pub trait FieldIter<'resolver, TypeId>: ExactSizeIterator<Item = Field<'resolver, TypeId>> {}
231impl<'resolver, TypeId, T> FieldIter<'resolver, TypeId> for T where
232 T: ExactSizeIterator<Item = Field<'resolver, TypeId>>
233{
234}
235
236/// An iterator over a set of variants.
237pub trait VariantIter<'resolver, Fields>:
238 ExactSizeIterator<Item = Variant<'resolver, Fields>>
239{
240}
241impl<'resolver, Fields, T> VariantIter<'resolver, Fields> for T where
242 T: ExactSizeIterator<Item = Variant<'resolver, Fields>>
243{
244}
245
246/// This is handed to [`ResolvedTypeVisitor::visit_primitive()`], and
247/// denotes the exact shape of the primitive type that we have resolved.
248#[allow(missing_docs)]
249#[derive(Clone, Copy, PartialEq, Eq, Debug)]
250pub enum Primitive {
251 Bool,
252 Char,
253 Str,
254 U8,
255 U16,
256 U32,
257 U64,
258 U128,
259 U256,
260 I8,
261 I16,
262 I32,
263 I64,
264 I128,
265 I256,
266}
267
268/// This is a runtime representation of the order that bits will be written
269/// to the specified [`BitsStoreFormat`].
270///
271/// - [`BitsOrderFormat::Lsb0`] means that we write to the least significant bit first
272/// and then work our way up to the most significant bit as we push new bits.
273/// - [`BitsOrderFormat::Msb0`] means that we write to the most significant bit first
274/// and then work our way down to the least significant bit as we push new bits.
275///
276/// These are equivalent to `bitvec`'s `order::Lsb0` and `order::Msb0`.
277#[derive(Copy, Clone, PartialEq, Eq, Debug)]
278pub enum BitsOrderFormat {
279 /// Least significant bit first.
280 Lsb0,
281 /// Most significant bit first.
282 Msb0,
283}
284
285/// This is a runtime representation of the store type that we're targeting. These
286/// are equivalent to the `bitvec` store types `u8`, `u16` and so on.
287#[derive(Copy, Clone, PartialEq, Eq, Debug)]
288pub enum BitsStoreFormat {
289 /// Equivalent to [`u8`].
290 U8,
291 /// Equivalent to [`u16`].
292 U16,
293 /// Equivalent to [`u32`].
294 U32,
295 /// Equivalent to [`u64`].
296 U64,
297}