-
Notifications
You must be signed in to change notification settings - Fork 255
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
codegen for root level error #930
codegen for root level error #930
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
} | ||
} | ||
|
||
|
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.
Nit; unnecessary newline :)
subxt/src/error/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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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::decode
if we can and instead use#mod_name::Error::decode_with_metadata
to decode it. Why? becauseDecode::decode
will just decode the bytes based on the static codegenned type, whereasdecode_with_metadata
will 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe won't be needed when moving to using names over indexes
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: no need for this extra line
subxt/src/error/dispatch_error.rs
Outdated
@@ -159,6 +163,12 @@ impl ModuleError { | |||
pub fn raw(&self) -> RawModuleError { | |||
self.raw | |||
} | |||
|
|||
/// attempts to decode the ModuleError into a value implementing the trait `RootError` |
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.
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) :)
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.
Added a test and it all looks to work well to me; nice job!
@@ -320,10 +321,13 @@ impl RuntimeGenerator { | |||
should_gen_docs, | |||
)?; | |||
|
|||
let errors = errors::generate_error_type_alias(&type_gen, pallet, should_gen_docs)?; |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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,
}
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.
Nicely done!
The PR looks clean, I've left a few ideas that we could tackle in other PRs 👍
fixes #734.
Added an enum
Error
to 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
Error
type to be constructed from a pallet index (u8) and 4 error bytes:The
ModuleError
struct 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: