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()
|
.iter()
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let name = field.ident.as_ref().unwrap();
|
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! {
|
quote! {
|
||||||
if let Some(content) = self.prev_content.as_ref() {
|
if let Some(content) = self.prev_content.as_ref() {
|
||||||
state.serialize_field("prev_content", content)?;
|
state.serialize_field("prev_content", content)?;
|
||||||
@ -143,7 +149,16 @@ fn expand_deserialize_event(
|
|||||||
.map(|field| {
|
.map(|field| {
|
||||||
let name = field.ident.as_ref().unwrap();
|
let name = field.ident.as_ref().unwrap();
|
||||||
if name == "content" {
|
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! {
|
quote! {
|
||||||
let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?;
|
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)?;
|
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 quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
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.
|
/// Parses attributes for `*EventContent` derives.
|
||||||
///
|
///
|
||||||
/// `#[ruma_event(type = "m.room.alias")]`
|
/// `#[ruma_event(type = "m.room.alias")]`
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
enum EventMeta {
|
enum EventMeta {
|
||||||
/// Variant holds the "m.whatever" event type.
|
/// Variant holds the "m.whatever" event type.
|
||||||
Type(LitStr),
|
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 {
|
impl Parse for EventMeta {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
input.parse::<Token![type]>()?;
|
if input.parse::<Token![type]>().is_ok() {
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
Ok(EventMeta::Type(input.parse::<LitStr>()?))
|
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.
|
/// 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 ident = &input.ident;
|
||||||
|
|
||||||
let event_type_attr =
|
let content_attr = input
|
||||||
input.attrs.iter().find(|attr| attr.path.is_ident("ruma_event")).ok_or_else(|| {
|
.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, \
|
let msg = "no event type attribute found, \
|
||||||
add `#[ruma_event(type = \"any.room.event\")]` \
|
add `#[ruma_event(type = \"any.room.event\")]` \
|
||||||
below the event content derive";
|
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)
|
syn::Error::new(Span::call_site(), msg)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let event_type = {
|
let redacted = if emit_redacted && needs_redacted(input) {
|
||||||
let event_meta = event_type_attr.parse_args::<EventMeta>()?;
|
let doc = format!("The payload for a redacted `{}`", ident);
|
||||||
let EventMeta::Type(lit) = event_meta;
|
let redacted_ident = quote::format_ident!("Redacted{}", ident);
|
||||||
lit
|
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! {
|
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 {
|
impl ::ruma_events::EventContent for #ident {
|
||||||
fn event_type(&self) -> &str {
|
fn event_type(&self) -> &str {
|
||||||
#event_type
|
#event_type
|
||||||
@ -61,65 +293,14 @@ pub fn expand_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
::serde_json::from_str(content.get())
|
::serde_json::from_str(content.get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `BasicEventContent` implementation for a struct
|
fn needs_redacted(input: &DeriveInput) -> bool {
|
||||||
pub fn expand_basic_event_content(input: DeriveInput) -> syn::Result<TokenStream> {
|
input
|
||||||
let ident = input.ident.clone();
|
.attrs
|
||||||
let event_content_impl = expand_event_content(input)?;
|
.iter()
|
||||||
|
.flat_map(|a| a.parse_args::<EventMeta>().ok())
|
||||||
Ok(quote! {
|
.find(|a| a == &EventMeta::CustomRedacted)
|
||||||
#event_content_impl
|
.is_none()
|
||||||
|
|
||||||
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 { }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,40 @@ use syn::{
|
|||||||
use crate::event_names::{
|
use crate::event_names::{
|
||||||
ANY_BASIC_EVENT, ANY_EPHEMERAL_EVENT, ANY_MESSAGE_EVENT, ANY_STATE_EVENT,
|
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,
|
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.
|
// Arrays of event enum names grouped by a field they share in common.
|
||||||
const ROOM_EVENT_KIND: &[&str] =
|
const ROOM_EVENT_KIND: &[&str] = &[
|
||||||
&[ANY_MESSAGE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
|
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] =
|
const EVENT_ID_KIND: &[&str] = &[
|
||||||
&[ANY_MESSAGE_EVENT, ANY_SYNC_MESSAGE_EVENT, ANY_STATE_EVENT, ANY_SYNC_STATE_EVENT];
|
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] = &[
|
const SENDER_KIND: &[&str] = &[
|
||||||
ANY_MESSAGE_EVENT,
|
ANY_MESSAGE_EVENT,
|
||||||
@ -28,11 +52,31 @@ const SENDER_KIND: &[&str] = &[
|
|||||||
ANY_TO_DEVICE_EVENT,
|
ANY_TO_DEVICE_EVENT,
|
||||||
ANY_SYNC_MESSAGE_EVENT,
|
ANY_SYNC_MESSAGE_EVENT,
|
||||||
ANY_STRIPPED_STATE_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 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.
|
/// 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 =
|
let event_stripped_enum =
|
||||||
if needs_stripped_event { expand_stripped_enum(&input)? } else { TokenStream::new() };
|
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 =
|
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! {
|
Ok(quote! {
|
||||||
#event_enum
|
#event_enum
|
||||||
@ -79,6 +129,8 @@ pub fn expand_event_enum(input: EventEnumInput) -> syn::Result<TokenStream> {
|
|||||||
|
|
||||||
#event_stripped_enum
|
#event_stripped_enum
|
||||||
|
|
||||||
|
#redacted_event_enums
|
||||||
|
|
||||||
#event_content_enum
|
#event_content_enum
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -97,10 +149,59 @@ pub fn expand_stripped_enum(input: &EventEnumInput) -> syn::Result<TokenStream>
|
|||||||
expand_any_enum_with_deserialize(input, &ident)
|
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`.
|
/// 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 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 event_type_str = &input.events;
|
||||||
|
|
||||||
let variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
|
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 => {
|
#event_type_str => {
|
||||||
let content = #content::from_parts(event_type, input)?;
|
let content = #content::from_parts(event_type, input)?;
|
||||||
Ok(#ident::#variants(content))
|
Ok(Self::#variants(content))
|
||||||
},
|
},
|
||||||
)*
|
)*
|
||||||
ev_type => {
|
ev_type => {
|
||||||
let content = ::ruma_events::custom::CustomEventContent::from_parts(ev_type, input)?;
|
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> {
|
) -> syn::Result<TokenStream> {
|
||||||
let attrs = &input.attrs;
|
let attrs = &input.attrs;
|
||||||
let event_type_str = &input.events;
|
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 variants = input.events.iter().map(to_camel_case).collect::<syn::Result<Vec<_>>>()?;
|
||||||
let content =
|
let content =
|
||||||
input.events.iter().map(|event| to_event_path(event, &event_struct)).collect::<Vec<_>>();
|
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! {
|
let any_enum = quote! {
|
||||||
#( #attrs )*
|
#( #attrs )*
|
||||||
#[derive(Clone, Debug, ::serde::Serialize)]
|
#[derive(Clone, Debug, ::serde::Serialize)]
|
||||||
@ -180,8 +283,7 @@ fn expand_any_enum_with_deserialize(
|
|||||||
#[doc = #event_type_str]
|
#[doc = #event_type_str]
|
||||||
#variants(#content),
|
#variants(#content),
|
||||||
)*
|
)*
|
||||||
/// An event not defined by the Matrix specification
|
#custom_variant
|
||||||
Custom(::ruma_events::#event_struct<::ruma_events::custom::CustomEventContent>),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,16 +301,10 @@ fn expand_any_enum_with_deserialize(
|
|||||||
#(
|
#(
|
||||||
#event_type_str => {
|
#event_type_str => {
|
||||||
let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?;
|
let event = ::serde_json::from_str::<#content>(json.get()).map_err(D::Error::custom)?;
|
||||||
Ok(#ident::#variants(event))
|
Ok(Self::#variants(event))
|
||||||
},
|
},
|
||||||
)*
|
)*
|
||||||
event => {
|
#custom_deserialize
|
||||||
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))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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 {
|
fn marker_traits(ident: &Ident) -> TokenStream {
|
||||||
match ident.to_string().as_str() {
|
match ident.to_string().as_str() {
|
||||||
"AnyStateEventContent" => quote! {
|
"AnyStateEventContent" => quote! {
|
||||||
@ -246,6 +379,10 @@ fn marker_traits(ident: &Ident) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn accessor_methods(ident: &Ident, variants: &[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
|
let fields = EVENT_FIELDS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, has_field)| generate_accessor(name, ident, *has_field, variants));
|
.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 {
|
fn to_event_path(name: &LitStr, struct_name: &Ident) -> TokenStream {
|
||||||
let span = name.span();
|
let span = name.span();
|
||||||
let name = name.value();
|
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);
|
let content = Ident::new(&format!("{}EventContent", event), span);
|
||||||
quote! { ::ruma_events::#struct_name<::ruma_events::#( #path )::*::#content> }
|
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);
|
let event_name = Ident::new(&format!("{}Event", event), span);
|
||||||
quote! { ::ruma_events::#( #path )::*::#event_name }
|
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 {
|
fn field_return_type(name: &str) -> TokenStream {
|
||||||
match name {
|
match name {
|
||||||
"origin_server_ts" => quote! { ::std::time::SystemTime },
|
"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,
|
//! certain code for certain enums. If the names change this is the one source of truth,
|
||||||
//! most comparisons and branching uses these constants.
|
//! 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
|
// State events
|
||||||
pub const ANY_STATE_EVENT: &str = "AnyStateEvent";
|
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";
|
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
|
// Message events
|
||||||
pub const ANY_MESSAGE_EVENT: &str = "AnyMessageEvent";
|
pub const ANY_MESSAGE_EVENT: &str = "AnyMessageEvent";
|
||||||
|
|
||||||
pub const ANY_SYNC_MESSAGE_EVENT: &str = "AnyMessageEventStub";
|
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
|
// Ephemeral events
|
||||||
pub const ANY_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEvent";
|
pub const ANY_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEvent";
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub"; // (UNUSED)
|
||||||
// This is currently not used but, left for completeness sake.
|
|
||||||
pub const ANY_SYNC_EPHEMERAL_EVENT: &str = "AnyEphemeralRoomEventStub";
|
|
||||||
|
|
||||||
// Basic event
|
// Basic event
|
||||||
pub const ANY_BASIC_EVENT: &str = "AnyBasicEvent";
|
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))]
|
#[proc_macro_derive(EventContent, attributes(ruma_event))]
|
||||||
pub fn derive_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// Generates an implementation of `ruma_events::BasicEventContent` and it's super traits.
|
||||||
#[proc_macro_derive(BasicEventContent, attributes(ruma_event))]
|
#[proc_macro_derive(BasicEventContent, attributes(ruma_event))]
|
||||||
pub fn derive_basic_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_basic_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// Generates an implementation of `ruma_events::RoomEventContent` and it's super traits.
|
||||||
#[proc_macro_derive(RoomEventContent, attributes(ruma_event))]
|
#[proc_macro_derive(RoomEventContent, attributes(ruma_event))]
|
||||||
pub fn derive_room_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_room_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// Generates an implementation of `ruma_events::MessageEventContent` and it's super traits.
|
||||||
#[proc_macro_derive(MessageEventContent, attributes(ruma_event))]
|
#[proc_macro_derive(MessageEventContent, attributes(ruma_event))]
|
||||||
pub fn derive_message_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_message_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// Generates an implementation of `ruma_events::StateEventContent` and it's super traits.
|
||||||
#[proc_macro_derive(StateEventContent, attributes(ruma_event))]
|
#[proc_macro_derive(StateEventContent, attributes(ruma_event))]
|
||||||
pub fn derive_state_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_state_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// Generates an implementation of `ruma_events::EphemeralRoomEventContent` and it's super traits.
|
||||||
#[proc_macro_derive(EphemeralRoomEventContent, attributes(ruma_event))]
|
#[proc_macro_derive(EphemeralRoomEventContent, attributes(ruma_event))]
|
||||||
pub fn derive_ephemeral_room_event_content(input: TokenStream) -> TokenStream {
|
pub fn derive_ephemeral_room_event_content(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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.
|
/// 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::{
|
use crate::{
|
||||||
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
|
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
|
||||||
RoomEventContent, StateEventContent,
|
RedactedEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent,
|
||||||
|
StateEventContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A custom event's type and `content` JSON object.
|
/// A custom event's type and `content` JSON object.
|
||||||
@ -20,6 +21,13 @@ pub struct CustomEventContent {
|
|||||||
pub json: JsonValue,
|
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 {
|
impl EventContent for CustomEventContent {
|
||||||
fn event_type(&self) -> &str {
|
fn event_type(&self) -> &str {
|
||||||
&self.event_type
|
&self.event_type
|
||||||
@ -42,3 +50,44 @@ impl EphemeralRoomEventContent for CustomEventContent {}
|
|||||||
impl MessageEventContent for CustomEventContent {}
|
impl MessageEventContent for CustomEventContent {}
|
||||||
|
|
||||||
impl StateEventContent 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.
|
/// sending client receiving keys over the newly established session.
|
||||||
pub type DummyEvent = BasicEvent<DummyEventContent>;
|
pub type DummyEvent = BasicEvent<DummyEventContent>;
|
||||||
|
|
||||||
|
/// The payload for `DummyEvent`.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)]
|
||||||
#[ruma_event(type = "m.dummy")]
|
#[ruma_event(type = "m.dummy")]
|
||||||
/// The payload for `DummyEvent`.
|
|
||||||
pub struct DummyEventContent(pub Empty);
|
pub struct DummyEventContent(pub Empty);
|
||||||
|
|
||||||
impl Deref for DummyEventContent {
|
impl Deref for DummyEventContent {
|
||||||
|
@ -97,6 +97,10 @@ pub enum AnyEvent {
|
|||||||
Message(AnyMessageEvent),
|
Message(AnyMessageEvent),
|
||||||
/// Any state event.
|
/// Any state event.
|
||||||
State(AnyStateEvent),
|
State(AnyStateEvent),
|
||||||
|
/// Any message event that has been redacted.
|
||||||
|
RedactedMessage(AnyRedactedMessageEvent),
|
||||||
|
/// Any state event that has been redacted.
|
||||||
|
RedactedState(AnyRedactedStateEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Any room event.
|
/// Any room event.
|
||||||
@ -107,6 +111,10 @@ pub enum AnyRoomEvent {
|
|||||||
Message(AnyMessageEvent),
|
Message(AnyMessageEvent),
|
||||||
/// Any state event.
|
/// Any state event.
|
||||||
State(AnyStateEvent),
|
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)
|
/// Any room event stub (room event without a `room_id`, as returned in `/sync` responses)
|
||||||
@ -117,6 +125,10 @@ pub enum AnyRoomEventStub {
|
|||||||
Message(AnyMessageEventStub),
|
Message(AnyMessageEventStub),
|
||||||
/// Any state event stub
|
/// Any state event stub
|
||||||
State(AnyStateEventStub),
|
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
|
// FIXME `#[serde(untagged)]` deserialization fails for these enums which
|
||||||
@ -127,14 +139,25 @@ impl<'de> de::Deserialize<'de> for AnyEvent {
|
|||||||
D: de::Deserializer<'de>,
|
D: de::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
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
|
// Determine whether the event is a state, message, ephemeral, or basic event
|
||||||
// based on the fields present.
|
// based on the fields present.
|
||||||
if state_key.is_some() {
|
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() {
|
} 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() {
|
} else if room_id.is_some() {
|
||||||
Ok(AnyEvent::Ephemeral(from_raw_json_value(&json)?))
|
Ok(AnyEvent::Ephemeral(from_raw_json_value(&json)?))
|
||||||
} else {
|
} else {
|
||||||
@ -149,12 +172,22 @@ impl<'de> de::Deserialize<'de> for AnyRoomEvent {
|
|||||||
D: de::Deserializer<'de>,
|
D: de::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
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() {
|
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 {
|
} 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>,
|
D: de::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
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() {
|
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 {
|
} 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::{
|
use crate::{
|
||||||
BasicEventContent, EphemeralRoomEventContent, EventContent, MessageEventContent,
|
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.
|
/// 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,
|
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.
|
/// State event.
|
||||||
#[derive(Clone, Debug, Event)]
|
#[derive(Clone, Debug, Event)]
|
||||||
pub struct StateEvent<C: StateEventContent> {
|
pub struct StateEvent<C: StateEventContent> {
|
||||||
@ -149,6 +191,76 @@ pub struct StrippedStateEventStub<C: StateEventContent> {
|
|||||||
pub state_key: String,
|
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.
|
/// An event sent using send-to-device messaging.
|
||||||
#[derive(Clone, Debug, Event)]
|
#[derive(Clone, Debug, Event)]
|
||||||
pub struct ToDeviceEvent<C: EventContent> {
|
pub struct ToDeviceEvent<C: EventContent> {
|
||||||
|
@ -166,13 +166,17 @@ pub use self::{
|
|||||||
enums::{
|
enums::{
|
||||||
AnyBasicEvent, AnyBasicEventContent, AnyEphemeralRoomEvent, AnyEphemeralRoomEventContent,
|
AnyBasicEvent, AnyBasicEventContent, AnyEphemeralRoomEvent, AnyEphemeralRoomEventContent,
|
||||||
AnyEphemeralRoomEventStub, AnyEvent, AnyMessageEvent, AnyMessageEventContent,
|
AnyEphemeralRoomEventStub, AnyEvent, AnyMessageEvent, AnyMessageEventContent,
|
||||||
AnyMessageEventStub, AnyRoomEvent, AnyRoomEventStub, AnyStateEvent, AnyStateEventContent,
|
AnyMessageEventStub, AnyRedactedMessageEvent, AnyRedactedMessageEventStub,
|
||||||
AnyStateEventStub, AnyStrippedStateEventStub, AnyToDeviceEvent, AnyToDeviceEventContent,
|
AnyRedactedStateEvent, AnyRedactedStateEventStub, AnyRedactedStrippedStateEventStub,
|
||||||
|
AnyRoomEvent, AnyRoomEventStub, AnyStateEvent, AnyStateEventContent, AnyStateEventStub,
|
||||||
|
AnyStrippedStateEventStub, AnyToDeviceEvent, AnyToDeviceEventContent,
|
||||||
},
|
},
|
||||||
error::{FromStrError, InvalidInput},
|
error::{FromStrError, InvalidInput},
|
||||||
event_kinds::{
|
event_kinds::{
|
||||||
BasicEvent, EphemeralRoomEvent, EphemeralRoomEventStub, MessageEvent, MessageEventStub,
|
BasicEvent, EphemeralRoomEvent, EphemeralRoomEventStub, MessageEvent, MessageEventStub,
|
||||||
StateEvent, StateEventStub, StrippedStateEventStub, ToDeviceEvent,
|
RedactedMessageEvent, RedactedMessageEventStub, RedactedStateEvent, RedactedStateEventStub,
|
||||||
|
RedactedStrippedStateEventStub, StateEvent, StateEventStub, StrippedStateEventStub,
|
||||||
|
ToDeviceEvent,
|
||||||
},
|
},
|
||||||
event_type::EventType,
|
event_type::EventType,
|
||||||
json::EventJson,
|
json::EventJson,
|
||||||
@ -237,6 +241,38 @@ pub trait MessageEventContent: RoomEventContent {}
|
|||||||
/// Marker trait for the content of a state event.
|
/// Marker trait for the content of a state event.
|
||||||
pub trait StateEventContent: RoomEventContent {}
|
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.
|
/// Helper struct to determine the event kind from a serde_json::value::RawValue.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[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
|
/// If no `event_id` or `state_key` are found but a `room_id` is present
|
||||||
/// the event will be deserialized as a ephemeral event.
|
/// the event will be deserialized as a ephemeral event.
|
||||||
pub room_id: Option<IgnoredAny>,
|
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.
|
/// Helper function for serde_json::value::RawValue deserialization.
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
use ruma_events_macros::StateEventContent;
|
use ruma_events_macros::StateEventContent;
|
||||||
use ruma_identifiers::RoomAliasId;
|
use ruma_identifiers::RoomAliasId;
|
||||||
use serde::{Deserialize, Serialize};
|
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.
|
/// Informs the room about what room aliases it has been given.
|
||||||
pub type AliasesEvent = StateEvent<AliasesEventContent>;
|
pub type AliasesEvent = StateEvent<AliasesEventContent>;
|
||||||
@ -12,7 +13,49 @@ pub type AliasesEvent = StateEvent<AliasesEventContent>;
|
|||||||
/// The payload for `AliasesEvent`.
|
/// The payload for `AliasesEvent`.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, StateEventContent)]
|
#[derive(Clone, Debug, Deserialize, Serialize, StateEventContent)]
|
||||||
#[ruma_event(type = "m.room.aliases")]
|
#[ruma_event(type = "m.room.aliases")]
|
||||||
|
#[ruma_event(custom_redacted)]
|
||||||
pub struct AliasesEventContent {
|
pub struct AliasesEventContent {
|
||||||
/// A list of room aliases.
|
/// A list of room aliases.
|
||||||
pub aliases: Vec<RoomAliasId>,
|
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")]
|
#[ruma_event(type = "m.room.create")]
|
||||||
pub struct CreateEventContent {
|
pub struct CreateEventContent {
|
||||||
/// The `user_id` of the room creator. This is set by the homeserver.
|
/// The `user_id` of the room creator. This is set by the homeserver.
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub creator: UserId,
|
pub creator: UserId,
|
||||||
|
|
||||||
/// Whether or not this room's data should be transferred to other homeservers.
|
/// 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")]
|
#[ruma_event(type = "m.room.history_visibility")]
|
||||||
pub struct HistoryVisibilityEventContent {
|
pub struct HistoryVisibilityEventContent {
|
||||||
/// Who can see the room history.
|
/// Who can see the room history.
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub history_visibility: HistoryVisibility,
|
pub history_visibility: HistoryVisibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ pub type JoinRulesEvent = StateEvent<JoinRulesEventContent>;
|
|||||||
#[ruma_event(type = "m.room.join_rules")]
|
#[ruma_event(type = "m.room.join_rules")]
|
||||||
pub struct JoinRulesEventContent {
|
pub struct JoinRulesEventContent {
|
||||||
/// The type of rules used for users wishing to join this room.
|
/// The type of rules used for users wishing to join this room.
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub join_rule: JoinRule,
|
pub join_rule: JoinRule,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ pub struct MemberEventContent {
|
|||||||
pub is_direct: Option<bool>,
|
pub is_direct: Option<bool>,
|
||||||
|
|
||||||
/// The membership state of this user.
|
/// The membership state of this user.
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub membership: MembershipState,
|
pub membership: MembershipState,
|
||||||
|
|
||||||
/// If this member event is the successor to a third party invitation, this field will
|
/// 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 {
|
pub struct PowerLevelsEventContent {
|
||||||
/// The level required to ban a user.
|
/// The level required to ban a user.
|
||||||
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub ban: Int,
|
pub ban: Int,
|
||||||
|
|
||||||
/// The level required to send specific event types.
|
/// The level required to send specific event types.
|
||||||
///
|
///
|
||||||
/// This is a mapping from event type to power level required.
|
/// This is a mapping from event type to power level required.
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub events: BTreeMap<EventType, Int>,
|
pub events: BTreeMap<EventType, Int>,
|
||||||
|
|
||||||
/// The default level required to send message events.
|
/// The default level required to send message events.
|
||||||
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub events_default: Int,
|
pub events_default: Int,
|
||||||
|
|
||||||
/// The level required to invite a user.
|
/// The level required to invite a user.
|
||||||
@ -36,24 +39,29 @@ pub struct PowerLevelsEventContent {
|
|||||||
|
|
||||||
/// The level required to kick a user.
|
/// The level required to kick a user.
|
||||||
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub kick: Int,
|
pub kick: Int,
|
||||||
|
|
||||||
/// The level required to redact an event.
|
/// The level required to redact an event.
|
||||||
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub redact: Int,
|
pub redact: Int,
|
||||||
|
|
||||||
/// The default level required to send state events.
|
/// The default level required to send state events.
|
||||||
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
#[serde(default = "default_power_level", skip_serializing_if = "is_default_power_level")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub state_default: Int,
|
pub state_default: Int,
|
||||||
|
|
||||||
/// The power levels for specific users.
|
/// The power levels for specific users.
|
||||||
///
|
///
|
||||||
/// This is a mapping from `user_id` to power level for that user.
|
/// This is a mapping from `user_id` to power level for that user.
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub users: BTreeMap<UserId, Int>,
|
pub users: BTreeMap<UserId, Int>,
|
||||||
|
|
||||||
/// The default power level for every user in the room.
|
/// The default power level for every user in the room.
|
||||||
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
||||||
|
#[ruma_event(skip_redaction)]
|
||||||
pub users_default: Int,
|
pub users_default: Int,
|
||||||
|
|
||||||
/// The power level requirements for specific notification types.
|
/// 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 ruma_identifiers::{EventId, RoomId, UserId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::UnsignedData;
|
use crate::{
|
||||||
|
MessageEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent,
|
||||||
|
UnsignedData,
|
||||||
|
};
|
||||||
|
|
||||||
/// Redaction event.
|
/// Redaction event.
|
||||||
#[derive(Clone, Debug, Event)]
|
#[derive(Clone, Debug, Event)]
|
||||||
@ -64,84 +67,10 @@ pub struct RedactionEventContent {
|
|||||||
pub reason: Option<String>,
|
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)]
|
impl RedactedMessageEventContent for RedactedRedactionEventContent {}
|
||||||
mod tests {
|
|
||||||
use std::{
|
|
||||||
convert::TryFrom,
|
|
||||||
time::{Duration, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
use matches::assert_matches;
|
impl RedactedStateEventContent for RedactedRedactionEventContent {}
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -9,6 +9,7 @@ use crate::BasicEvent;
|
|||||||
|
|
||||||
/// Informs the client of tags on a room.
|
/// Informs the client of tags on a room.
|
||||||
pub type TagEvent = BasicEvent<TagEventContent>;
|
pub type TagEvent = BasicEvent<TagEventContent>;
|
||||||
|
|
||||||
/// Map of tag names to tag info.
|
/// Map of tag names to tag info.
|
||||||
pub type Tags = BTreeMap<String, TagInfo>;
|
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
|
--> $DIR/03-invalid-event-type.rs:11:14
|
||||||
|
|
|
|
||||||
11 | #[ruma_event(event = "m.macro.test")]
|
11 | #[ruma_event(event = "m.macro.test")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user