Skip to content
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

Add is_compatible method to event content enums to fix Any*Event deserialization #52

Merged
merged 5 commits into from Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
@@ -1,2 +1,2 @@
/target
target
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

criterion generates a ruma-events/target dir I figured this should be ignored?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I don't like it doing that, but I guess we can't do much about it.

Cargo.lock
16 changes: 16 additions & 0 deletions ruma-events-macros/src/content_enum.rs
Expand Up @@ -8,9 +8,12 @@ use syn::{
};

/// Create a content enum from `ContentEnumInput`.
///
/// This is the internals of the `event_content_enum!` macro.
pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result<TokenStream> {
let attrs = &input.attrs;
let ident = &input.name;

let event_type_str = &input.events;

let variants = input.events.iter().map(to_camel_case).collect::<Vec<_>>();
Expand Down Expand Up @@ -51,11 +54,24 @@ pub fn expand_content_enum(input: ContentEnumInput) -> syn::Result<TokenStream>
}
};

let any_event_variant_impl = quote! {
impl #ident {
fn is_compatible(event_type: &str) -> bool {
match event_type {
#( #event_type_str => true, )*
_ => false,
}
}
}
};

let marker_trait_impls = marker_traits(ident);

Ok(quote! {
#content_enum

#any_event_variant_impl

#event_content_impl

#marker_trait_impls
Expand Down
5 changes: 5 additions & 0 deletions ruma-events/Cargo.toml
Expand Up @@ -27,3 +27,8 @@ maplit = "1.0.2"
matches = "0.1.8"
ruma-identifiers = { version = "0.16.2", path = "../ruma-identifiers", features = ["rand"] }
trybuild = "1.0.28"
criterion = "0.3.2"

[[bench]]
name = "event_deserialize"
harness = false
109 changes: 109 additions & 0 deletions ruma-events/benches/event_deserialize.rs
@@ -0,0 +1,109 @@
// `cargo bench` works, but if you use `cargo bench -- --save-baseline <name>`
// or pass any other args to it, it fails with the error
// `cargo bench unknown option --save-baseline`.
// To pass args to criterion, use this form
// `cargo bench --bench <name of the bench> -- --save-baseline <name>`.

use criterion::{criterion_group, criterion_main, Criterion};
use ruma_events::{
room::power_levels::PowerLevelsEventContent, AnyEvent, AnyRoomEvent, AnyStateEvent, EventJson,
StateEvent,
};
use serde_json::json;

fn power_levels() -> serde_json::Value {
json!({
"content": {
"ban": 50,
"events": {
"m.room.avatar": 50,
"m.room.canonical_alias": 50,
"m.room.history_visibility": 100,
"m.room.name": 50,
"m.room.power_levels": 100
},
"events_default": 0,
"invite": 0,
"kick": 50,
"redact": 50,
"state_default": 50,
"users": {
"@example:localhost": 100
},
"users_default": 0
},
"event_id": "$15139375512JaHAW:localhost",
"origin_server_ts": 45,
"sender": "@example:localhost",
"room_id": "!room:localhost",
"state_key": "",
"type": "m.room.power_levels",
"unsigned": {
"age": 45
}
})
}

fn deserialize_any_event(c: &mut Criterion) {
let json_data = power_levels();

c.bench_function("deserialize to `AnyEvent`", |b| {
b.iter(|| {
let _ = serde_json::from_value::<EventJson<AnyEvent>>(json_data.clone())
.unwrap()
.deserialize()
.unwrap();
})
});
}

fn deserialize_any_room_event(c: &mut Criterion) {
let json_data = power_levels();

c.bench_function("deserialize to `AnyRoomEvent`", |b| {
b.iter(|| {
let _ = serde_json::from_value::<EventJson<AnyRoomEvent>>(json_data.clone())
.unwrap()
.deserialize()
.unwrap();
})
});
}

fn deserialize_any_state_event(c: &mut Criterion) {
let json_data = power_levels();

c.bench_function("deserialize to `AnyStateEvent`", |b| {
b.iter(|| {
let _ = serde_json::from_value::<EventJson<AnyStateEvent>>(json_data.clone())
.unwrap()
.deserialize()
.unwrap();
})
});
}

fn deserialize_specific_event(c: &mut Criterion) {
let json_data = power_levels();

c.bench_function("deserialize to `StateEvent<PowerLevelsEventContent>`", |b| {
b.iter(|| {
let _ = serde_json::from_value::<EventJson<StateEvent<PowerLevelsEventContent>>>(
json_data.clone(),
)
.unwrap()
.deserialize()
.unwrap();
})
});
}

criterion_group!(
benches,
deserialize_any_event,
deserialize_any_room_event,
deserialize_any_state_event,
deserialize_specific_event
);

criterion_main!(benches);
95 changes: 90 additions & 5 deletions ruma-events/src/enums.rs
@@ -1,5 +1,9 @@
use ruma_events_macros::event_content_enum;
use serde::{Deserialize, Serialize};
use serde::{
de::{self, Error as _},
Serialize,
};
use serde_json::{from_value as from_json_value, Value as JsonValue};

use crate::{
event_kinds::{
Expand All @@ -8,6 +12,7 @@ use crate::{
},
presence::PresenceEvent,
room::redaction::{RedactionEvent, RedactionEventStub},
util,
};

event_content_enum! {
Expand Down Expand Up @@ -116,7 +121,7 @@ pub type AnyStrippedStateEventStub = StrippedStateEventStub<AnyStateEventContent
pub type AnyToDeviceEvent = ToDeviceEvent<AnyToDeviceEventContent>;

/// Any event.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnyEvent {
/// Any basic event.
Expand All @@ -134,7 +139,7 @@ pub enum AnyEvent {
}

/// Any room event.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnyRoomEvent {
/// Any message event.
Expand All @@ -146,13 +151,93 @@ pub enum AnyRoomEvent {
}

/// Any room event stub (room event without a `room_id`, as returned in `/sync` responses)
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnyRoomEventStub {
/// Any message event stub
Message(AnyMessageEventStub),
/// `"m.room.redaction"` stub
Redaction(RedactionEventStub),
/// Any state event stub
StateEvent(AnyStateEventStub),
State(AnyStateEventStub),
DevinR528 marked this conversation as resolved.
Show resolved Hide resolved
}

// FIXME `#[serde(untagged)]` deserialization fails for these enums which
// is odd as we are doing basically the same thing here, investigate?
impl<'de> de::Deserialize<'de> for AnyEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let json = JsonValue::deserialize(deserializer)?;
let ev_type: String = util::get_field(&json, "type")?;

match ev_type.as_str() {
"m.room.redaction" => {
Ok(AnyEvent::Redaction(from_json_value(json).map_err(D::Error::custom)?))
}
"m.presence" => {
Ok(AnyEvent::Presence(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyBasicEventContent::is_compatible(ev_type) => {
Ok(AnyEvent::Basic(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyEphemeralRoomEventContent::is_compatible(ev_type) => {
Ok(AnyEvent::Ephemeral(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyMessageEventContent::is_compatible(ev_type) => {
Ok(AnyEvent::Message(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyStateEventContent::is_compatible(ev_type) => {
Ok(AnyEvent::State(from_json_value(json).map_err(D::Error::custom)?))
}
_ => Err(D::Error::custom(format!("event type `{}` is not a valid event", ev_type))),
}
}
}

impl<'de> de::Deserialize<'de> for AnyRoomEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let json = JsonValue::deserialize(deserializer)?;
let ev_type: String = util::get_field(&json, "type")?;

match ev_type.as_str() {
"m.room.redaction" => {
Ok(AnyRoomEvent::Redaction(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyMessageEventContent::is_compatible(ev_type) => {
Ok(AnyRoomEvent::Message(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyStateEventContent::is_compatible(ev_type) => {
Ok(AnyRoomEvent::State(from_json_value(json).map_err(D::Error::custom)?))
}
_ => Err(D::Error::custom(format!("event type `{}` is not a valid event", ev_type))),
}
}
}

impl<'de> de::Deserialize<'de> for AnyRoomEventStub {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let json = JsonValue::deserialize(deserializer)?;
let ev_type: String = util::get_field(&json, "type")?;

match ev_type.as_str() {
"m.room.redaction" => {
Ok(AnyRoomEventStub::Redaction(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyMessageEventContent::is_compatible(ev_type) => {
Ok(AnyRoomEventStub::Message(from_json_value(json).map_err(D::Error::custom)?))
}
ev_type if AnyStateEventContent::is_compatible(ev_type) => {
Ok(AnyRoomEventStub::State(from_json_value(json).map_err(D::Error::custom)?))
}
_ => Err(D::Error::custom(format!("event type `{}` is not a valid event", ev_type))),
}
}
}