Description
Motivation
The upcoming actor exchanges messages over the network, where errors can occur. The current error enums we use are not serializable, and thus cannot be exchanged between actors. Another motivation may be to improve the currently stringly-typed errors in the WebAssembly bindings. In order to pass a value to JavaScript, it needs to be serializable. Errors also need to be exposable to future bindings, which often involves (de)serialization.
Description
Implement an error type that is serializable for over-the-network and bindings usage. WebAssembly bindings are a good case study. We currently use error enums in Rust, but any bindings that use a C FFI as the common interface, such as Wasm, can only use C-style enums. We have talked about this a number of times, and I want to summarize the approaches we've discussed, as it is not entirely clear yet, which is the best.
The general idea is to use an error code approach, and we've discussed variants of it. As an example we consider a part of the account error enum.
pub enum Error {
#[error("Stronghold snapshot password not found")]
StrongholdPasswordNotSet,
#[error("Stronghold error: {0}")]
StrongholdResult(String),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
StrongholdError(#[from] iota_stronghold::Error),
...
}
The simplest approach would be to map each of the variants to some error code. For the variants that contain associated data, information will be lost (or preserved in stringified form). On the receiver side, the error cannot be reconstructed, since not all information is available. Furthermore, since no enum is defined, the receiver only has the bare number but has a hard time attaching a meaning to it.
Thus, to build on this approach, we define roughly this Rust code:
pub struct Error {
code: ErrorCode,
stringified: String,
}
pub enum ErrorCode {
InternalError = 0,
StrongholdPasswordNotSet = 1,
StrongholdResult = 2,
...
}
To convert the example variants into this Error, we might do:
- the
StrongholdPasswordNotSet
variant is easily translated as some error code. - the
StrongholdResult
has an associatedString
. We can translate the variant itself to some number, and additionally transmit the stringified error. - The
IoError
is a fatal error from the other side, and the caller cannot do much about it, similar to a 5XX HTTP status. These errors can be mapped to a catch-allInternalError
(or similar) and also stringified to provide more detailed human-readable information. Some errors also should not be exposed, such asStrongholdMutexPoisoned
(not represented in the example above), which can similarly be mapped to the catch-all variant. - The
StrongholdError
can either be mapped to a singleStrongholdError
error code, or each of its variants can be mapped to error codes. The former approach means the caller has less information available, i.e. cannot programmatically match on specific variants. It is at least mitigated by the stringified version in terms of human readability. The latter approach includes a lot more maintenance burden. One other idea was to use two integers. E.g. to mapStrongholdError
, we store its error code in the first integer, and use the second integer to store error code of the inner variant. This has very high maintenance costs. To convert two integers back into a nested enum, we would need to copy the entire first two levels of nesting of theError
enum introduced above into new serializable enums.
There is perhaps more investigation needed to find the optimal approach. An implementation with unions might be viable, although given the unsafe
ty they introduce and the conversion that would also be necessary, similar to the previous approach, makes this less desirable.
Any feedback on this write-up is welcome!
Resources
To-do list
Create a task-specific to-do list . Please link PRs that match the To-do list item behind the item after it has been submitted.
- Find an error implementation that is maintainable, is as explicit as necessary, and memory-safe
- Implement the error system and provide conversions from the
identity_account::error::Error
and perhapsidentity_iota::error::Error
. - Ensure compatibility with WebAssembly bindings as early as possible, in particular conversion to exceptions
Change checklist
Add an x
to the boxes that are relevant to your changes, and delete any items that are not.
- The feature or fix is implemented in Rust and across all bindings whereas possible.
- The feature or fix has sufficient testing coverage
- All tests and examples build and run locally as expected
- Every piece of code has been document according to the documentation guidelines.
- If conceptual documentation (mdbook) and examples highlighting the feature exist, they are properly updated.
- If the feature is not currently documented, a documentation task Issue has been opened to address this.
Metadata
Metadata
Assignees
Type
Projects
Status