// 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 . 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 { if let Self::Module { index, error } = self { match (index, error) { #( #match_body_items ),*, _ => None } } else { None } } }, } } fn pallet_errors( metadata: &RuntimeMetadataV14, ) -> Result, 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) } } } }