Integrate generic event types and EventContent trait with TryFromRaw
This commit is contained in:
parent
3814690b29
commit
5091b9a9a8
@ -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 ),*
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
35
src/json.rs
35
src/json.rs
@ -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())
|
||||||
|
34
src/lib.rs
34
src/lib.rs
@ -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,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
456
src/state.rs
456
src/state.rs
@ -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),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user