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

Fix deserialization of redacted aliases events #128

Closed
wants to merge 6 commits into from
Closed
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
17 changes: 12 additions & 5 deletions ruma-events-macros/src/event.rs
Expand Up @@ -151,11 +151,18 @@ fn expand_deserialize_event(
if name == "content" {
if is_generic && ident.to_string().contains("Redacted") {
quote! {
let content = if !C::has_deserialize_fields() {
C::empty(&event_type).map_err(A::Error::custom)?
} else {
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
C::from_parts(&event_type, json).map_err(A::Error::custom)?
let content = match C::has_deserialize_fields() {
::ruma_events::HasDeserializeFields::False => {
C::empty(&event_type).map_err(A::Error::custom)?
},
::ruma_events::HasDeserializeFields::True => {
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
C::from_parts(&event_type, json).map_err(A::Error::custom)?
},
::ruma_events::HasDeserializeFields::Optional => {
let json = content.unwrap_or(::serde_json::value::RawValue::from_string("{}".to_string()).unwrap());
C::from_parts(&event_type, json).map_err(A::Error::custom)?
},
};
}
} else if is_generic {
Expand Down
14 changes: 10 additions & 4 deletions ruma-events-macros/src/event_content.rs
Expand Up @@ -135,7 +135,13 @@ pub fn expand_event_content(input: &DeriveInput, emit_redacted: bool) -> syn::Re
)
};

let has_fields = if kept_redacted_fields.is_empty() {
let has_deserialize_fields = if kept_redacted_fields.is_empty() {
quote! { ::ruma_events::HasDeserializeFields::False }
} else {
quote! { ::ruma_events::HasDeserializeFields::True }
};

let has_serialize_fields = if kept_redacted_fields.is_empty() {
quote! { false }
} else {
quote! { true }
Expand Down Expand Up @@ -170,11 +176,11 @@ pub fn expand_event_content(input: &DeriveInput, emit_redacted: bool) -> syn::Re
}

fn has_serialize_fields(&self) -> bool {
#has_fields
#has_serialize_fields
}

fn has_deserialize_fields() -> bool {
#has_fields
fn has_deserialize_fields() -> ::ruma_events::HasDeserializeFields {
#has_deserialize_fields
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions ruma-events/src/custom.rs
Expand Up @@ -4,9 +4,9 @@ use serde::Serialize;
use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};

use crate::{
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
RedactedEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent,
StateEventContent,
BasicEventContent, EphemeralRoomEventContent, EventContent, HasDeserializeFields,
MessageEventContent, RedactedEventContent, RedactedMessageEventContent,
RedactedStateEventContent, RoomEventContent, StateEventContent,
};

/// A custom event's type and `content` JSON object.
Expand Down Expand Up @@ -83,8 +83,8 @@ impl RedactedEventContent for RedactedCustomEventContent {
false
}

fn has_deserialize_fields() -> bool {
false
fn has_deserialize_fields() -> HasDeserializeFields {
HasDeserializeFields::False
}
}

Expand Down
26 changes: 24 additions & 2 deletions ruma-events/src/lib.rs
Expand Up @@ -243,20 +243,27 @@ pub trait StateEventContent: RoomEventContent {}

/// The base trait that all redacted event content types implement.
///
/// Implementing this trait allows content types to be serialized as well as deserialized.
/// This trait's associated functions and methods should not be used to build
/// redacted events, prefer the `redact` method on `AnyStateEvent` and
/// `AnyMessageEvent` and their "sync" and "stripped" counterparts. The
/// `RedactedEventContent` trait is an implementation detail, ruma makes no
/// API guarantees.
pub trait RedactedEventContent: EventContent {
/// Constructs the redacted event content.
///
/// If called for anything but "empty" redacted content this will error.
#[doc(hidden)]
fn empty(_event_type: &str) -> Result<Self, serde_json::Error> {
Err(serde::de::Error::custom("this event is not redacted"))
}

/// Determines if the redacted event content needs to serialize fields.
#[doc(hidden)]
fn has_serialize_fields(&self) -> bool;

/// Determines if the redacted event content needs to deserialize fields.
fn has_deserialize_fields() -> bool;
#[doc(hidden)]
fn has_deserialize_fields() -> HasDeserializeFields;
}

/// Marker trait for the content of a redacted message event.
Expand All @@ -265,6 +272,21 @@ pub trait RedactedMessageEventContent: RedactedEventContent {}
/// Marker trait for the content of a redacted state event.
pub trait RedactedStateEventContent: RedactedEventContent {}

/// `HasDeserializeFields` is used in the code generated by the `Event` derive
/// to aid in deserializing redacted events.
#[doc(hidden)]
#[derive(Debug)]
pub enum HasDeserializeFields {
/// Deserialize the event's content, failing if invalid.
True,
/// Return the redacted version of this event's content.
False,
/// `Optional` is used for `RedactedAliasesEventContent` since it has
/// an empty version and one with content left after redaction that
/// must be supported together.
Optional,
}

/// Helper struct to determine if the event has been redacted.
#[doc(hidden)]
#[derive(Debug, Deserialize)]
Expand Down
8 changes: 5 additions & 3 deletions ruma-events/src/room/aliases.rs
Expand Up @@ -5,7 +5,9 @@ use ruma_identifiers::RoomAliasId;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;

use crate::{EventContent, RedactedEventContent, RedactedStateEventContent, StateEvent};
use crate::{
EventContent, HasDeserializeFields, RedactedEventContent, RedactedStateEventContent, StateEvent,
};

/// Informs the room about what room aliases it has been given.
pub type AliasesEvent = StateEvent<AliasesEventContent>;
Expand Down Expand Up @@ -53,8 +55,8 @@ impl RedactedEventContent for RedactedAliasesEventContent {
self.aliases.is_some()
}

fn has_deserialize_fields() -> bool {
true
fn has_deserialize_fields() -> HasDeserializeFields {
HasDeserializeFields::Optional
}
}

Expand Down
123 changes: 76 additions & 47 deletions ruma-events/tests/redacted.rs
Expand Up @@ -19,8 +19,22 @@ use ruma_events::{
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

fn is_zst<T>(_: &T) -> bool {
std::mem::size_of::<T>() == 0
fn full_unsigned() -> UnsignedData {
let mut unsigned = UnsignedData::default();
// The presence of `redacted_because` triggers the event enum to return early
// with `RedactedContent` instead of failing to deserialize according
// to the event type string.
unsigned.redacted_because = Some(EventJson::from(RedactionEvent {
content: RedactionEventContent { reason: Some("redacted because".into()) },
redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(),
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
}));

unsigned
}

#[test]
Expand All @@ -45,7 +59,7 @@ fn redacted_message_event_serialize() {
}

#[test]
fn redacted_aliases_event_serialize() {
fn redacted_aliases_event_serialize_no_content() {
let redacted = RedactedSyncStateEvent {
content: RedactedAliasesEventContent { aliases: None },
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
Expand All @@ -68,20 +82,62 @@ fn redacted_aliases_event_serialize() {
}

#[test]
fn redacted_deserialize_any_room() {
let mut unsigned = UnsignedData::default();
// The presence of `redacted_because` triggers the event enum (AnyRoomEvent in this case)
// to return early with `RedactedContent` instead of failing to deserialize according
// to the event type string.
unsigned.redacted_because = Some(EventJson::from(RedactionEvent {
content: RedactionEventContent { reason: Some("redacted because".into()) },
redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(),
fn redacted_aliases_event_serialize_with_content() {
let redacted = RedactedSyncStateEvent {
content: RedactedAliasesEventContent { aliases: Some(vec![]) },
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
state_key: "".to_string(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
}));
};

let expected = json!({
"content": {
"aliases": []
},
"event_id": "$h29iv0s8:example.com",
"state_key": "",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"type": "m.room.aliases"
});

let actual = to_json_value(&redacted).unwrap();
assert_eq!(actual, expected);
}

#[test]
fn redacted_aliases_deserialize() {
let unsigned = full_unsigned();

let redacted = json!({
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"state_key": "hello",
"unsigned": unsigned,
"type": "m.room.aliases"
});

let actual = to_json_value(&redacted).unwrap();

assert_matches!(
from_json_value::<EventJson<AnySyncRoomEvent>>(actual)
.unwrap()
.deserialize()
.unwrap(),
AnySyncRoomEvent::RedactedState(AnyRedactedSyncStateEvent::RoomAliases(RedactedSyncStateEvent {
content: RedactedAliasesEventContent { aliases },
event_id, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& aliases.is_none()
)
}

#[test]
fn redacted_deserialize_any_room() {
let unsigned = full_unsigned();

let redacted = json!({
"event_id": "$h29iv0s8:example.com",
Expand All @@ -100,10 +156,10 @@ fn redacted_deserialize_any_room() {
.deserialize()
.unwrap(),
AnyRoomEvent::RedactedMessage(AnyRedactedMessageEvent::RoomMessage(RedactedMessageEvent {
event_id, room_id, content, ..
content: RedactedMessageEventContent,
event_id, room_id, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& room_id == RoomId::try_from("!roomid:room.com").unwrap()
&& is_zst(&content)
)
}

Expand Down Expand Up @@ -139,24 +195,15 @@ fn redacted_deserialize_any_room_sync() {
.deserialize()
.unwrap(),
AnySyncRoomEvent::RedactedMessage(AnyRedactedSyncMessageEvent::RoomMessage(RedactedSyncMessageEvent {
event_id, content, ..
content: RedactedMessageEventContent,
event_id, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& is_zst(&content)
)
}

#[test]
fn redacted_state_event_deserialize() {
let mut unsigned = UnsignedData::default();
unsigned.redacted_because = Some(EventJson::from(RedactionEvent {
content: RedactionEventContent { reason: Some("redacted because".into()) },
redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(),
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
}));
let unsigned = full_unsigned();

let redacted = json!({
"content": {
Expand Down Expand Up @@ -189,16 +236,7 @@ fn redacted_state_event_deserialize() {

#[test]
fn redacted_custom_event_serialize() {
let mut unsigned = UnsignedData::default();
unsigned.redacted_because = Some(EventJson::from(RedactionEvent {
content: RedactionEventContent { reason: Some("redacted because".into()) },
redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(),
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
}));
let unsigned = full_unsigned();

let redacted = json!({
"event_id": "$h29iv0s8:example.com",
Expand Down Expand Up @@ -234,16 +272,7 @@ fn redacted_custom_event_serialize() {

#[test]
fn redacted_custom_event_deserialize() {
let mut unsigned = UnsignedData::default();
unsigned.redacted_because = Some(EventJson::from(RedactionEvent {
content: RedactionEventContent { reason: Some("redacted because".into()) },
redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(),
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
room_id: RoomId::try_from("!roomid:room.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
}));
let unsigned = full_unsigned();

let redacted = RedactedSyncStateEvent {
content: RedactedCustomEventContent { event_type: "m.made.up".into() },
Expand Down