error-stack RFC: Enhancing Serialization and Deserialization for Reports #5352
indietyp
started this conversation in
Ideas (Libraries)
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This proposal introduces improvements for serializing and deserializing
error-stack
Report
s. Whileerror-stack
currently supports serialization, the functionality is limited and rigid. This RFC aims to address these limitations, proposing a more flexible approach to handlingReport
s during serialization.Motivation
The current serialization process for
Report
s loses critical information, making deserialization problematic. This RFC proposes a revised format that retains more information and is designed to be consistent with our existing debug output.Proposed Format
The new format differentiates between two types of objects:
Context
andAttachment
. While their serialized forms appear similar, they serve distinct roles. Below is the proposed structure:Context
Attachment
If
message
ordetails
is absent, it means those fields don't exist for that object. ThetypeId
helps during deserialization, acting as a unique identifier for reconstructing objects.For example:
This would serialize to:
Here, the attachment order is preserved. This mirrors the existing
Debug
implementation, where the order is maintained rather than reversed (which might be more intuitive from an implementation perspective).typeId
and Improving Serde HooksPast attempts to move
Debug
hook behavior toserde
have encountered issues, especially with deserialization. Specifically, important metadata is lost during serialization, making accurate reconstruction impossible.The proposed
typeId
addresses this. It's akin toAssetId::Uuid
in Bevy. For each pair of serde operations, we assign a (constant or non-constant) UUID to identify and register the type. WhileTypeId
could serve a similar purpose, it isn’t stable across compilations, rendering it unsuitable for storage. A static user UUID solves this problem, albeit with some additional friction, which can be mitigated via a derive macro.The API might look like this:
In this example, the
StableTypeId
trait provides a stableu128
identifier that helps track the type during serialization and deserialization, this is just aUuid
, but removing the reliance onUuid
allows easier implementations for the end-user. TheHook<T>
struct allows users to register custom serialization logic for specific types. Theauto
method automatically handles this for types implementingserde
, whilemanual
allows for more control over the process.During deserialization, the system checks the
typeId
and attempts to deserialize using the registered deserializer. If deserialization fails (for example, if the type wasn’t registered), the system falls back to a default approach instead of throwing an error.Deserialization Fallback
When deserialization fails due to e.g. a missing or unknown
typeId
, we introduce two fallback constructs:AnyAttachment
andAnyContext
(name TBD). These types capture the data that couldn't be deserialized:This ensures deserialization never fails entirely, and users can still retrieve the available data. We could then provide an API like
AnyContext::downcast<T>
whereT: serde::Deserialize
to attempt a downcast to the appropriate type.In cases where the last
Context
is unknown an end-user could fall back to anAnyReport
, which is a simply an alias toReport<AnyContext>
. While this potentially loses some information, it allows to reconstruct the most essential parts.Improving the API for Lossy Serialization
Currently, one major downside is the inflexibility of serializing attachments. For every new capability, such as serialization or printing, we would need to create new combinations of methods like
attach_printable
orattach_serializable
. As a result, the number of possible combinations grows exponentially, leading to unsustainable API design, and confusion to the end-user.This serialization is considered lossy, because unlike the hook implementation there is no requirement for a type id, making it impossible to deserialize it with the correct type. They would still be deserializable, but as a
AnyAttachment
instead ofT
.To simplify this, we propose a new
Attachment<T>
type. This abstraction allows the user to attach data with flexible capabilities without needing multiple API combinations. The newAttachment
type would work as follows:With this change, the
Report::attach
method would have the signature:The
Attachment
struct would provide methods for adding capabilities like printing or serialization in a chainable manner:Here’s an example of how this change reflects itself in the API:
Furthermore, to maintain backward compatibility and ease of use
attach_printable
will remain available and will not be deprecated:Future Possibilities
While I am usually not a fan of life before main, one could imagine a derive macro like
thiserror::Error
, that also registers the context automatically via a serde hook. This also the pro of us being able to support thingsthiserror
isn't planning any time soon, like propergeneric_member_access
support. This is definitely not needed in this version, but something to think about in potentially a future version, it could look like:Beta Was this translation helpful? Give feedback.
All reactions