Skip to content

Commit

Permalink
Use the generated DispatchError instead of the hardcoded Substrate one (
Browse files Browse the repository at this point in the history
#394)

* WIP DispatchError generic param

* main crate now compiling with new E generic param for DispatchError

* Remove E param from RpcClient since it doesn't really need it

* Point to generated DispatchError in codegen so no need for additional param there

* More error hunting; cargo check --all-targets passes now

* Use our own RuntimeVersion struct (for now) to avoid error decoding into sp_version::RuntimeVersion

* cargo fmt

* fix docs and expose private documented thing that I think should be pub

* move error info to compile time so that we can make DispatchError a little nicer to work with

* cargo fmt

* clippy

* Rework error handling to remove <E> param in most cases

* fix Error doc ambiguity (hopefully)

* doc tweaks

* docs: remove dismbiguation thing that isn't needed now

* One more Error<E> that can be a BasicError

* rewrite pallet errors thing into normal loops to tidy

* tidy errors codegen a little

* tidy examples/custom_type_derives.rs a little

* cargo fmt

* silcnce clippy in example
  • Loading branch information
jsdw authored Jan 20, 2022
1 parent 79bf32a commit 6437959
Show file tree
Hide file tree
Showing 24 changed files with 3,453 additions and 2,435 deletions.
13 changes: 8 additions & 5 deletions codegen/src/api/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn generate_calls(
pub fn #fn_name(
&self,
#( #call_fn_args, )*
) -> ::subxt::SubmittableExtrinsic<'a, T, E, A, #call_struct_name> {
) -> ::subxt::SubmittableExtrinsic<'a, T, X, A, #call_struct_name, DispatchError> {
let call = #call_struct_name { #( #call_args, )* };
::subxt::SubmittableExtrinsic::new(self.client, call)
}
Expand All @@ -80,17 +80,20 @@ pub fn generate_calls(
quote! {
pub mod calls {
use super::#types_mod_ident;

type DispatchError = #types_mod_ident::sp_runtime::DispatchError;

#( #call_structs )*

pub struct TransactionApi<'a, T: ::subxt::Config, E, A> {
pub struct TransactionApi<'a, T: ::subxt::Config, X, A> {
client: &'a ::subxt::Client<T>,
marker: ::core::marker::PhantomData<(E, A)>,
marker: ::core::marker::PhantomData<(X, A)>,
}

impl<'a, T, E, A> TransactionApi<'a, T, E, A>
impl<'a, T, X, A> TransactionApi<'a, T, X, A>
where
T: ::subxt::Config,
E: ::subxt::SignedExtra<T>,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData<T>,
{
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
Expand Down
162 changes: 162 additions & 0 deletions codegen/src/api/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is part of subxt.
//
// subxt is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// subxt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.

use frame_metadata::v14::RuntimeMetadataV14;
use proc_macro2::{
Span as Span2,
TokenStream as TokenStream2,
};
use quote::quote;

/// Tokens which allow us to provide static error information in the generated output.
pub struct ErrorDetails {
/// This type definition will be used in the `dispatch_error_impl_fn` and is
/// expected to be generated somewhere in scope for that to be possible.
pub type_def: TokenStream2,
// A function which will live in an impl block for our `DispatchError`,
// to statically return details for known error types:
pub dispatch_error_impl_fn: TokenStream2,
}

impl ErrorDetails {
fn emit_compile_error(err: &str) -> ErrorDetails {
let err_lit_str = syn::LitStr::new(err, Span2::call_site());
ErrorDetails {
type_def: quote!(),
dispatch_error_impl_fn: quote!(compile_error!(#err_lit_str)),
}
}
}

/// The purpose of this is to enumerate all of the possible `(module_index, error_index)` error
/// variants, so that we can convert `u8` error codes inside a generated `DispatchError` into
/// nicer error strings with documentation. To do this, we emit the type we'll return instances of,
/// and a function that returns such an instance for all of the error codes seen in the metadata.
pub fn generate_error_details(metadata: &RuntimeMetadataV14) -> ErrorDetails {
let errors = match pallet_errors(metadata) {
Ok(errors) => errors,
Err(e) => {
let err_string =
format!("Failed to generate error details from metadata: {}", e);
return ErrorDetails::emit_compile_error(&err_string)
}
};

let match_body_items = errors.into_iter().map(|err| {
let docs = err.docs;
let pallet_index = err.pallet_index;
let error_index = err.error_index;
let pallet_name = err.pallet;
let error_name = err.error;

quote! {
(#pallet_index, #error_index) => Some(ErrorDetails {
pallet: #pallet_name,
error: #error_name,
docs: #docs
})
}
});

ErrorDetails {
type_def: quote! {
pub struct ErrorDetails {
pub pallet: &'static str,
pub error: &'static str,
pub docs: &'static str,
}
},
dispatch_error_impl_fn: quote! {
pub fn details(&self) -> Option<ErrorDetails> {
if let Self::Module { index, error } = self {
match (index, error) {
#( #match_body_items ),*,
_ => None
}
} else {
None
}
}
},
}
}

fn pallet_errors(
metadata: &RuntimeMetadataV14,
) -> Result<Vec<ErrorMetadata>, InvalidMetadataError> {
let get_type_def_variant = |type_id: u32| {
let ty = metadata
.types
.resolve(type_id)
.ok_or(InvalidMetadataError::MissingType(type_id))?;
if let scale_info::TypeDef::Variant(var) = ty.type_def() {
Ok(var)
} else {
Err(InvalidMetadataError::TypeDefNotVariant(type_id))
}
};

let mut pallet_errors = vec![];
for pallet in &metadata.pallets {
let error = match &pallet.error {
Some(err) => err,
None => continue,
};

let type_def_variant = get_type_def_variant(error.ty.id())?;
for var in type_def_variant.variants().iter() {
pallet_errors.push(ErrorMetadata {
pallet_index: pallet.index,
error_index: var.index(),
pallet: pallet.name.clone(),
error: var.name().clone(),
docs: var.docs().join("\n"),
});
}
}

Ok(pallet_errors)
}

/// Information about each error that we find in the metadata;
/// used to generate the static error information.
#[derive(Clone, Debug)]
struct ErrorMetadata {
pub pallet_index: u8,
pub error_index: u8,
pub pallet: String,
pub error: String,
pub docs: String,
}

#[derive(Debug)]
enum InvalidMetadataError {
MissingType(u32),
TypeDefNotVariant(u32),
}

impl std::fmt::Display for InvalidMetadataError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidMetadataError::MissingType(n) => {
write!(f, "Type {} missing from type registry", n)
}
InvalidMetadataError::TypeDefNotVariant(n) => {
write!(f, "Type {} was not a variant/enum type", n)
}
}
}
}
38 changes: 26 additions & 12 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with subxt. If not, see <http://www.gnu.org/licenses/>.

mod calls;
mod errors;
mod events;
mod storage;

Expand Down Expand Up @@ -216,6 +217,10 @@ impl RuntimeGenerator {
pallet.calls.as_ref().map(|_| pallet_mod_name)
});

let error_details = errors::generate_error_details(&self.metadata);
let error_type = error_details.type_def;
let error_fn = error_details.dispatch_error_impl_fn;

quote! {
#[allow(dead_code, unused_imports, non_camel_case_types)]
pub mod #mod_ident {
Expand All @@ -227,6 +232,15 @@ impl RuntimeGenerator {
/// constructing a transaction.
pub type DefaultAccountData = self::system::storage::Account;

/// The default error type returned when there is a runtime issue.
pub type DispatchError = self::runtime_types::sp_runtime::DispatchError;

// Statically generate error information so that we don't need runtime metadata for it.
#error_type
impl DispatchError {
#error_fn
}

impl ::subxt::AccountData<::subxt::DefaultConfig> for DefaultAccountData {
fn nonce(result: &<Self as ::subxt::StorageEntry>::Value) -> <::subxt::DefaultConfig as ::subxt::Config>::Index {
result.nonce
Expand All @@ -236,31 +250,31 @@ impl RuntimeGenerator {
}
}

pub struct RuntimeApi<T: ::subxt::Config, E> {
pub struct RuntimeApi<T: ::subxt::Config, X> {
pub client: ::subxt::Client<T>,
marker: ::core::marker::PhantomData<E>,
marker: ::core::marker::PhantomData<X>,
}

impl<T, E> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, E>
impl<T, X> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T, X>
where
T: ::subxt::Config,
E: ::subxt::SignedExtra<T>,
X: ::subxt::SignedExtra<T>,
{
fn from(client: ::subxt::Client<T>) -> Self {
Self { client, marker: ::core::marker::PhantomData }
}
}

impl<'a, T, E> RuntimeApi<T, E>
impl<'a, T, X> RuntimeApi<T, X>
where
T: ::subxt::Config,
E: ::subxt::SignedExtra<T>,
X: ::subxt::SignedExtra<T>,
{
pub fn storage(&'a self) -> StorageApi<'a, T> {
StorageApi { client: &self.client }
}

pub fn tx(&'a self) -> TransactionApi<'a, T, E, DefaultAccountData> {
pub fn tx(&'a self) -> TransactionApi<'a, T, X, DefaultAccountData> {
TransactionApi { client: &self.client, marker: ::core::marker::PhantomData }
}
}
Expand All @@ -280,19 +294,19 @@ impl RuntimeGenerator {
)*
}

pub struct TransactionApi<'a, T: ::subxt::Config, E, A> {
pub struct TransactionApi<'a, T: ::subxt::Config, X, A> {
client: &'a ::subxt::Client<T>,
marker: ::core::marker::PhantomData<(E, A)>,
marker: ::core::marker::PhantomData<(X, A)>,
}

impl<'a, T, E, A> TransactionApi<'a, T, E, A>
impl<'a, T, X, A> TransactionApi<'a, T, X, A>
where
T: ::subxt::Config,
E: ::subxt::SignedExtra<T>,
X: ::subxt::SignedExtra<T>,
A: ::subxt::AccountData<T>,
{
#(
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, E, A> {
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T, X, A> {
#pallets_with_calls::calls::TransactionApi::new(self.client)
}
)*
Expand Down
5 changes: 3 additions & 2 deletions codegen/src/api/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub fn generate_storage(
quote! {
pub mod storage {
use super::#types_mod_ident;

#( #storage_structs )*

pub struct StorageApi<'a, T: ::subxt::Config> {
Expand Down Expand Up @@ -195,7 +196,7 @@ fn generate_storage_entry_fns(
pub async fn #fn_name_iter(
&self,
hash: ::core::option::Option<T::Hash>,
) -> ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident>, ::subxt::Error> {
) -> ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident>, ::subxt::BasicError> {
self.client.storage().iter(hash).await
}
)
Expand All @@ -211,7 +212,7 @@ fn generate_storage_entry_fns(
&self,
#( #key_args, )*
hash: ::core::option::Option<T::Hash>,
) -> ::core::result::Result<#return_ty, ::subxt::Error> {
) -> ::core::result::Result<#return_ty, ::subxt::BasicError> {
let entry = #constructor;
self.client.storage().#fetch(&entry, hash).await
}
Expand Down
1 change: 1 addition & 0 deletions codegen/src/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl Default for GeneratedTypeDerives {
let mut derives = Punctuated::new();
derives.push(syn::parse_quote!(::subxt::codec::Encode));
derives.push(syn::parse_quote!(::subxt::codec::Decode));
derives.push(syn::parse_quote!(Debug));
Self::new(derives)
}
}
Expand Down
Loading

0 comments on commit 6437959

Please sign in to comment.