Skip to content

[Task] Implement a serializable error #321

Open
@PhilippGackstatter

Description

@PhilippGackstatter

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 associated String. 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-all InternalError (or similar) and also stringified to provide more detailed human-readable information. Some errors also should not be exposed, such as StrongholdMutexPoisoned (not represented in the example above), which can similarly be mapped to the catch-all variant.
  • The StrongholdError can either be mapped to a single StrongholdError 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 map StrongholdError, 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 the Error 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 unsafety 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 perhaps identity_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

No one assigned

    Labels

    EnhancementNew feature or improvement to an existing feature

    Type

    No type

    Projects

    Status

    Product Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions