Integrate generic event types and EventContent trait with TryFromRaw

This commit is contained in:
Jonas Platte 2020-05-31 17:29:22 +02:00
parent 3814690b29
commit 5091b9a9a8
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
8 changed files with 420 additions and 234 deletions

View File

@ -28,8 +28,8 @@ pub fn expand_collection(input: RumaCollectionInput) -> syn::Result<TokenStream>
let collection = quote! { let collection = quote! {
#( #attrs )* #( #attrs )*
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, /*Serialize*/)]
#[serde(untagged)] //#[serde(untagged)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum #ident { pub enum #ident {
#( #variants ),* #( #variants ),*

View File

@ -52,19 +52,18 @@ fn expand_room_event(input: DeriveInput) -> syn::Result<TokenStream> {
fn event_type(&self) -> &str { fn event_type(&self) -> &str {
#event_type #event_type
} }
}
impl ::ruma_events::RawEventContent for raw::#ident {
fn from_parts( fn from_parts(
ev_type: &str, ev_type: &str,
content: Box<::serde_json::value::RawValue> content: Box<::serde_json::value::RawValue>
) -> Result<Self, ::ruma_events::InvalidEvent> { ) -> Result<Self, String> {
if ev_type != #event_type { if ev_type != #event_type {
return Err( return Err(format!("expected `{}` found {}", #event_type, ev_type));
::ruma_events::InvalidEvent::wrong_event_type(#event_type, ev_type)
);
} }
let ev_json = ::ruma_events::EventJson::from(content); ::serde_json::from_str(content.get()).map_err(|e| e.to_string())
ev_json.deserialize()
} }
} }
}; };

View File

@ -9,7 +9,7 @@ use serde::de::DeserializeOwned;
/// [try]: trait.TryFromRaw.html /// [try]: trait.TryFromRaw.html
pub trait FromRaw: Sized { pub trait FromRaw: Sized {
/// The raw type. /// The raw type.
type Raw: DeserializeOwned; type Raw;
/// Converts the raw type to `Self`. /// Converts the raw type to `Self`.
fn from_raw(_: Self::Raw) -> Self; fn from_raw(_: Self::Raw) -> Self;
@ -19,7 +19,7 @@ pub trait FromRaw: Sized {
/// corresponding 'raw' type, a potentially invalid representation that can be converted to `Self`. /// corresponding 'raw' type, a potentially invalid representation that can be converted to `Self`.
pub trait TryFromRaw: Sized { pub trait TryFromRaw: Sized {
/// The raw type. /// The raw type.
type Raw: DeserializeOwned; type Raw;
/// The error type returned if conversion fails. /// The error type returned if conversion fails.
type Err: Display; type Err: Display;

View File

@ -5,14 +5,14 @@ use std::{
}; };
use serde::{ use serde::{
de::{Deserialize, Deserializer}, de::{Deserialize, DeserializeOwned, Deserializer},
ser::{Serialize, Serializer}, ser::{Serialize, Serializer},
}; };
use serde_json::value::RawValue; use serde_json::value::RawValue;
use crate::{ use crate::{
error::{InvalidEvent, InvalidEventKind}, error::{InvalidEvent, InvalidEventKind},
TryFromRaw, EventContent, RawEventContent, TryFromRaw,
}; };
/// A wrapper around `Box<RawValue>`, to be used in place of event [content] [collection] types in /// A wrapper around `Box<RawValue>`, to be used in place of event [content] [collection] types in
@ -42,7 +42,10 @@ impl<T> EventJson<T> {
} }
} }
impl<T: TryFromRaw> EventJson<T> { impl<T: TryFromRaw> EventJson<T>
where
T::Raw: DeserializeOwned,
{
/// Try to deserialize the JSON into the expected event type. /// Try to deserialize the JSON into the expected event type.
pub fn deserialize(&self) -> Result<T, InvalidEvent> { pub fn deserialize(&self) -> Result<T, InvalidEvent> {
let raw_ev: T::Raw = match serde_json::from_str(self.json.get()) { let raw_ev: T::Raw = match serde_json::from_str(self.json.get()) {
@ -65,6 +68,32 @@ impl<T: TryFromRaw> EventJson<T> {
} }
} }
impl<T: EventContent> EventJson<T>
where
T::Raw: RawEventContent,
{
/// Try to deserialize the JSON as event content
pub fn deserialize_content(self, event_type: &str) -> Result<T, InvalidEvent> {
let raw_content = match T::Raw::from_parts(event_type, self.json) {
Ok(raw) => raw,
Err(message) => {
return Err(InvalidEvent {
message,
kind: InvalidEventKind::Deserialization,
});
}
};
match T::try_from_raw(raw_content) {
Ok(value) => Ok(value),
Err(err) => Err(InvalidEvent {
message: err.to_string(),
kind: InvalidEventKind::Validation,
}),
}
}
}
impl<T: Serialize> From<&T> for EventJson<T> { impl<T: Serialize> From<&T> for EventJson<T> {
fn from(val: &T) -> Self { fn from(val: &T) -> Self {
Self::new(serde_json::value::to_raw_value(val).unwrap()) Self::new(serde_json::value::to_raw_value(val).unwrap())

View File

@ -168,7 +168,7 @@ pub use self::{
event_type::EventType, event_type::EventType,
from_raw::{FromRaw, TryFromRaw}, from_raw::{FromRaw, TryFromRaw},
json::EventJson, json::EventJson,
state::{AnyStateEventContent, StateEvent}, state::StateEvent,
}; };
/// Extra information about an event that is not incorporated into the event's /// Extra information about an event that is not incorporated into the event's
@ -207,19 +207,37 @@ impl UnsignedData {
/// The base trait that all event content types implement. /// The base trait that all event content types implement.
/// ///
/// Implementing this trait allows content types to be serialized as well as deserialized. /// Implementing this trait allows content types to be serialized as well as deserialized.
pub trait EventContent: Sized + Serialize { pub trait EventContent: TryFromRaw + Serialize
/// Constructs the given event content. where
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, InvalidEvent>; Self::Raw: RawEventContent,
{
/// A matrix event identifier, like `m.room.message`. /// A matrix event identifier, like `m.room.message`.
fn event_type(&self) -> &str; fn event_type(&self) -> &str;
} }
#[doc(hidden)]
pub trait RawEventContent: Sized {
/// Constructs the given event content.
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, String>;
}
/// Marker trait for the content of a room event. /// Marker trait for the content of a room event.
pub trait RoomEventContent: EventContent {} pub trait RoomEventContent: EventContent
where
Self::Raw: RawEventContent,
{
}
/// Marker trait for the content of a message event. /// Marker trait for the content of a message event.
pub trait MessageEventContent: RoomEventContent {} pub trait MessageEventContent: RoomEventContent
where
Self::Raw: RawEventContent,
{
}
/// 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
where
Self::Raw: RawEventContent,
{
}

View File

@ -16,7 +16,7 @@ pub mod encryption;
pub mod guest_access; pub mod guest_access;
pub mod history_visibility; pub mod history_visibility;
pub mod join_rules; pub mod join_rules;
// pub mod member; pub mod member;
// pub mod message; // pub mod message;
// pub mod name; // pub mod name;
// pub mod pinned_events; // pub mod pinned_events;

View File

@ -7,6 +7,8 @@ use ruma_identifiers::UserId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::{Display, EnumString}; use strum::{Display, EnumString};
use crate::StateEvent;
/// The current membership state of a user in the room. /// The current membership state of a user in the room.
/// ///
/// Adjusts the membership state for a user in a room. It is preferable to use the membership /// Adjusts the membership state for a user in a room. It is preferable to use the membership
@ -150,44 +152,44 @@ pub enum MembershipChange {
NotImplemented, NotImplemented,
} }
// impl MemberEvent { impl StateEvent<MemberEventContent> {
// /// Helper function for membership change. Check [the specification][spec] for details. /// Helper function for membership change. Check [the specification][spec] for details.
// /// ///
// /// [spec]: https://matrix.org/docs/spec/client_server/latest#m-room-member /// [spec]: https://matrix.org/docs/spec/client_server/latest#m-room-member
// pub fn membership_change(&self) -> MembershipChange { pub fn membership_change(&self) -> MembershipChange {
// use MembershipState::*; use MembershipState::*;
// let prev_membership = if let Some(prev_content) = &self.prev_content { let prev_membership = if let Some(prev_content) = &self.prev_content {
// prev_content.membership prev_content.membership
// } else { } else {
// Leave Leave
// }; };
// match (prev_membership, &self.content.membership) { match (prev_membership, &self.content.membership) {
// (Invite, Invite) | (Leave, Leave) | (Ban, Ban) => MembershipChange::None, (Invite, Invite) | (Leave, Leave) | (Ban, Ban) => MembershipChange::None,
// (Invite, Join) | (Leave, Join) => MembershipChange::Joined, (Invite, Join) | (Leave, Join) => MembershipChange::Joined,
// (Invite, Leave) => { (Invite, Leave) => {
// if self.sender == self.state_key { if self.sender == self.state_key {
// MembershipChange::InvitationRevoked MembershipChange::InvitationRevoked
// } else { } else {
// MembershipChange::InvitationRejected MembershipChange::InvitationRejected
// } }
// } }
// (Invite, Ban) | (Leave, Ban) => MembershipChange::Banned, (Invite, Ban) | (Leave, Ban) => MembershipChange::Banned,
// (Join, Invite) | (Ban, Invite) | (Ban, Join) => MembershipChange::Error, (Join, Invite) | (Ban, Invite) | (Ban, Join) => MembershipChange::Error,
// (Join, Join) => MembershipChange::ProfileChanged, (Join, Join) => MembershipChange::ProfileChanged,
// (Join, Leave) => { (Join, Leave) => {
// if self.sender == self.state_key { if self.sender == self.state_key {
// MembershipChange::Left MembershipChange::Left
// } else { } else {
// MembershipChange::Kicked MembershipChange::Kicked
// } }
// } }
// (Join, Ban) => MembershipChange::KickedAndBanned, (Join, Ban) => MembershipChange::KickedAndBanned,
// (Leave, Invite) => MembershipChange::Invited, (Leave, Invite) => MembershipChange::Invited,
// (Ban, Leave) => MembershipChange::Unbanned, (Ban, Leave) => MembershipChange::Unbanned,
// (Knock, _) | (_, Knock) => MembershipChange::NotImplemented, (Knock, _) | (_, Knock) => MembershipChange::NotImplemented,
// } }
// } }
// } }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -197,10 +199,8 @@ mod tests {
use matches::assert_matches; use matches::assert_matches;
use serde_json::{from_value as from_json_value, json}; use serde_json::{from_value as from_json_value, json};
use super::{ use super::{MemberEventContent, MembershipState, SignedContent, ThirdPartyInvite};
MemberEvent, MemberEventContent, MembershipState, SignedContent, ThirdPartyInvite, use crate::{EventJson, StateEvent};
};
use crate::EventJson;
#[test] #[test]
fn serde_with_no_prev_content() { fn serde_with_no_prev_content() {
@ -217,11 +217,11 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<EventJson<MemberEvent>>(json) from_json_value::<EventJson<StateEvent<MemberEventContent>>>(json)
.unwrap() .unwrap()
.deserialize() .deserialize()
.unwrap(), .unwrap(),
MemberEvent { StateEvent::<MemberEventContent> {
content: MemberEventContent { content: MemberEventContent {
avatar_url: None, avatar_url: None,
displayname: None, displayname: None,
@ -231,7 +231,7 @@ mod tests {
}, },
event_id, event_id,
origin_server_ts, origin_server_ts,
room_id: Some(room_id), room_id,
sender, sender,
state_key, state_key,
unsigned, unsigned,
@ -263,11 +263,11 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<EventJson<MemberEvent>>(json) from_json_value::<EventJson<StateEvent<MemberEventContent>>>(json)
.unwrap() .unwrap()
.deserialize() .deserialize()
.unwrap(), .unwrap(),
MemberEvent { StateEvent::<MemberEventContent> {
content: MemberEventContent { content: MemberEventContent {
avatar_url: None, avatar_url: None,
displayname: None, displayname: None,
@ -277,7 +277,7 @@ mod tests {
}, },
event_id, event_id,
origin_server_ts, origin_server_ts,
room_id: Some(room_id), room_id,
sender, sender,
state_key, state_key,
unsigned, unsigned,
@ -327,11 +327,11 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<EventJson<MemberEvent>>(json) from_json_value::<EventJson<StateEvent<MemberEventContent>>>(json)
.unwrap() .unwrap()
.deserialize() .deserialize()
.unwrap(), .unwrap(),
MemberEvent { StateEvent::<MemberEventContent> {
content: MemberEventContent { content: MemberEventContent {
avatar_url: Some(avatar_url), avatar_url: Some(avatar_url),
displayname: Some(displayname), displayname: Some(displayname),
@ -344,7 +344,7 @@ mod tests {
}, },
event_id, event_id,
origin_server_ts, origin_server_ts,
room_id: Some(room_id), room_id,
sender, sender,
state_key, state_key,
unsigned, unsigned,
@ -401,11 +401,11 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<EventJson<MemberEvent>>(json) from_json_value::<EventJson<StateEvent<MemberEventContent>>>(json)
.unwrap() .unwrap()
.deserialize() .deserialize()
.unwrap(), .unwrap(),
MemberEvent { StateEvent::<MemberEventContent> {
content: MemberEventContent { content: MemberEventContent {
avatar_url: None, avatar_url: None,
displayname: None, displayname: None,
@ -415,7 +415,7 @@ mod tests {
}, },
event_id, event_id,
origin_server_ts, origin_server_ts,
room_id: Some(room_id), room_id,
sender, sender,
state_key, state_key,
unsigned, unsigned,

View File

@ -20,7 +20,8 @@ use serde_json::value::RawValue as RawJsonValue;
use crate::{ use crate::{
error::{InvalidEvent, InvalidEventKind}, error::{InvalidEvent, InvalidEventKind},
room::{aliases::AliasesEventContent, avatar::AvatarEventContent}, room::{aliases::AliasesEventContent, avatar::AvatarEventContent},
EventContent, RoomEventContent, StateEventContent, TryFromRaw, UnsignedData, EventContent, FromRaw, RawEventContent, RoomEventContent, StateEventContent, TryFromRaw,
UnsignedData,
}; };
use ruma_events_macros::event_content_collection; use ruma_events_macros::event_content_collection;
@ -32,7 +33,10 @@ event_content_collection! {
/// State event. /// State event.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct StateEvent<C: StateEventContent> { pub struct StateEvent<C: StateEventContent>
where
C::Raw: RawEventContent,
{
/// Data specific to the event type. /// Data specific to the event type.
pub content: C, pub content: C,
@ -61,6 +65,31 @@ pub struct StateEvent<C: StateEventContent> {
pub unsigned: UnsignedData, pub unsigned: UnsignedData,
} }
impl FromRaw for AnyStateEventContent {
type Raw = raw::AnyStateEventContent;
fn from_raw(raw: Self::Raw) -> Self {
use raw::AnyStateEventContent::*;
match raw {
RoomAliases(c) => Self::RoomAliases(FromRaw::from_raw(c)),
RoomAvatar(c) => Self::RoomAvatar(FromRaw::from_raw(c)),
}
}
}
impl Serialize for AnyStateEventContent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
AnyStateEventContent::RoomAliases(content) => content.serialize(serializer),
AnyStateEventContent::RoomAvatar(content) => content.serialize(serializer),
}
}
}
impl EventContent for AnyStateEventContent { impl EventContent for AnyStateEventContent {
fn event_type(&self) -> &str { fn event_type(&self) -> &str {
match self { match self {
@ -68,41 +97,38 @@ impl EventContent for AnyStateEventContent {
AnyStateEventContent::RoomAvatar(content) => content.event_type(), AnyStateEventContent::RoomAvatar(content) => content.event_type(),
} }
} }
fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, InvalidEvent> {
fn deserialize_variant<T: StateEventContent>(
ev_type: &str,
input: Box<RawJsonValue>,
variant: fn(T) -> AnyStateEventContent,
) -> Result<AnyStateEventContent, InvalidEvent> {
let content = T::from_parts(ev_type, input)?;
Ok(variant(content))
}
match event_type {
"m.room.avatar" => deserialize_variant::<AvatarEventContent>(
event_type,
content,
AnyStateEventContent::RoomAvatar,
),
"m.room.aliases" => deserialize_variant::<AliasesEventContent>(
event_type,
content,
AnyStateEventContent::RoomAliases,
),
ev => Err(InvalidEvent {
kind: InvalidEventKind::Deserialization,
message: format!("event not supported {}", ev),
}),
}
}
} }
impl RoomEventContent for AnyStateEventContent {} impl RoomEventContent for AnyStateEventContent {}
impl StateEventContent for AnyStateEventContent {} impl StateEventContent for AnyStateEventContent {}
impl<C: StateEventContent> Serialize for StateEvent<C> { impl<C> TryFromRaw for StateEvent<C>
where
C: StateEventContent + TryFromRaw,
C::Raw: RawEventContent,
{
type Raw = raw::StateEvent<C::Raw>;
type Err = C::Err;
fn try_from_raw(raw: Self::Raw) -> Result<Self, Self::Err> {
Ok(Self {
content: C::try_from_raw(raw.content)?,
event_id: raw.event_id,
sender: raw.sender,
origin_server_ts: raw.origin_server_ts,
room_id: raw.room_id,
state_key: raw.state_key,
prev_content: raw.prev_content.map(C::try_from_raw).transpose()?,
unsigned: raw.unsigned,
})
}
}
impl<C: StateEventContent> Serialize for StateEvent<C>
where
C::Raw: RawEventContent,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@ -130,146 +156,238 @@ impl<C: StateEventContent> Serialize for StateEvent<C> {
} }
} }
impl<'de, C: StateEventContent> Deserialize<'de> for StateEvent<C> { mod raw {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> use std::{
where fmt,
D: Deserializer<'de>, marker::PhantomData,
{ time::{Duration, SystemTime, UNIX_EPOCH},
deserializer.deserialize_map(StateEventVisitor(std::marker::PhantomData)) };
}
}
#[derive(serde::Deserialize)] use js_int::UInt;
#[serde(field_identifier, rename_all = "snake_case")] use ruma_events_macros::event_content_collection;
enum Field { use ruma_identifiers::{EventId, RoomId, UserId};
Type, use serde::{
Content, de::{self, Deserialize, Deserializer, Error as _, MapAccess, Visitor},
EventId, Serialize,
Sender, };
OriginServerTs, use serde_json::value::RawValue as RawJsonValue;
RoomId,
StateKey,
PrevContent,
Unsigned,
}
/// Visits the fields of a StateEvent<C> to handle deserialization of use crate::{
/// the `content` and `prev_content` fields. room::{aliases::raw::AliasesEventContent, avatar::raw::AvatarEventContent},
struct StateEventVisitor<C: StateEventContent>(PhantomData<C>); RawEventContent, UnsignedData,
};
impl<'de, C: StateEventContent> Visitor<'de> for StateEventVisitor<C> { event_content_collection! {
type Value = StateEvent<C>; /// A state event.
name: AnyStateEventContent,
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { events: ["m.room.aliases", "m.room.avatar"]
write!(formatter, "struct implementing StateEventContent")
} }
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> impl RawEventContent for AnyStateEventContent {
where fn from_parts(event_type: &str, content: Box<RawJsonValue>) -> Result<Self, String> {
A: MapAccess<'de>, fn deserialize_variant<T: RawEventContent>(
{ ev_type: &str,
let mut content: Option<Box<RawJsonValue>> = None; input: Box<RawJsonValue>,
let mut event_type: Option<String> = None; variant: fn(T) -> AnyStateEventContent,
let mut event_id: Option<EventId> = None; ) -> Result<AnyStateEventContent, String> {
let mut sender: Option<UserId> = None; let content = T::from_parts(ev_type, input)?;
let mut origin_server_ts: Option<UInt> = None; Ok(variant(content))
let mut room_id: Option<RoomId> = None; }
let mut state_key: Option<String> = None;
let mut prev_content: Option<Box<RawJsonValue>> = None;
let mut unsigned: Option<UnsignedData> = None;
while let Some(key) = map.next_key()? { match event_type {
match key { "m.room.avatar" => deserialize_variant::<AvatarEventContent>(
Field::Content => { event_type,
if content.is_some() { content,
return Err(de::Error::duplicate_field("content")); AnyStateEventContent::RoomAvatar,
} ),
content = Some(map.next_value()?); "m.room.aliases" => deserialize_variant::<AliasesEventContent>(
} event_type,
Field::EventId => { content,
if event_id.is_some() { AnyStateEventContent::RoomAliases,
return Err(de::Error::duplicate_field("event_id")); ),
} ev => Err(format!("event not supported {}", ev)),
event_id = Some(map.next_value()?);
}
Field::Sender => {
if sender.is_some() {
return Err(de::Error::duplicate_field("sender"));
}
sender = Some(map.next_value()?);
}
Field::OriginServerTs => {
if origin_server_ts.is_some() {
return Err(de::Error::duplicate_field("origin_server_ts"));
}
origin_server_ts = Some(map.next_value()?);
}
Field::RoomId => {
if room_id.is_some() {
return Err(de::Error::duplicate_field("room_id"));
}
room_id = Some(map.next_value()?);
}
Field::StateKey => {
if state_key.is_some() {
return Err(de::Error::duplicate_field("state_key"));
}
state_key = Some(map.next_value()?);
}
Field::PrevContent => {
if prev_content.is_some() {
return Err(de::Error::duplicate_field("prev_content"));
}
prev_content = Some(map.next_value()?);
}
Field::Type => {
if event_type.is_some() {
return Err(de::Error::duplicate_field("type"));
}
event_type = Some(map.next_value()?);
}
Field::Unsigned => {
if unsigned.is_some() {
return Err(de::Error::duplicate_field("unsigned"));
}
unsigned = Some(map.next_value()?);
}
} }
} }
}
let event_type = event_type.ok_or_else(|| de::Error::missing_field("type"))?; /// State event.
#[derive(Clone, Debug)]
pub struct StateEvent<C> {
/// Data specific to the event type.
pub content: C,
let raw = content.ok_or_else(|| de::Error::missing_field("content"))?; /// The globally unique event identifier for the user who sent the event.
let content = C::from_parts(&event_type, raw).map_err(A::Error::custom)?; pub event_id: EventId,
let event_id = event_id.ok_or_else(|| de::Error::missing_field("event_id"))?; /// Contains the fully-qualified ID of the user who sent this event.
let sender = sender.ok_or_else(|| de::Error::missing_field("sender"))?; pub sender: UserId,
let origin_server_ts = origin_server_ts /// Timestamp in milliseconds on originating homeserver when this event was sent.
.map(|time| UNIX_EPOCH + Duration::from_millis(time.into())) pub origin_server_ts: SystemTime,
.ok_or_else(|| de::Error::missing_field("origin_server_ts"))?;
let room_id = room_id.ok_or_else(|| de::Error::missing_field("room_id"))?; /// The ID of the room associated with this event.
let state_key = state_key.ok_or_else(|| de::Error::missing_field("state_key"))?; pub room_id: RoomId,
let prev_content = if let Some(raw) = prev_content { /// A unique key which defines the overwriting semantics for this piece of room state.
Some(C::from_parts(&event_type, raw).map_err(A::Error::custom)?) ///
} else { /// This is often an empty string, but some events send a `UserId` to show
None /// which user the event affects.
}; pub state_key: String,
let unsigned = unsigned.unwrap_or_default(); /// Optional previous content for this event.
pub prev_content: Option<C>,
Ok(StateEvent { /// Additional key-value pairs not signed by the homeserver.
content, pub unsigned: UnsignedData,
event_id, }
sender,
origin_server_ts, impl<'de, C> Deserialize<'de> for StateEvent<C>
room_id, where
state_key, C: RawEventContent,
prev_content, {
unsigned, fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
}) where
D: Deserializer<'de>,
{
deserializer.deserialize_map(StateEventVisitor(std::marker::PhantomData))
}
}
#[derive(serde::Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Type,
Content,
EventId,
Sender,
OriginServerTs,
RoomId,
StateKey,
PrevContent,
Unsigned,
}
/// Visits the fields of a StateEvent<C> to handle deserialization of
/// the `content` and `prev_content` fields.
struct StateEventVisitor<C>(PhantomData<C>);
impl<'de, C> Visitor<'de> for StateEventVisitor<C>
where
C: RawEventContent,
{
type Value = StateEvent<C>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "struct implementing StateEventContent")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut content: Option<Box<RawJsonValue>> = None;
let mut event_type: Option<String> = None;
let mut event_id: Option<EventId> = None;
let mut sender: Option<UserId> = None;
let mut origin_server_ts: Option<UInt> = None;
let mut room_id: Option<RoomId> = None;
let mut state_key: Option<String> = None;
let mut prev_content: Option<Box<RawJsonValue>> = None;
let mut unsigned: Option<UnsignedData> = None;
while let Some(key) = map.next_key()? {
match key {
Field::Content => {
if content.is_some() {
return Err(de::Error::duplicate_field("content"));
}
content = Some(map.next_value()?);
}
Field::EventId => {
if event_id.is_some() {
return Err(de::Error::duplicate_field("event_id"));
}
event_id = Some(map.next_value()?);
}
Field::Sender => {
if sender.is_some() {
return Err(de::Error::duplicate_field("sender"));
}
sender = Some(map.next_value()?);
}
Field::OriginServerTs => {
if origin_server_ts.is_some() {
return Err(de::Error::duplicate_field("origin_server_ts"));
}
origin_server_ts = Some(map.next_value()?);
}
Field::RoomId => {
if room_id.is_some() {
return Err(de::Error::duplicate_field("room_id"));
}
room_id = Some(map.next_value()?);
}
Field::StateKey => {
if state_key.is_some() {
return Err(de::Error::duplicate_field("state_key"));
}
state_key = Some(map.next_value()?);
}
Field::PrevContent => {
if prev_content.is_some() {
return Err(de::Error::duplicate_field("prev_content"));
}
prev_content = Some(map.next_value()?);
}
Field::Type => {
if event_type.is_some() {
return Err(de::Error::duplicate_field("type"));
}
event_type = Some(map.next_value()?);
}
Field::Unsigned => {
if unsigned.is_some() {
return Err(de::Error::duplicate_field("unsigned"));
}
unsigned = Some(map.next_value()?);
}
}
}
let event_type = event_type.ok_or_else(|| de::Error::missing_field("type"))?;
let raw = content.ok_or_else(|| de::Error::missing_field("content"))?;
let content = C::from_parts(&event_type, raw).map_err(A::Error::custom)?;
let event_id = event_id.ok_or_else(|| de::Error::missing_field("event_id"))?;
let sender = sender.ok_or_else(|| de::Error::missing_field("sender"))?;
let origin_server_ts = origin_server_ts
.map(|time| UNIX_EPOCH + Duration::from_millis(time.into()))
.ok_or_else(|| de::Error::missing_field("origin_server_ts"))?;
let room_id = room_id.ok_or_else(|| de::Error::missing_field("room_id"))?;
let state_key = state_key.ok_or_else(|| de::Error::missing_field("state_key"))?;
let prev_content = if let Some(raw) = prev_content {
Some(C::from_parts(&event_type, raw).map_err(A::Error::custom)?)
} else {
None
};
let unsigned = unsigned.unwrap_or_default();
Ok(StateEvent {
content,
event_id,
sender,
origin_server_ts,
room_id,
state_key,
prev_content,
unsigned,
})
}
} }
} }
@ -288,7 +406,7 @@ mod tests {
use super::{AliasesEventContent, AnyStateEventContent, AvatarEventContent, StateEvent}; use super::{AliasesEventContent, AnyStateEventContent, AvatarEventContent, StateEvent};
use crate::{ use crate::{
room::{ImageInfo, ThumbnailInfo}, room::{ImageInfo, ThumbnailInfo},
UnsignedData, EventJson, UnsignedData,
}; };
#[test] #[test]
@ -358,6 +476,22 @@ mod tests {
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test]
fn deserialize_aliases_content() {
let json_data = json!({
"aliases": [ "#somewhere:localhost" ]
});
assert_matches!(
from_json_value::<EventJson<AnyStateEventContent>>(json_data)
.unwrap()
.deserialize_content("m.room.aliases")
.unwrap(),
AnyStateEventContent::RoomAliases(content)
if content.aliases == vec![RoomAliasId::try_from("#somewhere:localhost").unwrap()]
);
}
#[test] #[test]
fn deserialize_aliases_with_prev_content() { fn deserialize_aliases_with_prev_content() {
let json_data = json!({ let json_data = json!({
@ -376,7 +510,10 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<StateEvent<AnyStateEventContent>>(json_data).unwrap(), from_json_value::<EventJson<StateEvent<AnyStateEventContent>>>(json_data)
.unwrap()
.deserialize()
.unwrap(),
StateEvent { StateEvent {
content: AnyStateEventContent::RoomAliases(content), content: AnyStateEventContent::RoomAliases(content),
event_id, event_id,
@ -425,7 +562,10 @@ mod tests {
}); });
assert_matches!( assert_matches!(
from_json_value::<StateEvent<AnyStateEventContent>>(json_data).unwrap(), from_json_value::<EventJson<StateEvent<AnyStateEventContent>>>(json_data)
.unwrap()
.deserialize()
.unwrap(),
StateEvent { StateEvent {
content: AnyStateEventContent::RoomAvatar(AvatarEventContent { content: AnyStateEventContent::RoomAvatar(AvatarEventContent {
info: Some(info), info: Some(info),