-
Notifications
You must be signed in to change notification settings - Fork 254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use the generated DispatchError instead of the hardcoded Substrate one #394
Changes from 16 commits
92c2c1f
55c1dcb
2a8f094
2f19f9c
5d0a56a
940c770
8235ddc
8425253
3748160
63fbde8
b6e13bd
a458565
ad38bdb
8c7a207
6309a1f
f6f27db
19fe205
5e6b82f
19df387
e1918eb
d8e40fa
424acb5
d7e5914
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// 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; | ||
|
||
pub struct ErrorDetails { | ||
pub type_def: TokenStream2, | ||
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.description(); | ||
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 { | ||
// A type we'll be returning that needs defining at the top level: | ||
type_def: quote! { | ||
pub struct ErrorDetails { | ||
pub pallet: &'static str, | ||
pub error: &'static str, | ||
pub docs: &'static str, | ||
} | ||
}, | ||
// A function which will live in an impl block for our DispatchError, | ||
// to statically return details for known error types: | ||
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 pallet_errors = metadata | ||
.pallets | ||
.iter() | ||
.filter_map(|pallet| { | ||
pallet.error.as_ref().map(|error| { | ||
let type_def_variant = get_type_def_variant(error.ty.id())?; | ||
Ok((pallet, type_def_variant)) | ||
}) | ||
}) | ||
.collect::<Result<Vec<(_, _)>, _>>()?; | ||
|
||
let errors = pallet_errors | ||
.iter() | ||
.flat_map(|(pallet, type_def_variant)| { | ||
type_def_variant.variants().iter().map(move |var| { | ||
ErrorMetadata { | ||
pallet_index: pallet.index, | ||
error_index: var.index(), | ||
pallet: pallet.name.clone(), | ||
error: var.name().clone(), | ||
variant: var.clone(), | ||
} | ||
}) | ||
}) | ||
.collect(); | ||
|
||
Ok(errors) | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct ErrorMetadata { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing docs here. Also, what do you think about moving this to the top? |
||
pub pallet_index: u8, | ||
pub error_index: u8, | ||
pub pallet: String, | ||
pub error: String, | ||
variant: scale_info::Variant<scale_info::form::PortableForm>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dq: what is the reason for storing the variant for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup, that's it! I'm not sure why I did/left this now. I've made it be just the doc string, which simplifies things a bit! |
||
} | ||
|
||
impl ErrorMetadata { | ||
pub fn description(&self) -> String { | ||
self.variant.docs().join("\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So if the node is compiled to strip the docs, this will be empty? Worth detecting and add some kind of default, "No docs available in the metadata"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the empty case docs will just be "", which seems good enough (that can be detected and handled down the line in whichever way things prefer) |
||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum InvalidMetadataError { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah; doesn't need to be |
||
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) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can unify these two chains into a single? Like, why collect the
pallet_errors
first and then build theerrors
Vec?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a bit tricky; we end up with
Result<_>
Items just prior to that collect, so the collecting I think is just there to give a chance to bail if we encounter any errors! I'll see whether I can do it in a clean way!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I settled on standard for loops; it felt tidier: