Add support for redacted events

* Generate redacted event enums and implement corresponding event structs
* Enable the *EventContent derives to generate redacted events

  Most redacted event code is now generated by the *EventContent derive
  macro. The exception are any content structs with the custom_redaction
  attribute. This leaves implementing up to the user.
* Add redact method to Redaction/CustomEventContent
* Add accessor methods for redacted event enums
* Add RedactedEventContent trait and super traits to match EventContent
This commit is contained in:
Ragotzy.devin 2020-07-11 08:59:36 -04:00 committed by GitHub
parent c19bcaab31
commit 5e428ac95a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1070 additions and 202 deletions

View File

@ -37,7 +37,13 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
.iter()
.map(|field| {
let name = field.ident.as_ref().unwrap();
if name == "prev_content" {
if name == "content" && ident.to_string().contains("Redacted") {
quote! {
if ::ruma_events::RedactedEventContent::has_serialize_fields(&self.content) {
state.serialize_field("content", &self.content)?;
}
}
} else if name == "prev_content" {
quote! {
if let Some(content) = self.prev_content.as_ref() {
state.serialize_field("prev_content", content)?;
@ -143,7 +149,16 @@ fn expand_deserialize_event(
.map(|field| {
let name = field.ident.as_ref().unwrap();
if name == "content" {
if is_generic {
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)?
};
}
} else if is_generic {
quote! {
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
let content = C::from_parts(&event_type, json).map_err(A::Error::custom)?;

View File

@ -4,31 +4,70 @@ use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
DeriveInput, LitStr, Token,
DeriveInput, Ident, LitStr, Token,
};
mod kw {
// This `content` field is kept when the event is redacted.
syn::custom_keyword!(skip_redaction);
// Do not emit any redacted event code.
syn::custom_keyword!(custom_redacted);
}
/// Parses attributes for `*EventContent` derives.
///
/// `#[ruma_event(type = "m.room.alias")]`
#[derive(Eq, PartialEq)]
enum EventMeta {
/// Variant holds the "m.whatever" event type.
Type(LitStr),
/// Fields marked with `#[ruma_event(skip_redaction)]` are kept when the event is
/// redacted.
SkipRedacted,
/// This attribute signals that the events redacted form is manually implemented and should
/// not be generated.
CustomRedacted,
}
impl EventMeta {
fn get_event_type(&self) -> Option<&LitStr> {
if let Self::Type(lit) = self {
Some(lit)
} else {
None
}
}
}
impl Parse for EventMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![type]>()?;
if input.parse::<Token![type]>().is_ok() {
input.parse::<Token![=]>()?;
Ok(EventMeta::Type(input.parse::<LitStr>()?))
} else if input.parse::<kw::skip_redaction>().is_ok() {
Ok(EventMeta::SkipRedacted)
} else if input.parse::<kw::custom_redacted>().is_ok() {
Ok(EventMeta::CustomRedacted)
} else {
Err(syn::Error::new(input.span(), "not a recognized `ruma_event` attribute"))
}
}
}
/// Create an `EventContent` implementation for a struct.
pub fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
pub fn expand_event_content(input: &DeriveInput, emit_redacted: bool) -> syn::Result<TokenStream> {
let ident = &input.ident;
let event_type_attr =
input.attrs.iter().find(|attr| attr.path.is_ident("ruma_event")).ok_or_else(|| {
let content_attr = input
.attrs
.iter()
.filter(|attr| attr.path.is_ident("ruma_event"))
.map(|attr| attr.parse_args::<EventMeta>())
.collect::<syn::Result<Vec<_>>>()?;
let event_type = content_attr.iter().find_map(|a| a.get_event_type()).ok_or_else(|| {
let msg = "no event type attribute found, \
add `#[ruma_event(type = \"any.room.event\")]` \
below the event content derive";
@ -36,13 +75,206 @@ pub fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
syn::Error::new(Span::call_site(), msg)
})?;
let event_type = {
let event_meta = event_type_attr.parse_args::<EventMeta>()?;
let EventMeta::Type(lit) = event_meta;
lit
let redacted = if emit_redacted && needs_redacted(input) {
let doc = format!("The payload for a redacted `{}`", ident);
let redacted_ident = quote::format_ident!("Redacted{}", ident);
let kept_redacted_fields = if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { named, .. }),
..
}) = &input.data
{
// this is to validate the `#[ruma_event(skip_redaction)]` attribute
named
.iter()
.flat_map(|f| &f.attrs)
.filter(|a| a.path.is_ident("ruma_event"))
.find_map(|a| {
if let Err(e) = a.parse_args::<EventMeta>() {
Some(Err(e))
} else {
None
}
})
.unwrap_or(Ok(()))?;
let mut fields = named
.iter()
.filter(|f| {
f.attrs.iter().find_map(|a| a.parse_args::<EventMeta>().ok())
== Some(EventMeta::SkipRedacted)
})
.cloned()
.collect::<Vec<_>>();
// don't re-emit our `ruma_event` attributes
for f in &mut fields {
f.attrs.retain(|a| !a.path.is_ident("ruma_event"));
}
fields
} else {
vec![]
};
let redaction_struct_fields = kept_redacted_fields.iter().flat_map(|f| &f.ident);
// redacted_fields allows one to declare an empty redacted event without braces,
// otherwise `RedactedWhateverEventContent {}` is needed.
// The redacted_return is used in `EventContent::redacted` which only returns
// zero sized types (unit structs).
let (redacted_fields, redacted_return) = if kept_redacted_fields.is_empty() {
(quote! { ; }, quote! { Ok(#redacted_ident {}) })
} else {
(
quote! {
{ #( #kept_redacted_fields, )* }
},
quote! {
Err(::serde::de::Error::custom(
format!("this redacted event has fields that cannot be constructed")
))
},
)
};
let has_fields = if kept_redacted_fields.is_empty() {
quote! { false }
} else {
quote! { true }
};
let redacted_event_content = generate_event_content_impl(&redacted_ident, event_type);
quote! {
// this is the non redacted event content's impl
impl #ident {
/// Transforms the full event content into a redacted content according to spec.
pub fn redact(self) -> #redacted_ident {
#redacted_ident { #( #redaction_struct_fields: self.#redaction_struct_fields, )* }
}
}
#[doc = #doc]
#[derive(Clone, Debug, ::serde::Deserialize, ::serde::Serialize)]
pub struct #redacted_ident #redacted_fields
#redacted_event_content
impl ::ruma_events::RedactedEventContent for #redacted_ident {
fn empty(ev_type: &str) -> Result<Self, ::serde_json::Error> {
if ev_type != #event_type {
return Err(::serde::de::Error::custom(
format!("expected event type `{}`, found `{}`", #event_type, ev_type)
));
}
#redacted_return
}
fn has_serialize_fields(&self) -> bool {
#has_fields
}
fn has_deserialize_fields() -> bool {
#has_fields
}
}
}
} else {
TokenStream::new()
};
let event_content = generate_event_content_impl(ident, event_type);
Ok(quote! {
#event_content
#redacted
})
}
/// Create a `BasicEventContent` implementation for a struct
pub fn expand_basic_event_content(input: &DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input, false)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::BasicEventContent for #ident { }
})
}
/// Create a `EphemeralRoomEventContent` implementation for a struct
pub fn expand_ephemeral_room_event_content(input: &DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input, false)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::EphemeralRoomEventContent for #ident { }
})
}
/// Create a `RoomEventContent` implementation for a struct.
pub fn expand_room_event_content(input: &DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input, true)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::RoomEventContent for #ident { }
})
}
/// Create a `MessageEventContent` implementation for a struct
pub fn expand_message_event_content(input: &DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let room_ev_content = expand_room_event_content(input)?;
let redacted_marker_trait = if needs_redacted(input) {
let ident = quote::format_ident!("Redacted{}", &ident);
quote! {
impl ::ruma_events::RedactedMessageEventContent for #ident { }
}
} else {
TokenStream::new()
};
Ok(quote! {
#room_ev_content
impl ::ruma_events::MessageEventContent for #ident { }
#redacted_marker_trait
})
}
/// Create a `StateEventContent` implementation for a struct
pub fn expand_state_event_content(input: &DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let room_ev_content = expand_room_event_content(input)?;
let redacted_marker_trait = if needs_redacted(input) {
let ident = quote::format_ident!("Redacted{}", input.ident);
quote! {
impl ::ruma_events::RedactedStateEventContent for #ident { }
}
} else {
TokenStream::new()
};
Ok(quote! {
#room_ev_content
impl ::ruma_events::StateEventContent for #ident { }
#redacted_marker_trait
})
}
fn generate_event_content_impl(ident: &Ident, event_type: &LitStr) -> TokenStream {
quote! {
impl ::ruma_events::EventContent for #ident {
fn event_type(&self) -> &str {
#event_type
@ -61,65 +293,14 @@ pub fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
::serde_json::from_str(content.get())
}
}
})
}
}
/// Create a `BasicEventContent` implementation for a struct
pub fn expand_basic_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::BasicEventContent for #ident { }
})
}
/// Create a `EphemeralRoomEventContent` implementation for a struct
pub fn expand_ephemeral_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::EphemeralRoomEventContent for #ident { }
})
}
/// Create a `RoomEventContent` implementation for a struct.
pub fn expand_room_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let event_content_impl = expand_event_content(input)?;
Ok(quote! {
#event_content_impl
impl ::ruma_events::RoomEventContent for #ident { }
})
}
/// Create a `MessageEventContent` implementation for a struct
pub fn expand_message_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let room_ev_content = expand_room_event_content(input)?;
Ok(quote! {
#room_ev_content
impl ::ruma_events::MessageEventContent for #ident { }
})
}
/// Create a `StateEventContent` implementation for a struct
pub fn expand_state_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident.clone();
let room_ev_content = expand_room_event_content(input)?;
Ok(quote! {
#room_ev_content
impl ::ruma_events::StateEventContent for #ident { }
})
fn needs_redacted(input: &DeriveInput) -> bool {
input
.attrs
.iter()
.flat_map(|a| a.parse_args::<EventMeta>().ok())
.find(|a| a == &EventMeta::CustomRedacted)
.is_none()
}

View File

@ -10,16 +10,40 @@ use syn::{
use crate::event_names::{
ANY_BASIC_EVENT, ANY_EPHEMERAL_EVENT, ANY_MESSAGE_EVENT, ANY_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_SYNC_STATE_EVENT, ANY_TO_DEVICE_EVENT,
REDACTED_MESSAGE_EVENT, REDACTED_STATE_EVENT, REDACTED_STRIPPED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT, REDACTED_SYNC_STATE_EVENT,
};
// Arrays of event enum names grouped by a field they share in common.
const ROOM_EVENT_KIND: &[&str] =
&[ANY_MESSAGE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
const ROOM_EVENT_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_SYNC_STATE_EVENT,
];
const ROOM_ID_KIND: &[&str] = &[ANY_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_EPHEMERAL_EVENT];
const ROOM_ID_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_EPHEMERAL_EVENT,
REDACTED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
];
const EVENT_ID_KIND: &[&str] =
&[ANY_MESSAGE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
const EVENT_ID_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
REDACTED_SYNC_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
];
const SENDER_KIND: &[&str] = &[
ANY_MESSAGE_EVENT,
@ -28,11 +52,31 @@ const SENDER_KIND: &[&str] = &[
ANY_TO_DEVICE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
ANY_STRIPPED_STATE_EVENT,
REDACTED_MESSAGE_EVENT,
REDACTED_STATE_EVENT,
REDACTED_STRIPPED_STATE_EVENT,
REDACTED_SYNC_MESSAGE_EVENT,
REDACTED_SYNC_STATE_EVENT,
];
const PREV_CONTENT_KIND: &[&str] = &[ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
const STATE_KEY_KIND: &[&str] = &[ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT, ANY_STRIPPED_STATE_EVENT];
const STATE_KEY_KIND: &[&str] = &[
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT,
REDACTED_SYNC_STATE_EVENT,
REDACTED_STRIPPED_STATE_EVENT,
REDACTED_STATE_EVENT,
];
const REDACTED_EVENT_KIND: &[&str] = &[
ANY_STATE_EVENT,
ANY_SYNC_STATE_EVENT,
ANY_STRIPPED_STATE_EVENT,
ANY_MESSAGE_EVENT,
ANY_SYNC_MESSAGE_EVENT,
];
/// This const is used to generate the accessor methods for the `Any*Event` enums.
///
@ -69,8 +113,14 @@ pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
let event_stripped_enum =
if needs_stripped_event { expand_stripped_enum(&input)? } else { TokenStream::new() };
let redacted_event_enums = if needs_redacted(ident) {
expand_any_redacted_enum_with_deserialize(&input, ident)?
} else {
TokenStream::new()
};
let event_content_enum =
if needs_event_content { expand_content_enum(&input)? } else { TokenStream::new() };
if needs_event_content { expand_content_enum(&input, ident)? } else { TokenStream::new() };
Ok(quote! {
#event_enum
@ -79,6 +129,8 @@ pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
#event_stripped_enum
#redacted_event_enums
#event_content_enum
})
}
@ -97,10 +149,59 @@ pub fn expand_stripped_enum(input: &EventEnumInput) -> syn::Result<TokenStream>
expand_any_enum_with_deserialize(input, &ident)
}
/// Generates the 3 redacted state enums, 2 redacted message enums,
/// and `Deserialize` implementations.
///
/// No content enums are generated since no part of the API deals with
/// redacted event's content. There are only five state variants that contain content.
fn expand_any_redacted_enum_with_deserialize(
input: &EventEnumInput,
ident: &Ident,
) -> syn::Result<TokenStream> {
let name = ident.to_string().trim_start_matches("Any").to_string();
let redacted_enums_deserialize = if ident.to_string().contains("State") {
let ident = Ident::new(&format!("AnyRedacted{}", name), ident.span());
let full = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedacted{}Stub", name), ident.span());
let stub = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedactedStripped{}Stub", name), ident.span());
let stripped = expand_any_enum_with_deserialize(input, &ident)?;
quote! {
#full
#stub
#stripped
}
} else {
let ident = Ident::new(&format!("AnyRedacted{}", name), ident.span());
let full = expand_any_enum_with_deserialize(input, &ident)?;
let ident = Ident::new(&format!("AnyRedacted{}Stub", name), ident.span());
let stub = expand_any_enum_with_deserialize(input, &ident)?;
quote! {
#full
#stub
}
};
Ok(quote! {
#redacted_enums_deserialize
})
}
/// Create a content enum from `EventEnumInput`.
pub fn expand_content_enum(input: &EventEnumInput) -> syn::Result<TokenStream> {
pub fn expand_content_enum(input: &EventEnumInput, ident: &Ident) -> syn::Result<TokenStream> {
let attrs = &input.attrs;
let ident = Ident::new(&format!("{}Content", input.name.to_string()), input.name.span());
let ident = Ident::new(&format!("{}Content", ident), ident.span());
let event_type_str = &input.events;
let variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
@ -135,12 +236,12 @@ pub fn expand_content_enum(input: &EventEnumInput) -> syn::Result<TokenStream> {
#(
#event_type_str => {
let content = #content::from_parts(event_type, input)?;
Ok(#ident::#variants(content))
Ok(Self::#variants(content))
},
)*
ev_type => {
let content = ::ruma_events::custom::CustomEventContent::from_parts(ev_type, input)?;
Ok(#ident::Custom(content))
Ok(Self::Custom(content))
},
}
}
@ -164,12 +265,14 @@ fn expand_any_enum_with_deserialize(
) -> syn::Result<TokenStream> {
let attrs = &input.attrs;
let event_type_str = &input.events;
let event_struct = Ident::new(&ident.to_string().trim_start_matches("Any"), ident.span());
let event_struct = Ident::new(&ident.to_string().replace("Any", ""), ident.span());
let variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
let content =
input.events.iter().map(|event| to_event_path(event, &event_struct)).collect::<Vec<_>>();
let (custom_variant, custom_deserialize) = expand_custom_variant(ident, &event_struct);
let any_enum = quote! {
#( #attrs )*
#[derive(Clone, Debug, ::serde::Serialize)]
@ -180,8 +283,7 @@ fn expand_any_enum_with_deserialize(
#[doc = #event_type_str]
#variants(#content),
)*
/// An event not defined by the Matrix specification
Custom(::ruma_events::#event_struct<::ruma_events::custom::CustomEventContent>),
#custom_variant
}
};
@ -199,16 +301,10 @@ fn expand_any_enum_with_deserialize(
#(
#event_type_str => {
let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?;
Ok(#ident::#variants(event))
Ok(Self::#variants(event))
},
)*
event => {
let event =
::serde_json::from_str::<::ruma_events::#event_struct<::ruma_events::custom::CustomEventContent>>(json.get())
.map_err(D::Error::custom)?;
Ok(Self::Custom(event))
},
#custom_deserialize
}
}
}
@ -225,6 +321,43 @@ fn expand_any_enum_with_deserialize(
})
}
fn expand_custom_variant(ident: &Ident, event_struct: &Ident) -> (TokenStream, TokenStream) {
if ident.to_string().contains("Redacted") {
(
quote! {
/// A redacted event not defined by the Matrix specification
Custom(::ruma_events::#event_struct<::ruma_events::custom::RedactedCustomEventContent>),
},
quote! {
event => {
let event = ::serde_json::from_str::<
::ruma_events::#event_struct<::ruma_events::custom::RedactedCustomEventContent>,
>(json.get())
.map_err(D::Error::custom)?;
Ok(Self::Custom(event))
},
},
)
} else {
(
quote! {
/// An event not defined by the Matrix specification
Custom(::ruma_events::#event_struct<::ruma_events::custom::CustomEventContent>),
},
quote! {
event => {
let event =
::serde_json::from_str::<::ruma_events::#event_struct<::ruma_events::custom::CustomEventContent>>(json.get())
.map_err(D::Error::custom)?;
Ok(Self::Custom(event))
},
},
)
}
}
fn marker_traits(ident: &Ident) -> TokenStream {
match ident.to_string().as_str() {
"AnyStateEventContent" => quote! {
@ -246,6 +379,10 @@ fn marker_traits(ident: &Ident) -> TokenStream {
}
fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
if ident.to_string().contains("Redacted") {
return redacted_accessor_methods(ident, variants);
}
let fields = EVENT_FIELDS
.iter()
.map(|(name, has_field)| generate_accessor(name, ident, *has_field, variants));
@ -296,6 +433,20 @@ fn accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
}
}
/// Redacted events do NOT generate `content` or `prev_content` methods like
/// un-redacted events; otherwise, they are the same.
fn redacted_accessor_methods(ident: &Ident, variants: &[Ident]) -> TokenStream {
let fields = EVENT_FIELDS
.iter()
.map(|(name, has_field)| generate_accessor(name, ident, *has_field, variants));
quote! {
impl #ident {
#( #fields )*
}
}
}
fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
let span = name.span();
let name = name.value();
@ -325,6 +476,10 @@ fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
let content = Ident::new(&format!("{}EventContent", event), span);
quote! { ::ruma_events::#struct_name<::ruma_events::#( #path )::*::#content> }
}
struct_str if struct_str.contains("Redacted") => {
let content = Ident::new(&format!("Redacted{}EventContent", event), span);
quote! { ::ruma_events::#struct_name<::ruma_events::#( #path )::*::#content> }
}
_ => {
let event_name = Ident::new(&format!("{}Event", event), span);
quote! { ::ruma_events::#( #path )::*::#event_name }
@ -401,6 +556,11 @@ fn generate_accessor(
}
}
/// Returns true if the `ident` is a state or message event.
fn needs_redacted(ident: &Ident) -> bool {
REDACTED_EVENT_KIND.contains(&ident.to_string().as_str())
}
fn field_return_type(name: &str) -> TokenStream {
match name {
"origin_server_ts" => quote! { ::std::time::SystemTime },

View File

@ -2,6 +2,11 @@
//! certain code for certain enums. If the names change this is the one source of truth,
//! most comparisons and branching uses these constants.
#![allow(dead_code)]
// Those marked with (UNUSED) are not used but, left for completeness sake.
// If you change this please remove the (UNUSED) comment.
// State events
pub const ANY_STATE_EVENT: &str = "AnyStateEvent";
@ -9,17 +14,27 @@ pub const ANY_SYNC_STATE_EVENT: &str = "AnyStateEventStub";
pub const ANY_STRIPPED_STATE_EVENT: &str = "AnyStrippedStateEventStub";
// Redacted state events
pub const REDACTED_STATE_EVENT: &str = "AnyRedactedStateEvent"; // (UNUSED)
pub const REDACTED_SYNC_STATE_EVENT: &str = "AnyRedactedStateEventStub"; // (UNUSED)
pub const REDACTED_STRIPPED_STATE_EVENT: &str = "AnyRedactedStrippedStateEventStub"; // (UNUSED)
// Message events
pub const ANY_MESSAGE_EVENT: &str = "AnyMessageEvent";
pub const ANY_SYNC_MESSAGE_EVENT: &str = "AnyMessageEventStub";
// Redacted message events
pub const REDACTED_MESSAGE_EVENT: &str = "AnyRedactedMessageEvent"; // (UNUSED)
pub const REDACTED_SYNC_MESSAGE_EVENT: &str = "AnyRedactedMessageEventStub"; // (UNUSED)
// Ephemeral events
pub const ANY_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEvent";
#[allow(dead_code)]
// This is currently not used but, left for completeness sake.
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub";
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub"; // (UNUSED)
// Basic event
pub const ANY_BASIC_EVENT: &str = "AnyBasicEvent";

View File

@ -52,42 +52,42 @@ pub fn event_enum(input: TokenStream) -> TokenStream {
#[proc_macro_derive(EventContent, attributes(ruma_event))]
pub fn derive_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_event_content(&input, true).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates an implementation of `ruma_events::BasicEventContent` and it's super traits.
#[proc_macro_derive(BasicEventContent, attributes(ruma_event))]
pub fn derive_basic_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_basic_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_basic_event_content(&input).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates an implementation of `ruma_events::RoomEventContent` and it's super traits.
#[proc_macro_derive(RoomEventContent, attributes(ruma_event))]
pub fn derive_room_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_room_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_room_event_content(&input).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates an implementation of `ruma_events::MessageEventContent` and it's super traits.
#[proc_macro_derive(MessageEventContent, attributes(ruma_event))]
pub fn derive_message_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_message_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_message_event_content(&input).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates an implementation of `ruma_events::StateEventContent` and it's super traits.
#[proc_macro_derive(StateEventContent, attributes(ruma_event))]
pub fn derive_state_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_state_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_state_event_content(&input).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates an implementation of `ruma_events::EphemeralRoomEventContent` and it's super traits.
#[proc_macro_derive(EphemeralRoomEventContent, attributes(ruma_event))]
pub fn derive_ephemeral_room_event_content(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_ephemeral_room_event_content(input).unwrap_or_else(|err| err.to_compile_error()).into()
expand_ephemeral_room_event_content(&input).unwrap_or_else(|err| err.to_compile_error()).into()
}
/// Generates implementations needed to serialize and deserialize Matrix events.

View File

@ -5,7 +5,8 @@ use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};
use crate::{
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
RoomEventContent, StateEventContent,
RedactedEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent,
StateEventContent,
};
/// A custom event's type and `content` JSON object.
@ -20,6 +21,13 @@ pub struct CustomEventContent {
pub json: JsonValue,
}
impl CustomEventContent {
/// Transforms the full event content into a redacted content according to spec.
pub fn redact(self) -> RedactedCustomEventContent {
RedactedCustomEventContent { event_type: self.event_type }
}
}
impl EventContent for CustomEventContent {
fn event_type(&self) -> &str {
&self.event_type
@ -42,3 +50,44 @@ impl EphemeralRoomEventContent for CustomEventContent {}
impl MessageEventContent for CustomEventContent {}
impl StateEventContent for CustomEventContent {}
/// A custom event that has been redacted.
#[derive(Clone, Debug, Serialize)]
pub struct RedactedCustomEventContent {
// This field is marked skipped but will be present because deserialization
// passes the `type` field of the JSON event to the events `EventContent::from_parts` method.
/// The event type string for this custom event "m.whatever".
#[serde(skip)]
pub event_type: String,
}
impl EventContent for RedactedCustomEventContent {
fn event_type(&self) -> &str {
&self.event_type
}
fn from_parts(
event_type: &str,
_content: Box<RawJsonValue>,
) -> Result<Self, serde_json::Error> {
Ok(Self { event_type: event_type.to_string() })
}
}
impl RedactedEventContent for RedactedCustomEventContent {
fn empty(event_type: &str) -> Result<Self, serde_json::Error> {
Ok(Self { event_type: event_type.to_string() })
}
fn has_serialize_fields(&self) -> bool {
false
}
fn has_deserialize_fields() -> bool {
false
}
}
impl RedactedMessageEventContent for RedactedCustomEventContent {}
impl RedactedStateEventContent for RedactedCustomEventContent {}

View File

@ -19,9 +19,9 @@ use crate::BasicEvent;
/// sending client receiving keys over the newly established session.
pub type DummyEvent = BasicEvent<DummyEventContent>;
/// The payload for `DummyEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
#[ruma_event(type = "m.dummy")]
/// The payload for `DummyEvent`.
pub struct DummyEventContent(pub Empty);
impl Deref for DummyEventContent {

View File

@ -97,6 +97,10 @@ pub enum AnyEvent {
Message(AnyMessageEvent),
/// Any state event.
State(AnyStateEvent),
/// Any message event that has been redacted.
RedactedMessage(AnyRedactedMessageEvent),
/// Any state event that has been redacted.
RedactedState(AnyRedactedStateEvent),
}
/// Any room event.
@ -107,6 +111,10 @@ pub enum AnyRoomEvent {
Message(AnyMessageEvent),
/// Any state event.
State(AnyStateEvent),
/// Any message event that has been redacted.
RedactedMessage(AnyRedactedMessageEvent),
/// Any state event that has been redacted.
RedactedState(AnyRedactedStateEvent),
}
/// Any room event stub (room event without a `room_id`, as returned in `/sync` responses)
@ -117,6 +125,10 @@ pub enum AnyRoomEventStub {
Message(AnyMessageEventStub),
/// Any state event stub
State(AnyStateEventStub),
/// Any message event stub that has been redacted.
RedactedMessage(AnyRedactedMessageEventStub),
/// Any state event stub that has been redacted.
RedactedState(AnyRedactedStateEventStub),
}
// FIXME `#[serde(untagged)]` deserialization fails for these enums which
@ -127,14 +139,25 @@ impl<'de> de::Deserialize<'de> for AnyEvent {
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EventDeHelper { state_key, event_id, room_id, .. } = from_raw_json_value(&json)?;
let EventDeHelper { state_key, event_id, room_id, unsigned, .. } =
from_raw_json_value(&json)?;
// Determine whether the event is a state, message, ephemeral, or basic event
// based on the fields present.
if state_key.is_some() {
Ok(AnyEvent::State(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyEvent::RedactedState(from_raw_json_value(&json)?)
}
_ => AnyEvent::State(from_raw_json_value(&json)?),
})
} else if event_id.is_some() {
Ok(AnyEvent::Message(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyEvent::RedactedMessage(from_raw_json_value(&json)?)
}
_ => AnyEvent::Message(from_raw_json_value(&json)?),
})
} else if room_id.is_some() {
Ok(AnyEvent::Ephemeral(from_raw_json_value(&json)?))
} else {
@ -149,12 +172,22 @@ impl<'de> de::Deserialize<'de> for AnyRoomEvent {
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EventDeHelper { state_key, .. } = from_raw_json_value(&json)?;
let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;
if state_key.is_some() {
Ok(AnyRoomEvent::State(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyRoomEvent::RedactedState(from_raw_json_value(&json)?)
}
_ => AnyRoomEvent::State(from_raw_json_value(&json)?),
})
} else {
Ok(AnyRoomEvent::Message(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
}
_ => AnyRoomEvent::Message(from_raw_json_value(&json)?),
})
}
}
}
@ -165,12 +198,22 @@ impl<'de> de::Deserialize<'de> for AnyRoomEventStub {
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EventDeHelper { state_key, .. } = from_raw_json_value(&json)?;
let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;
if state_key.is_some() {
Ok(AnyRoomEventStub::State(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyRoomEventStub::RedactedState(from_raw_json_value(&json)?)
}
_ => AnyRoomEventStub::State(from_raw_json_value(&json)?),
})
} else {
Ok(AnyRoomEventStub::Message(from_raw_json_value(&json)?))
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
AnyRoomEventStub::RedactedMessage(from_raw_json_value(&json)?)
}
_ => AnyRoomEventStub::Message(from_raw_json_value(&json)?),
})
}
}
}

View File

@ -5,7 +5,7 @@ use ruma_identifiers::{EventId, RoomId, UserId};
use crate::{
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
StateEventContent, UnsignedData,
RedactedMessageEventContent, RedactedStateEventContent, StateEventContent, UnsignedData,
};
/// A basic event one that consists only of it's type and the `content` object.
@ -73,6 +73,48 @@ pub struct MessageEventStub<C: MessageEventContent> {
pub unsigned: UnsignedData,
}
/// A redacted message event.
#[derive(Clone, Debug, Event)]
pub struct RedactedMessageEvent<C: RedactedMessageEventContent> {
/// Data specific to the event type.
pub content: C,
/// The globally unique event identifier for the user who sent the event.
pub event_id: EventId,
/// The fully-qualified ID of the user who sent this event.
pub sender: UserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
pub origin_server_ts: SystemTime,
/// The ID of the room associated with this event.
pub room_id: RoomId,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: UnsignedData,
}
/// A redacted message event without a `room_id`.
#[derive(Clone, Debug, Event)]
pub struct RedactedMessageEventStub<C: RedactedMessageEventContent> {
/// Data specific to the event type.
// #[serde(default, skip_serializing_if = "is_zst")]
pub content: C,
/// The globally unique event identifier for the user who sent the event.
pub event_id: EventId,
/// The fully-qualified ID of the user who sent this event.
pub sender: UserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
pub origin_server_ts: SystemTime,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: UnsignedData,
}
/// State event.
#[derive(Clone, Debug, Event)]
pub struct StateEvent<C: StateEventContent> {
@ -149,6 +191,76 @@ pub struct StrippedStateEventStub<C: StateEventContent> {
pub state_key: String,
}
/// A redacted state event.
#[derive(Clone, Debug, Event)]
pub struct RedactedStateEvent<C: RedactedStateEventContent> {
/// Data specific to the event type.
pub content: C,
/// The globally unique event identifier for the user who sent the event.
pub event_id: EventId,
/// The fully-qualified ID of the user who sent this event.
pub sender: UserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
pub origin_server_ts: SystemTime,
/// The ID of the room associated with this event.
pub room_id: RoomId,
/// A unique key which defines the overwriting semantics for this piece of room state.
///
/// This is often an empty string, but some events send a `UserId` to show
/// which user the event affects.
pub state_key: String,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: UnsignedData,
}
/// A redacted state event without a `room_id`.
#[derive(Clone, Debug, Event)]
pub struct RedactedStateEventStub<C: RedactedStateEventContent> {
/// Data specific to the event type.
// #[serde(default, skip_serializing_if = "is_zst")]
pub content: C,
/// The globally unique event identifier for the user who sent the event.
pub event_id: EventId,
/// The fully-qualified ID of the user who sent this event.
pub sender: UserId,
/// Timestamp in milliseconds on originating homeserver when this event was sent.
pub origin_server_ts: SystemTime,
/// A unique key which defines the overwriting semantics for this piece of room state.
///
/// This is often an empty string, but some events send a `UserId` to show
/// which user the event affects.
pub state_key: String,
/// Additional key-value pairs not signed by the homeserver.
pub unsigned: UnsignedData,
}
/// A stripped-down redacted state event.
#[derive(Clone, Debug, Event)]
pub struct RedactedStrippedStateEventStub<C: RedactedStateEventContent> {
/// Data specific to the event type.
pub content: C,
/// The fully-qualified ID of the user who sent this event.
pub sender: UserId,
/// A unique key which defines the overwriting semantics for this piece of room state.
///
/// This is often an empty string, but some events send a `UserId` to show
/// which user the event affects.
pub state_key: String,
}
/// An event sent using send-to-device messaging.
#[derive(Clone, Debug, Event)]
pub struct ToDeviceEvent<C: EventContent> {

View File

@ -166,13 +166,17 @@ pub use self::{
enums::{
AnyBasicEvent, AnyBasicEventContent, AnyEphemeralRoomEvent, AnyEphemeralRoomEventContent,
AnyEphemeralRoomEventStub, AnyEvent, AnyMessageEvent, AnyMessageEventContent,
AnyMessageEventStub, AnyRoomEvent, AnyRoomEventStub, AnyStateEvent, AnyStateEventContent,
AnyStateEventStub, AnyStrippedStateEventStub, AnyToDeviceEvent, AnyToDeviceEventContent,
AnyMessageEventStub, AnyRedactedMessageEvent, AnyRedactedMessageEventStub,
AnyRedactedStateEvent, AnyRedactedStateEventStub, AnyRedactedStrippedStateEventStub,
AnyRoomEvent, AnyRoomEventStub, AnyStateEvent, AnyStateEventContent, AnyStateEventStub,
AnyStrippedStateEventStub, AnyToDeviceEvent, AnyToDeviceEventContent,
},
error::{FromStrError, InvalidInput},
event_kinds::{
BasicEvent, EphemeralRoomEvent, EphemeralRoomEventStub, MessageEvent, MessageEventStub,
StateEvent, StateEventStub, StrippedStateEventStub, ToDeviceEvent,
RedactedMessageEvent, RedactedMessageEventStub, RedactedStateEvent, RedactedStateEventStub,
RedactedStrippedStateEventStub, StateEvent, StateEventStub, StrippedStateEventStub,
ToDeviceEvent,
},
event_type::EventType,
json::EventJson,
@ -237,6 +241,38 @@ pub trait MessageEventContent: RoomEventContent {}
/// Marker trait for the content of a state event.
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.
pub trait RedactedEventContent: EventContent {
/// Constructs the redacted event content.
///
/// If called for anything but "empty" redacted content this will error.
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.
fn has_serialize_fields(&self) -> bool;
/// Determines if the redacted event content needs to deserialize fields.
fn has_deserialize_fields() -> bool;
}
/// Marker trait for the content of a redacted message event.
pub trait RedactedMessageEventContent: RedactedEventContent {}
/// Marker trait for the content of a redacted state event.
pub trait RedactedStateEventContent: RedactedEventContent {}
/// Helper struct to determine if the event has been redacted.
#[doc(hidden)]
#[derive(Debug, Deserialize)]
pub struct UnsignedDeHelper {
/// This is the field that signals an event has been redacted.
pub redacted_because: Option<IgnoredAny>,
}
/// Helper struct to determine the event kind from a serde_json::value::RawValue.
#[doc(hidden)]
#[derive(Debug, Deserialize)]
@ -255,6 +291,10 @@ pub struct EventDeHelper {
/// If no `event_id` or `state_key` are found but a `room_id` is present
/// the event will be deserialized as a ephemeral event.
pub room_id: Option<IgnoredAny>,
/// If this `UnsignedData` contains a redacted_because key the event is
/// immediately deserialized as a redacted event.
pub unsigned: Option<UnsignedDeHelper>,
}
/// Helper function for serde_json::value::RawValue deserialization.

View File

@ -3,8 +3,9 @@
use ruma_events_macros::StateEventContent;
use ruma_identifiers::RoomAliasId;
use serde::{Deserialize, Serialize};
use serde_json::value::RawValue as RawJsonValue;
use crate::StateEvent;
use crate::{EventContent, RedactedEventContent, RedactedStateEventContent, StateEvent};
/// Informs the room about what room aliases it has been given.
pub type AliasesEvent = StateEvent<AliasesEventContent>;
@ -12,7 +13,49 @@ pub type AliasesEvent = StateEvent<AliasesEventContent>;
/// The payload for `AliasesEvent`.
#[derive(Clone, Debug, Deserialize, Serialize, StateEventContent)]
#[ruma_event(type = "m.room.aliases")]
#[ruma_event(custom_redacted)]
pub struct AliasesEventContent {
/// A list of room aliases.
pub aliases: Vec<RoomAliasId>,
}
/// An aliases event that has been redacted.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RedactedAliasesEventContent {
/// A list of room aliases.
///
/// According to the Matrix spec version 1 redaction rules allowed this field to be
/// kept after redaction, this was changed in version 6.
pub aliases: Option<Vec<RoomAliasId>>,
}
impl EventContent for RedactedAliasesEventContent {
fn event_type(&self) -> &str {
"m.room.aliases"
}
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, serde_json::Error> {
if event_type != "m.room.aliases" {
return Err(::serde::de::Error::custom(format!(
"expected event type `m.room.aliases`, found `{}`",
event_type
)));
}
serde_json::from_str(content.get())
}
}
// Since this redacted event has fields we leave the default `empty` method
// that will error if called.
impl RedactedEventContent for RedactedAliasesEventContent {
fn has_serialize_fields(&self) -> bool {
self.aliases.is_some()
}
fn has_deserialize_fields() -> bool {
true
}
}
impl RedactedStateEventContent for RedactedAliasesEventContent {}

View File

@ -17,6 +17,7 @@ pub type CreateEvent = StateEvent<CreateEventContent>;
#[ruma_event(type = "m.room.create")]
pub struct CreateEventContent {
/// The `user_id` of the room creator. This is set by the homeserver.
#[ruma_event(skip_redaction)]
pub creator: UserId,
/// Whether or not this room's data should be transferred to other homeservers.

View File

@ -15,6 +15,7 @@ pub type HistoryVisibilityEvent = StateEvent<HistoryVisibilityEventContent>;
#[ruma_event(type = "m.room.history_visibility")]
pub struct HistoryVisibilityEventContent {
/// Who can see the room history.
#[ruma_event(skip_redaction)]
pub history_visibility: HistoryVisibility,
}

View File

@ -14,6 +14,7 @@ pub type JoinRulesEvent = StateEvent<JoinRulesEventContent>;
#[ruma_event(type = "m.room.join_rules")]
pub struct JoinRulesEventContent {
/// The type of rules used for users wishing to join this room.
#[ruma_event(skip_redaction)]
pub join_rule: JoinRule,
}

View File

@ -54,6 +54,7 @@ pub struct MemberEventContent {
pub is_direct: Option<bool>,
/// The membership state of this user.
#[ruma_event(skip_redaction)]
pub membership: MembershipState,
/// If this member event is the successor to a third party invitation, this field will

View File

@ -18,16 +18,19 @@ pub type PowerLevelsEvent = StateEvent<PowerLevelsEventContent>;
pub struct PowerLevelsEventContent {
/// The level required to ban a user.
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
#[ruma_event(skip_redaction)]
pub ban: Int,
/// The level required to send specific event types.
///
/// This is a mapping from event type to power level required.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[ruma_event(skip_redaction)]
pub events: BTreeMap<EventType, Int>,
/// The default level required to send message events.
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
#[ruma_event(skip_redaction)]
pub events_default: Int,
/// The level required to invite a user.
@ -36,24 +39,29 @@ pub struct PowerLevelsEventContent {
/// The level required to kick a user.
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
#[ruma_event(skip_redaction)]
pub kick: Int,
/// The level required to redact an event.
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
#[ruma_event(skip_redaction)]
pub redact: Int,
/// The default level required to send state events.
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
#[ruma_event(skip_redaction)]
pub state_default: Int,
/// The power levels for specific users.
///
/// This is a mapping from `user_id` to power level for that user.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[ruma_event(skip_redaction)]
pub users: BTreeMap<UserId, Int>,
/// The default power level for every user in the room.
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
#[ruma_event(skip_redaction)]
pub users_default: Int,
/// The power level requirements for specific notification types.

View File

@ -6,7 +6,10 @@ use ruma_events_macros::{Event, EventContent};
use ruma_identifiers::{EventId, RoomId, UserId};
use serde::{Deserialize, Serialize};
use crate::UnsignedData;
use crate::{
MessageEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent,
UnsignedData,
};
/// Redaction event.
#[derive(Clone, Debug, Event)]
@ -64,84 +67,10 @@ pub struct RedactionEventContent {
pub reason: Option<String>,
}
impl ruma_events::RoomEventContent for RedactionEventContent {}
impl RoomEventContent for RedactionEventContent {}
impl ruma_events::MessageEventContent for RedactionEventContent {}
impl MessageEventContent for RedactionEventContent {}
#[cfg(test)]
mod tests {
use std::{
convert::TryFrom,
time::{Duration, UNIX_EPOCH},
};
impl RedactedMessageEventContent for RedactedRedactionEventContent {}
use matches::assert_matches;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{RedactionEvent, RedactionEventContent};
use crate::{EventJson, UnsignedData};
#[test]
fn serialization() {
let event = 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 json = json!({
"content": {
"reason": "redacted because"
},
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"redacts": "$h29iv0s8:example.com",
"room_id": "!roomid:room.com",
"sender": "@carl:example.com",
"type": "m.room.redaction",
});
assert_eq!(to_json_value(&event).unwrap(), json);
}
#[test]
fn deserialization() {
let e_id = EventId::try_from("$h29iv0s8:example.com").unwrap();
let json = json!({
"content": {
"reason": "redacted because"
},
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"redacts": "$h29iv0s8:example.com",
"room_id": "!roomid:room.com",
"sender": "@carl:example.com",
"type": "m.room.redaction",
});
assert_matches!(
from_json_value::<EventJson<RedactionEvent>>(json)
.unwrap()
.deserialize()
.unwrap(),
RedactionEvent {
content: RedactionEventContent {
reason: Some(reason),
},
sender,
event_id, origin_server_ts, redacts, room_id, unsigned,
} if reason == "redacted because" && redacts == e_id
&& event_id == e_id
&& sender == "@carl:example.com"
&& origin_server_ts == UNIX_EPOCH + Duration::from_millis(1)
&& room_id == RoomId::try_from("!roomid:room.com").unwrap()
&& unsigned.is_empty()
);
}
}
impl RedactedStateEventContent for RedactedRedactionEventContent {}

View File

@ -9,6 +9,7 @@ use crate::BasicEvent;
/// Informs the client of tags on a room.
pub type TagEvent = BasicEvent<TagEventContent>;
/// Map of tag names to tag info.
pub type Tags = BTreeMap<String, TagInfo>;

View File

@ -0,0 +1,268 @@
use std::{
convert::TryFrom,
time::{Duration, UNIX_EPOCH},
};
use matches::assert_matches;
use ruma_events::{
custom::RedactedCustomEventContent,
room::{
aliases::RedactedAliasesEventContent,
create::RedactedCreateEventContent,
message::RedactedMessageEventContent,
redaction::{RedactionEvent, RedactionEventContent},
},
AnyRedactedMessageEvent, AnyRedactedMessageEventStub, AnyRedactedStateEventStub, AnyRoomEvent,
AnyRoomEventStub, EventJson, RedactedMessageEvent, RedactedMessageEventStub,
RedactedStateEventStub, UnsignedData,
};
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
}
#[test]
fn redacted_message_event_serialize() {
let redacted = RedactedMessageEventStub {
content: RedactedMessageEventContent,
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
};
let expected = json!({
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"type": "m.room.message"
});
let actual = to_json_value(&redacted).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn redacted_aliases_event_serialize() {
let redacted = RedactedStateEventStub {
content: RedactedAliasesEventContent { aliases: None },
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
state_key: "".to_string(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
sender: UserId::try_from("@carl:example.com").unwrap(),
unsigned: UnsignedData::default(),
};
let expected = json!({
"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_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(),
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 redacted = json!({
"event_id": "$h29iv0s8:example.com",
"room_id": "!roomid:room.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"unsigned": unsigned,
"type": "m.room.message"
});
let actual = to_json_value(&redacted).unwrap();
assert_matches!(
from_json_value::<EventJson<AnyRoomEvent>>(actual)
.unwrap()
.deserialize()
.unwrap(),
AnyRoomEvent::RedactedMessage(AnyRedactedMessageEvent::RoomMessage(RedactedMessageEvent {
event_id, room_id, content, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& room_id == RoomId::try_from("!roomid:room.com").unwrap()
&& is_zst(&content)
)
}
#[test]
fn redacted_deserialize_any_room_stub() {
let mut unsigned = UnsignedData::default();
// The presence of `redacted_because` triggers the event enum (AnyRoomEventStub 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(),
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 redacted = json!({
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"unsigned": unsigned,
"type": "m.room.message"
});
let actual = to_json_value(&redacted).unwrap();
assert_matches!(
from_json_value::<EventJson<AnyRoomEventStub>>(actual)
.unwrap()
.deserialize()
.unwrap(),
AnyRoomEventStub::RedactedMessage(AnyRedactedMessageEventStub::RoomMessage(RedactedMessageEventStub {
event_id, content, ..
})) 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 redacted = json!({
"content": {
"creator": "@carl:example.com",
},
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"state_key": "hello there",
"unsigned": unsigned,
"type": "m.room.create"
});
assert_matches!(
from_json_value::<EventJson<AnyRoomEventStub>>(redacted)
.unwrap()
.deserialize()
.unwrap(),
AnyRoomEventStub::RedactedState(AnyRedactedStateEventStub::RoomCreate(RedactedStateEventStub {
content: RedactedCreateEventContent {
creator,
},
event_id, state_key, unsigned, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& unsigned.redacted_because.is_some()
&& state_key == "hello there"
&& creator == UserId::try_from("@carl:example.com").unwrap()
)
}
#[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 redacted = json!({
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"state_key": "hello there",
"unsigned": unsigned,
"type": "m.made.up"
});
assert_matches!(
from_json_value::<EventJson<AnyRoomEventStub>>(redacted.clone())
.unwrap()
.deserialize()
.unwrap(),
AnyRoomEventStub::RedactedState(AnyRedactedStateEventStub::Custom(RedactedStateEventStub {
content: RedactedCustomEventContent {
event_type,
},
event_id, state_key, unsigned, ..
})) if event_id == EventId::try_from("$h29iv0s8:example.com").unwrap()
&& unsigned.redacted_because.is_some()
&& state_key == "hello there"
&& event_type == "m.made.up"
);
let x = from_json_value::<EventJson<crate::AnyRedactedStateEventStub>>(redacted)
.unwrap()
.deserialize()
.unwrap();
assert_eq!(x.event_id(), &EventId::try_from("$h29iv0s8:example.com").unwrap())
}
#[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 redacted = RedactedStateEventStub {
content: RedactedCustomEventContent { event_type: "m.made.up".to_string() },
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
sender: UserId::try_from("@carl:example.com").unwrap(),
state_key: "hello there".to_string(),
origin_server_ts: UNIX_EPOCH + Duration::from_millis(1),
unsigned: unsigned.clone(),
};
let expected = json!({
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"state_key": "hello there",
"unsigned": unsigned,
"type": "m.made.up"
});
let actual = to_json_value(&redacted).unwrap();
assert_eq!(actual, expected);
}

View File

@ -1,4 +1,4 @@
error: expected `type`
error: not a recognized `ruma_event` attribute
--> $DIR/03-invalid-event-type.rs:11:14
|
11 | #[ruma_event(event = "m.macro.test")]