subxt-historic: prep 0.0.8 release: expose type resolver that can be used with visitors (#2140)

* subxt-historic: prep 0.0.8 release: expose type resolver that can be used with visitors

* clippy

* Fix example

* Fix docs test
This commit is contained in:
James Wilson
2025-12-05 17:11:25 +00:00
committed by GitHub
parent 24ccd584f1
commit c32017e453
7 changed files with 116 additions and 34 deletions
+4
View File
@@ -4,6 +4,10 @@ This is separate from the Subxt changelog as subxt-historic is currently releasa
Eventually this project will merge with Subxt and no longer exist, but until then it's being maintained and updated where needed.
## 0.0.8 (2025-12-04)
Expose `ClientAtBlock::resolver()`. This hands back a type resolver which is capable of resolving type IDs given by the `.visit()` methods on extrinsic fields and storage values. The extrinsics example has been modified to show how this can be used.
## 0.0.7 (2025-12-03)
Expose `OfflineClientAtBlock`, `OfflineClientAtBlockT`, `OnlinelientAtBlock`, `OnlineClientAtBlockT`.
+67 -22
View File
@@ -48,7 +48,8 @@ async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static
.unwrap_or_default();
// When visiting fields we can also decode into a custom shape like so:
let _custom_value = field.visit(value::GetValue::new())?;
let _custom_value =
field.visit(value::GetValue::new(&client_at_block.resolver()))?;
// We can also obtain and decode things without the complexity of the above:
println!(
@@ -183,7 +184,8 @@ mod value {
I256([u8; 32]),
U256([u8; 32]),
Struct(HashMap<String, Value>),
Variant(String, VariantFields),
VariantWithoutData(String),
VariantWithData(String, VariantFields),
}
pub enum VariantFields {
@@ -198,23 +200,23 @@ mod value {
Decode(#[from] scale_decode::visitor::DecodeError),
#[error("Cannot decode bit sequence: {0}")]
CannotDecodeBitSequence(codec::Error),
#[error("Cannot resolve variant type information: {0}")]
CannotResolveVariantType(String),
}
/// This is a visitor which obtains type names.
pub struct GetValue<R> {
marker: core::marker::PhantomData<R>,
pub struct GetValue<'r, R> {
resolver: &'r R,
}
impl<R> GetValue<R> {
impl<'r, R> GetValue<'r, R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetValue {
marker: core::marker::PhantomData,
}
pub fn new(resolver: &'r R) -> Self {
GetValue { resolver }
}
}
impl<R: TypeResolver> Visitor for GetValue<R> {
impl<'r, R: TypeResolver> Visitor for GetValue<'r, R> {
type Value<'scale, 'resolver> = Value;
type Error = ValueError;
type TypeResolver = R;
@@ -346,7 +348,11 @@ mod value {
values: &mut Array<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_sequence<'scale, 'resolver>(
@@ -354,7 +360,11 @@ mod value {
values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_str<'scale, 'resolver>(
@@ -370,7 +380,11 @@ mod value {
values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
Ok(Value::Array(to_array(
self.resolver,
values.remaining(),
values,
)?))
}
fn visit_bitsequence<'scale, 'resolver>(
@@ -401,7 +415,7 @@ mod value {
}
// Reuse logic for decoding variant fields:
match to_variant_fieldish(value)? {
match to_variant_fieldish(self.resolver, value)? {
VariantFields::Named(s) => Ok(Value::Struct(s)),
VariantFields::Unnamed(a) => Ok(Value::Array(a)),
}
@@ -410,20 +424,50 @@ mod value {
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Because we have access to a type resolver on self, we can
// look up the type IDs we're given back and base decode decisions
// on them. here we see whether the enum type has any data attached:
let has_data_visitor = scale_type_resolver::visitor::new((), |_, _| false)
.visit_variant(|_, _, variants| {
for mut variant in variants {
if variant.fields.next().is_some() {
return true;
}
}
false
});
// Do any variants have data in this enum type?
let has_data = self
.resolver
.resolve_type(type_id, has_data_visitor)
.map_err(|e| ValueError::CannotResolveVariantType(e.to_string()))?;
let name = value.name().to_owned();
let fields = to_variant_fieldish(value.fields())?;
Ok(Value::Variant(name, fields))
// base our decoding on whether any data in enum type.
if has_data {
let fields = to_variant_fieldish(self.resolver, value.fields())?;
Ok(Value::VariantWithData(name, fields))
} else {
Ok(Value::VariantWithoutData(name))
}
}
}
fn to_variant_fieldish<'scale, 'resolver, R: TypeResolver>(
fn to_variant_fieldish<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
value: &mut Composite<'scale, 'resolver, R>,
) -> Result<VariantFields, ValueError> {
// If fields are unnamed, treat as array:
if value.fields().iter().all(|f| f.name.is_none()) {
return Ok(VariantFields::Unnamed(to_array(value.remaining(), value)?));
return Ok(VariantFields::Unnamed(to_array(
resolver,
value.remaining(),
value,
)?));
}
// Otherwise object:
@@ -431,18 +475,19 @@ mod value {
for field in value {
let field = field?;
let name = field.name().unwrap().to_string();
let value = field.decode_with_visitor(GetValue::new())?;
let value = field.decode_with_visitor(GetValue::new(resolver))?;
out.insert(name, value);
}
Ok(VariantFields::Named(out))
}
fn to_array<'scale, 'resolver, R: TypeResolver>(
fn to_array<'r, 'scale, 'resolver, R: TypeResolver>(
resolver: &'r R,
len: usize,
mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>,
) -> Result<Vec<Value>, ValueError> {
let mut out = Vec::with_capacity(len);
while let Some(value) = values.decode_item(GetValue::new()) {
while let Some(value) = values.decode_item(GetValue::new(resolver)) {
out.push(value?);
}
Ok(out)
+25
View File
@@ -4,6 +4,7 @@ mod online_client;
use crate::config::Config;
use crate::extrinsics::ExtrinsicsClient;
use crate::storage::StorageClient;
use crate::utils::AnyResolver;
use frame_metadata::RuntimeMetadata;
use std::marker::PhantomData;
@@ -45,4 +46,28 @@ where
pub fn metadata(&self) -> &RuntimeMetadata {
self.client.metadata()
}
/// Return something which implements [`scale_type_resolver::TypeResolver`] and
/// can be used in conjnction with type IDs in `.visit` methods.
pub fn resolver(&self) -> AnyResolver<'_, 'client> {
match self.client.metadata() {
RuntimeMetadata::V0(_)
| RuntimeMetadata::V1(_)
| RuntimeMetadata::V2(_)
| RuntimeMetadata::V3(_)
| RuntimeMetadata::V4(_)
| RuntimeMetadata::V5(_)
| RuntimeMetadata::V6(_)
| RuntimeMetadata::V7(_)
| RuntimeMetadata::V8(_)
| RuntimeMetadata::V9(_)
| RuntimeMetadata::V10(_)
| RuntimeMetadata::V11(_)
| RuntimeMetadata::V12(_)
| RuntimeMetadata::V13(_) => AnyResolver::B(self.client.legacy_types()),
RuntimeMetadata::V14(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V15(m) => AnyResolver::A(&m.types),
RuntimeMetadata::V16(m) => AnyResolver::A(&m.types),
}
}
}
+5 -3
View File
@@ -54,7 +54,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCall<'extrinsics, 'atblock> {
pub struct ExtrinsicCallFields<'extrinsics, 'atblock> {
all_bytes: &'extrinsics [u8],
info: &'extrinsics AnyExtrinsicInfo<'atblock>,
resolver: AnyResolver<'atblock>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
@@ -135,7 +135,7 @@ impl<'extrinsics, 'atblock> ExtrinsicCallFields<'extrinsics, 'atblock> {
pub struct ExtrinsicCallField<'fields, 'extrinsics, 'atblock> {
field_bytes: &'extrinsics [u8],
info: AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock>,
resolver: &'fields AnyResolver<'atblock>,
resolver: &'fields AnyResolver<'atblock, 'atblock>,
}
enum AnyExtrinsicCallFieldInfo<'extrinsics, 'atblock> {
@@ -172,7 +172,9 @@ impl<'fields, 'extrinsics, 'atblock> ExtrinsicCallField<'fields, 'extrinsics, 'a
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`ExtrinsicCallField::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`ExtrinsicCallField::decode_as`].
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'extrinsics, 'fields>, V::Error> {
+4 -2
View File
@@ -10,7 +10,7 @@ use std::sync::Arc;
pub struct StorageValue<'atblock> {
pub(crate) info: Arc<AnyStorageInfo<'atblock>>,
bytes: Cow<'atblock, [u8]>,
resolver: AnyResolver<'atblock>,
resolver: AnyResolver<'atblock, 'atblock>,
}
impl<'atblock> StorageValue<'atblock> {
@@ -41,7 +41,9 @@ impl<'atblock> StorageValue<'atblock> {
/// Visit the given field with a [`scale_decode::visitor::Visitor`]. This is like a lower level
/// version of [`StorageValue::decode_as`], as the visitor is able to preserve lifetimes
/// and has access to more type information than is available via [`StorageValue::decode_as`].
pub fn visit<V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock>>>(
pub fn visit<
V: scale_decode::visitor::Visitor<TypeResolver = AnyResolver<'atblock, 'atblock>>,
>(
&self,
visitor: V,
) -> Result<V::Value<'_, '_>, V::Error> {
+3 -5
View File
@@ -3,10 +3,8 @@ use scale_info_legacy::LookupName;
use scale_type_resolver::ResolvedTypeVisitor;
/// A type resolver which could either be for modern or historic resolving.
pub type AnyResolver<'resolver> = Either<
&'resolver scale_info::PortableRegistry,
&'resolver scale_info_legacy::TypeRegistrySet<'resolver>,
>;
pub type AnyResolver<'a, 'b> =
Either<&'a scale_info::PortableRegistry, &'a scale_info_legacy::TypeRegistrySet<'b>>;
/// A type ID which is either a modern or historic ID.
pub type AnyTypeId = Either<u32, scale_info_legacy::LookupName>;
@@ -60,7 +58,7 @@ pub enum AnyResolverError {
ScaleInfoLegacy(scale_info_legacy::type_registry::TypeRegistryResolveError),
}
impl<'resolver> scale_type_resolver::TypeResolver for AnyResolver<'resolver> {
impl<'a, 'b> scale_type_resolver::TypeResolver for AnyResolver<'a, 'b> {
type TypeId = AnyTypeId;
type Error = AnyResolverError;
@@ -99,12 +99,18 @@ fn interface_docs(should_gen_docs: bool) -> Vec<String> {
#[test]
fn check_documentation() {
// Inspect metadata recursively and obtain all associated documentation.
// Inspect metadata and obtain all associated documentation.
let raw_docs = metadata_docs();
// Obtain documentation from the generated API.
let runtime_docs = interface_docs(true);
for raw in raw_docs.iter() {
if raw.contains(|c: char| !c.is_ascii()) {
// Ignore lines containing on-ascii chars; they are encoded currently
// as "\u{nn}" which doesn't match their input which is the raw non-ascii
// char.
continue;
}
assert!(
runtime_docs.contains(raw),
"Documentation not present in runtime API: {raw}"
@@ -114,7 +120,7 @@ fn check_documentation() {
#[test]
fn check_no_documentation() {
// Inspect metadata recursively and obtain all associated documentation.
// Inspect metadata and obtain all associated documentation.
let raw_docs = metadata_docs();
// Obtain documentation from the generated API.
let runtime_docs = interface_docs(false);