- There is a hierarchy of event traits:
Event
>RoomEvent
>StateEvent
- There is a Rust type for each kind of event (each allowed value for the 'type' field of the json representation), including custom [room / state] events
- There is also a content type for each event type
- In addition, there are "special" event types:
- the
Event
,RoomEvent
andStateEvent
enums fromcollections::all
contain every event type event / room event type / state event type respectively- =>
RoomEvent
contains all the same variants asStateEvent
, and more - =>
Event
contains all the same variants asRoomEvent
, and more
- =>
collections::only::Event
contains every event type that is not a room eventscollections::only::RoomEvent
contains every room event type that is not a state eventStrippedStateContent
is a generic wrapper for state event content types that has a smaller set of the common state-event fields- it is actually an event type rather than an event content type though, contrary to what the name suggests. I only started thinking about that when writing this though, no idea why it's named like this. Potentially, it's because the following enum already took the obvious name:
StrippedState
is an equivalent tocollections::all::StateEvent
where each variant is wrapped inStrippedStateContent
- the
In ruma-client
, we had the issue that some endpoint responses that returned lists of events
would fail deserialization if a single event contained would fail deserialization. It was a
massive pain in the ass for a long time (both concerning usability in general and concerning
figuring out the root cause of the deserialization fails).
Thus we figured we need to allow deserialization of event lists to succeed even if individual
events fail deserialization.
ruma-events 0.13 and 0.14 do this by removing Deserialize
impls, and adding FromStr
impls instead, and also, along the way, added event validation, in order to enforce additional
constraints the spec puts on events that are not represented in the types of the deserialized
fields. In order to implement event validation, each event type and event content type gained an
additional raw version of itself (under a private raw
submodule). This also helped keeping the
FromStr
implementations small, because the raw types, due to being private, could just derive
Deserialize.
When trying to port ruma-client-api
to ruma-events
0.14, I discovered that it wasn't really
possible. We couldn't derive Deserialize
for the endpoint response types anymore, which contained
event lists indirectly. So a new solution had to be found.
Somewhat recently, someone (I don't remember who) had the idea of changing the lists of event types
to lists of something akin to Result<EventT, serde_json::Value>
. (I discovered later that that
exact thing would actually work, but wouldn't keep the error message in case the event type's
deserialization logic would fail. Since serde only supports single-pass deserialization, this
fallback works by first deserializing to a generic value type that can represent anything serde can
deserialize, then trying to deserialize that to the Ok type, and if that fails, the Err type.
We wanted to do the same thing with error message preservation and also validation on top, so we
created EventResult
. Since everything else is tied to json currently and serde's value type is
private, we decided to implement this by first deserializing to json and trying to deserialize to
the event type afterwards. The fallback doesn't have to do any more deserialization work then,
because we already have a json value.
Validation in EventResult
until very recently was based on TryInto
(we wouldn't actually try
deserializing the json value to the final event type but its raw form, trying to convert via
TryInto
afterwards). However, that lead to coherence issues with the generic
StrippedStateContent
, which I solved by getting rid of the TryInto
and instead requiring an
fn try_from_raw
for each event type.
My work doesn't touch the Event
trait, but rather has all the abstractions required for
EventResult
deserialization to work in a separate trait EventResultCompatible
. This is because
a bunch of types (all of the event content types + StrippedEventContent
) don't actually implement
Event
, but need to be deserializable as EventResult<T>
. Because many EventResultCompatible
don't require validation, try_from_raw
has a generic error type (to allow implementations where
the compiler knows they're actually infallible).
Note to self: (I just realized this): the custom Void type used for infallible conversion could
most likely be replaced by std::convert::Infallible
; potentially we could even have a blanket
impl for try_from_raw (which would then again need to be in a separate trait I think) for types
with a From<RawT>
impl.