Conversation
codegen/src/api/mod.rs
Outdated
| }; | ||
|
|
||
| let root_error_match_arms = self.metadata.pallets.iter().filter_map(|p| { | ||
| let variant_index = p.index; |
There was a problem hiding this comment.
We should compare based on variant names and not indexes; indexes can chanbe between nodes (as pallets tend to live in different indexes on different chains), so comparing on names is more portable :)
There was a problem hiding this comment.
Ah okay, so a conversion from index to name should happen inside the as_root_error() function of this struct:
pub struct ModuleError {
metadata: Metadata,
raw: RawModuleError,
}using it's metadata field?
There was a problem hiding this comment.
There's a details() function on the ModuleError already which will give you the pallet name that the error came from, so I'd probably call that inside the as_root_error function and then hand the name to the RootError trait so then the codegen can use the name to find the correct variant that was generated.
codegen/src/api/mod.rs
Outdated
|
|
||
| p.error.as_ref().map(|_| | ||
| quote! { | ||
| #variant_index => { |
There was a problem hiding this comment.
Just as a nit; I did "if arms" and not "match arms" for the RootEvent stuff, so I think we should be consistent one way or the other :)
The reason I did "if arms" was so that the impl below that uses this output is cleaner (match arms mean you have part of the match syntax here and part of it below which imo is a little less clean).
There was a problem hiding this comment.
Ah okay, I liked the match style more, because it is less generated code, but I can change it to if arms
There was a problem hiding this comment.
I'm not strongly against the match style; the "if" one just felt nicer code wise; either way both impls should be identical to be consistent :)
codegen/src/api/mod.rs
Outdated
| } | ||
| } | ||
|
|
||
|
|
| pub trait RootError: Sized { | ||
| /// Given details of the pallet error we want to decode | ||
| fn root_error(pallet_index: &u8, error: &[u8; 4]) -> Result<Self, Error>; | ||
| } |
There was a problem hiding this comment.
I'd more closely mirror the events version which looks like:
pub trait RootEvent: Sized {
fn root_event(
pallet_bytes: &[u8],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}So something more like this maybe:
pub trait RootError: Sized {
fn root_error(
pallet_bytes: [u8; 4],
pallet_name: &str,
pallet_event_ty: u32,
metadata: &Metadata,
) -> Result<Self, Error>;
}Reasons:
- We should avoid relying on
Decode::decodeif we can and instead use#mod_name::Error::decode_with_metadatato decode it. Why? becauseDecode::decodewill just decode the bytes based on the static codegenned type, whereasdecode_with_metadatawill decode into the error enum based on matching up the encoded variant names and fields with the error struct we've codegenned, making it more robust to changes in eg variant order or field order or whatever. - Using a string instead of index for pallet; same as above, just allows more robust matching across nodes where pallets are likely in a different order.
- Just generally to be consistent with RootError :)
subxt/src/metadata/metadata_type.rs
Outdated
| /// Returns a reference to [`PalletMetadata`]. | ||
| pub fn pallet_by_index(&self, pallet_index: u8) -> Result<&PalletMetadata, MetadataError> { | ||
| self.inner | ||
| .pallets | ||
| .values() | ||
| .find(|pallet_metadata| pallet_metadata.index == pallet_index) | ||
| .ok_or(MetadataError::PalletNotFound) | ||
| } |
There was a problem hiding this comment.
Maybe won't be needed when moving to using names over indexes
There was a problem hiding this comment.
You're right, I removed the function.
|
I was not able to find a way to produce a ModuleError in an integration test, if one of you @jsdw @niklasad1 @lexnv knows how I can get a ModuleError returned from a substrate node, please let me know, then I add a test. Otherwise we can merge without test, it compiles at least. |
codegen/src/api/errors.rs
Outdated
| Ok(quote! { | ||
| #docs | ||
| pub type Error = #error_type; | ||
|
|
There was a problem hiding this comment.
Nit: no need for this extra line
subxt/src/error/dispatch_error.rs
Outdated
| self.raw | ||
| } | ||
|
|
||
| /// attempts to decode the ModuleError into a value implementing the trait `RootError` |
There was a problem hiding this comment.
| /// attempts to decode the ModuleError into a value implementing the trait `RootError` | |
| /// Attempts to decode the ModuleError into a value implementing the trait `RootError` |
Nit: we should start public documentation with upper case, for code comments this is fine tho
subxt/src/error/mod.rs
Outdated
| pub trait RootError: Sized { | ||
| /// Given details of the pallet error we want to decode | ||
| fn root_error( | ||
| pallet_bytes: &[u8; 4], |
There was a problem hiding this comment.
Nit: Not a big deal at all but I'd either go for [u8; 4] (no need for reference since it's only 4 bytes) or &[u8] (gives a little room for growth if the error type grows from [u8;4] for some reason) :)
jsdw
left a comment
There was a problem hiding this comment.
Added a test and it all looks to work well to me; nice job!
| should_gen_docs, | ||
| )?; | ||
|
|
||
| let errors = errors::generate_error_type_alias(&type_gen, pallet, should_gen_docs)?; |
There was a problem hiding this comment.
nit: We could name this something like error_alias to be a bit more explicit, however you prefer here
| let outer_error_variants = self.metadata.pallets.iter().filter_map(|p| { | ||
| let variant_name = format_ident!("{}", p.name); | ||
| let mod_name = format_ident!("{}", p.name.to_string().to_snake_case()); | ||
| let index = proc_macro2::Literal::u8_unsuffixed(p.index); | ||
|
|
||
| p.error.as_ref().map(|_| { | ||
| quote! { | ||
| #[codec(index = #index)] | ||
| #variant_name(#mod_name::Error), | ||
| } | ||
| }) | ||
| }); | ||
|
|
||
| let outer_error = quote! { | ||
| #default_derives | ||
| pub enum Error { | ||
| #( #outer_error_variants )* | ||
| } | ||
| }; |
There was a problem hiding this comment.
Leaving here my thoughts, not necessarily to be done for this PR:
This part of the code seems highly similar to the generation of the outer Event enum.
There might be a few things we could do here to reduce code duplicate, improve reliability and maintenance:
- add a function
generate_outer_enum - take in the name of the enum (either Event / Error or other names we might add in the future)
#[derive(ToTokens)]
enum OuterEnumName {
Error,
Event,
}
lexnv
left a comment
There was a problem hiding this comment.
Nicely done!
The PR looks clean, I've left a few ideas that we could tackle in other PRs 👍
fixes #734.
Added an enum
Errorto the generated code. It has one variant for each pallet that exposes an error type.It implements a new trait called RootError. The RootError trait allows the generated root
Errortype to be constructed from a pallet index (u8) and 4 error bytes:The
ModuleErrorstruct now exposes a functionas_root_error<E: RootError>(&self) -> Result<E, Error>that trys to convert the ModuleError into the generates top level enumError.This makes it possible to check if a certain subxt error is a certain error from a certain pallet: