scale_info_legacy/
type_registry_set.rs

1// Copyright (C) 2024 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-info-legacy 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//! This module provides a [`TypeRegistrySet`], which is constructed from a selection of
17//! [`crate::TypeRegistry`]'s, and will resolve types by looking through each one in a known order
18//! until a match is found. This allows us to compose different sets of types into a single thing
19//! which itself also implements [`scale_type_resolver::TypeResolver`].
20
21use crate::type_registry::{
22    RuntimeApi, TypeRegistryResolveError, TypeRegistryResolveWithParentError,
23};
24use crate::{LookupName, TypeRegistry};
25use alloc::borrow::Cow;
26use alloc::collections::VecDeque;
27use hashbrown::HashSet;
28use scale_type_resolver::TypeResolver;
29
30/// This can be constructed from an iterator of [`crate::TypeRegistry`]s. When resolving types
31/// via [`scale_type_resolver::TypeResolver::resolve_type()`], it will treat the provided
32/// registries as a stack, looking in the last provided registry first and then working back
33/// through them until it can find the type, failing with an error if none of the registries
34/// contain an answer.
35#[derive(Debug)]
36pub struct TypeRegistrySet<'a> {
37    registries: VecDeque<Cow<'a, TypeRegistry>>,
38}
39
40impl<'a> TypeRegistrySet<'a> {
41    /// Take ownership of this [`TypeRegistrySet`]. If the underlying type registries are
42    /// borrowed, then they are cloned in order to take ownership of them.
43    pub fn to_owned(self) -> TypeRegistrySet<'static> {
44        let registries = self.registries.into_iter().map(|r| Cow::Owned(r.into_owned())).collect();
45        TypeRegistrySet { registries }
46    }
47
48    /// Add some types to the beginning of the set of registries. These types will be
49    /// checked after all of the others.
50    pub fn prepend(&mut self, types: impl Into<Cow<'a, TypeRegistry>>) {
51        self.registries.push_front(types.into());
52    }
53
54    /// Add some types to the end of the set of registries. These types will be
55    /// checked before all of the others.
56    pub fn append(&mut self, types: impl Into<Cow<'a, TypeRegistry>>) {
57        self.registries.push_back(types.into());
58    }
59
60    /// Resolve some type information by providing a [`LookupName`], which is the concrete name of
61    /// a type we want to know how to decode values for, and a `visitor` which will be called in
62    /// order to describe how to decode it.
63    ///
64    /// This will work through the inner type registries from latest to earliest until it finds a
65    /// matching type.
66    pub fn resolve_type<
67        'this,
68        V: scale_type_resolver::ResolvedTypeVisitor<'this, TypeId = LookupName>,
69    >(
70        &'this self,
71        mut type_id: LookupName,
72        mut visitor: V,
73    ) -> Result<V::Value, TypeRegistryResolveError> {
74        macro_rules! resolve_type {
75            () => {
76                for registry in self.registries.iter().rev() {
77                    match registry.resolve_type_with_parent(self, type_id, visitor) {
78                        // Found a value! return it.
79                        Ok(val) => return Ok(val),
80                        // Hit some other error; return it.
81                        Err(TypeRegistryResolveWithParentError::Other(e)) => return Err(e),
82                        // The type wasn't found; we'll continue to the next registry in the vec.
83                        Err(TypeRegistryResolveWithParentError::TypeNotFound {
84                            type_name: tn,
85                            visitor: v,
86                        }) => {
87                            type_id = tn;
88                            visitor = v;
89                        }
90                    }
91                }
92            };
93        }
94
95        resolve_type!();
96
97        // If the lookup was pallet scoped and we didn't find anything, then now we can remove
98        // the pallet scope and try to lookup the type again in the global scope.
99        if type_id.take_pallet().is_some() {
100            resolve_type!();
101        }
102
103        // We couldn't find the type, so call the "not found" method on the visitor.
104        Ok(visitor.visit_not_found())
105    }
106
107    /// Resolve some type information by providing the string name of the type,
108    /// and a `visitor` which will be called in order to describe how to decode it.
109    /// This just creates a [`LookupName`] under the hood and uses that to resolve the
110    /// type.
111    pub fn resolve_type_str<
112        'this,
113        V: scale_type_resolver::ResolvedTypeVisitor<'this, TypeId = LookupName>,
114    >(
115        &'this self,
116        type_name_str: &str,
117        visitor: V,
118    ) -> Result<V::Value, TypeRegistryResolveError> {
119        let type_id = LookupName::parse(type_name_str)
120            .map_err(|e| TypeRegistryResolveError::LookupNameInvalid(type_name_str.into(), e))?;
121        self.resolve_type(type_id, visitor)
122    }
123
124    /// Return a matching runtime API definition if one exists.
125    ///
126    /// This will work through the inner type registries from latest to earliest until it finds a
127    /// matching API.
128    pub fn runtime_api(&self, trait_name: &str, method_name: &str) -> Option<&RuntimeApi> {
129        for registry in self.registries.iter().rev() {
130            if let Some(api) = registry.runtime_api(trait_name, method_name) {
131                return Some(api);
132            }
133        }
134        None
135    }
136
137    /// Return an iterator of tuples of `(trait_name, method_name)` for all runtime APIs
138    /// that exist in this set of registries.
139    pub fn runtime_apis(&self) -> impl Iterator<Item = (&str, &str)> {
140        // We want to avoid returning any API more than once, so keep track of what we've seen already.
141        let mut seen = HashSet::<(&str, &str)>::new();
142
143        self.registries.iter().rev().flat_map(move |registry| registry.runtime_apis()).filter_map(
144            move |(trait_name, method_name)| {
145                if seen.insert((trait_name, method_name)) {
146                    Some((trait_name, method_name))
147                } else {
148                    None
149                }
150            },
151        )
152    }
153}
154
155impl<'a, R: Into<Cow<'a, TypeRegistry>>> core::iter::FromIterator<R> for TypeRegistrySet<'a> {
156    fn from_iter<T: IntoIterator<Item = R>>(iter: T) -> Self {
157        TypeRegistrySet { registries: iter.into_iter().map(Into::into).collect() }
158    }
159}
160
161impl<'a> TypeResolver for TypeRegistrySet<'a> {
162    type TypeId = LookupName;
163    type Error = TypeRegistryResolveError;
164
165    fn resolve_type<
166        'this,
167        V: scale_type_resolver::ResolvedTypeVisitor<'this, TypeId = Self::TypeId>,
168    >(
169        &'this self,
170        type_id: Self::TypeId,
171        visitor: V,
172    ) -> Result<V::Value, Self::Error> {
173        self.resolve_type(type_id, visitor)
174    }
175}
176
177#[cfg(test)]
178mod test {
179    use super::*;
180    use crate::test_utils::{to_resolved_info, ResolvedTypeInfo};
181    use crate::type_shape::Primitive;
182    use crate::{InsertName, TypeShape};
183    use alloc::vec;
184    use alloc::vec::Vec;
185    use scale_type_resolver::{BitsOrderFormat, BitsStoreFormat};
186
187    fn ln(name: &str) -> LookupName {
188        LookupName::parse(name).unwrap()
189    }
190    fn n(name: &str) -> InsertName {
191        InsertName::parse(name).unwrap()
192    }
193
194    #[test]
195    fn picks_pallet_scope_before_global() {
196        let mut a = TypeRegistry::empty();
197        a.insert_str("Val", TypeShape::Primitive(Primitive::I8)).unwrap();
198        a.insert(n("Val").in_pallet("p"), TypeShape::Primitive(Primitive::U8));
199
200        let mut b = TypeRegistry::empty();
201        b.insert_str("Val", TypeShape::Primitive(Primitive::I16)).unwrap();
202
203        let types = TypeRegistrySet::from_iter([a, b]);
204
205        // Globally, we find the most recent val first.
206        assert_eq!(to_resolved_info("Val", &types), ResolvedTypeInfo::Primitive(Primitive::I16));
207        // In pallet, we find the first in-pallet val anywhere before looking at any global ones.
208        assert_eq!(
209            to_resolved_info(ln("Val").in_pallet("p"), &types),
210            ResolvedTypeInfo::Primitive(Primitive::U8)
211        );
212    }
213
214    #[test]
215    fn picks_last_registry_first() {
216        let mut a = TypeRegistry::empty();
217        a.insert_str("u8", TypeShape::Primitive(Primitive::U8)).unwrap();
218        a.insert_str("Val", TypeShape::Primitive(Primitive::I8)).unwrap();
219
220        let mut b = TypeRegistry::empty();
221        b.insert_str("Val", TypeShape::Primitive(Primitive::I16)).unwrap();
222
223        let mut c = TypeRegistry::empty();
224        c.insert(n("Val").in_pallet("balances"), TypeShape::Primitive(Primitive::I32));
225
226        // Resolving will look in c, then b, then a.
227        let types = TypeRegistrySet::from_iter([a, b, c]);
228
229        // Sanity check; find val decalred inthe "bottom" registry only.
230        assert_eq!(to_resolved_info("u8", &types), ResolvedTypeInfo::Primitive(Primitive::U8));
231        // Ignores the pallet val since we aren't in said pallet.
232        assert_eq!(to_resolved_info("Val", &types), ResolvedTypeInfo::Primitive(Primitive::I16));
233        // Use the pallet specific val if the ID we pass is in that pallet.
234        assert_eq!(
235            to_resolved_info(ln("Val").in_pallet("balances"), &types),
236            ResolvedTypeInfo::Primitive(Primitive::I32)
237        );
238    }
239
240    #[test]
241    fn resolve_bitvec_backwards_across_registries() {
242        let mut a = TypeRegistry::empty();
243        a.insert_str(
244            "BitVec",
245            TypeShape::BitSequence { order: ln("bitvec::order::Lsb0"), store: ln("Store") },
246        )
247        .unwrap();
248
249        let mut b = TypeRegistry::empty();
250        b.insert_str("bitvec::order::Lsb0", TypeShape::StructOf(vec![])).unwrap();
251
252        let mut c = TypeRegistry::empty();
253        c.insert_str("Store", TypeShape::Primitive(Primitive::U8)).unwrap();
254
255        // Resolving will look in c, then b, then a.
256        let types = TypeRegistrySet::from_iter([a, b, c]);
257
258        // Order and store types are both in different registries. Not only different,
259        // but ones we've already looked in before we get to the BitVec.
260        assert_eq!(
261            to_resolved_info("BitVec", &types),
262            ResolvedTypeInfo::BitSequence(BitsStoreFormat::U8, BitsOrderFormat::Lsb0)
263        );
264    }
265
266    #[test]
267    fn resolve_alias_backwards_across_registries() {
268        let mut a = TypeRegistry::empty();
269        a.insert_str("A", TypeShape::AliasOf(ln("B"))).unwrap();
270
271        let mut b = TypeRegistry::empty();
272        b.insert_str("B", TypeShape::AliasOf(ln("C"))).unwrap();
273
274        let mut c = TypeRegistry::empty();
275        c.insert_str("C", TypeShape::Primitive(Primitive::Bool)).unwrap();
276
277        // Resolving will look in c, then b, then a.
278        let types = TypeRegistrySet::from_iter([a, b, c]);
279
280        // We try to resolve the alias A, which is in the last registry to be queried.
281        // This will need to backtrack to previous registries in the list to resolve.
282        assert_eq!(to_resolved_info("A", &types), ResolvedTypeInfo::Primitive(Primitive::Bool));
283    }
284
285    #[test]
286    fn runtime_apis_works_avoiding_dupes() {
287        let mut a = TypeRegistry::empty();
288        a.try_insert_runtime_api_without_inputs("A", "a1", "bool").unwrap();
289        a.try_insert_runtime_api_without_inputs("A", "a2", "bool").unwrap();
290
291        let mut b = TypeRegistry::empty();
292        b.try_insert_runtime_api_without_inputs("A", "a2", "bool").unwrap();
293
294        let mut c = TypeRegistry::empty();
295        c.try_insert_runtime_api_without_inputs("B", "b1", "bool").unwrap();
296
297        // Resolving will look in c, then b, then a.
298        let types = TypeRegistrySet::from_iter([a, b, c]);
299
300        let all_apis: Vec<_> = types.runtime_apis().collect();
301        assert_eq!(all_apis, vec![("B", "b1"), ("A", "a2"), ("A", "a1")]);
302
303        assert!(types.runtime_api("A", "a1").is_some());
304        assert!(types.runtime_api("A", "a2").is_some());
305        assert!(types.runtime_api("B", "b1").is_some());
306    }
307}