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:
parent
c19bcaab31
commit
5e428ac95a
@ -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)?;
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 },
|
||||
|
@ -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";
|
||||
|
@ -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.
|
||||
|
@ -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 {}
|
||||
|
@ -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 {
|
||||
|
@ -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)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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.
|
||||
|
@ -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 {}
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {}
|
||||
|
@ -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>;
|
||||
|
||||
|
268
ruma-events/tests/redacted.rs
Normal file
268
ruma-events/tests/redacted.rs
Normal 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);
|
||||
}
|
@ -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")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user