use std::{collections::BTreeMap, time::Duration};
use as_variant::as_variant;
use js_int::UInt;
use ruma_common::{
OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId,
api::{auth_scheme::AccessToken, request, response},
metadata,
presence::PresenceState,
serde::Raw,
};
use ruma_events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyToDeviceEvent,
presence::PresenceEvent,
};
use serde::{Deserialize, Serialize};
mod response_serde;
use super::{DeviceLists, UnreadNotificationsCount};
use crate::filter::FilterDefinition;
metadata! {
method: GET,
rate_limited: false,
authentication: AccessToken,
history: {
1.0 => "/_matrix/client/r0/sync",
1.1 => "/_matrix/client/v3/sync",
}
}
#[request(error = crate::Error)]
#[derive(Default)]
pub struct Request {
#[serde(skip_serializing_if = "Option::is_none")]
#[ruma_api(query)]
pub filter: Option<Filter>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ruma_api(query)]
pub since: Option<String>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
#[ruma_api(query)]
pub full_state: bool,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
#[ruma_api(query)]
pub set_presence: PresenceState,
#[serde(
with = "ruma_common::serde::duration::opt_ms",
default,
skip_serializing_if = "Option::is_none"
)]
#[ruma_api(query)]
pub timeout: Option<Duration>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
#[ruma_api(query)]
pub use_state_after: bool,
}
#[response(error = crate::Error)]
pub struct Response {
pub next_batch: String,
#[serde(default, skip_serializing_if = "Rooms::is_empty")]
pub rooms: Rooms,
#[serde(default, skip_serializing_if = "Presence::is_empty")]
pub presence: Presence,
#[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
pub account_data: GlobalAccountData,
#[serde(default, skip_serializing_if = "ToDevice::is_empty")]
pub to_device: ToDevice,
#[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
pub device_lists: DeviceLists,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
}
impl Request {
pub fn new() -> Self {
Default::default()
}
}
impl Response {
pub fn new(next_batch: String) -> Self {
Self {
next_batch,
rooms: Default::default(),
presence: Default::default(),
account_data: Default::default(),
to_device: Default::default(),
device_lists: Default::default(),
device_one_time_keys_count: BTreeMap::new(),
device_unused_fallback_key_types: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[allow(clippy::large_enum_variant)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[serde(untagged)]
pub enum Filter {
#[serde(with = "ruma_common::serde::json_string")]
FilterDefinition(FilterDefinition),
FilterId(String),
}
impl From<FilterDefinition> for Filter {
fn from(def: FilterDefinition) -> Self {
Self::FilterDefinition(def)
}
}
impl From<String> for Filter {
fn from(id: String) -> Self {
Self::FilterId(id)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Rooms {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
}
impl Rooms {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
}
}
#[derive(Clone, Debug, Default, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct LeftRoom {
#[serde(skip_serializing_if = "Timeline::is_empty")]
pub timeline: Timeline,
#[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
pub state: State,
#[serde(skip_serializing_if = "RoomAccountData::is_empty")]
pub account_data: RoomAccountData,
}
impl LeftRoom {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
}
}
#[derive(Clone, Debug, Default, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct JoinedRoom {
#[serde(skip_serializing_if = "RoomSummary::is_empty")]
pub summary: RoomSummary,
#[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
pub unread_notifications: UnreadNotificationsCount,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
#[serde(skip_serializing_if = "Timeline::is_empty")]
pub timeline: Timeline,
#[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
pub state: State,
#[serde(skip_serializing_if = "RoomAccountData::is_empty")]
pub account_data: RoomAccountData,
#[serde(skip_serializing_if = "Ephemeral::is_empty")]
pub ephemeral: Ephemeral,
#[cfg(feature = "unstable-msc2654")]
#[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
pub unread_count: Option<UInt>,
}
impl JoinedRoom {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
let is_empty = self.summary.is_empty()
&& self.unread_notifications.is_empty()
&& self.unread_thread_notifications.is_empty()
&& self.timeline.is_empty()
&& self.state.is_empty()
&& self.account_data.is_empty()
&& self.ephemeral.is_empty();
#[cfg(not(feature = "unstable-msc2654"))]
return is_empty;
#[cfg(feature = "unstable-msc2654")]
return is_empty && self.unread_count.is_none();
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct KnockedRoom {
#[serde(default, skip_serializing_if = "KnockState::is_empty")]
pub knock_state: KnockState,
}
impl KnockedRoom {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.knock_state.is_empty()
}
}
impl From<KnockState> for KnockedRoom {
fn from(knock_state: KnockState) -> Self {
KnockedRoom { knock_state, ..Default::default() }
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct KnockState {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyStrippedStateEvent>>,
}
impl KnockState {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Timeline {
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub limited: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub prev_batch: Option<String>,
pub events: Vec<Raw<AnySyncTimelineEvent>>,
}
impl Timeline {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
!self.limited && self.prev_batch.is_none() && self.events.is_empty()
}
}
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub enum State {
#[serde(rename = "state")]
Before(StateEvents),
#[serde(rename = "state_after")]
After(StateEvents),
}
impl State {
fn is_before_and_empty(&self) -> bool {
as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
}
pub fn is_empty(&self) -> bool {
match self {
Self::Before(state) => state.is_empty(),
Self::After(state) => state.is_empty(),
}
}
}
impl Default for State {
fn default() -> Self {
Self::Before(Default::default())
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct StateEvents {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnySyncStateEvent>>,
}
impl StateEvents {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
Self { events, ..Default::default() }
}
}
impl From<Vec<Raw<AnySyncStateEvent>>> for StateEvents {
fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
Self::with_events(events)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct GlobalAccountData {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
}
impl GlobalAccountData {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RoomAccountData {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
}
impl RoomAccountData {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Ephemeral {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
}
impl Ephemeral {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RoomSummary {
#[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
pub heroes: Vec<OwnedUserId>,
#[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
pub joined_member_count: Option<UInt>,
#[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
pub invited_member_count: Option<UInt>,
}
impl RoomSummary {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.heroes.is_empty()
&& self.joined_member_count.is_none()
&& self.invited_member_count.is_none()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct InvitedRoom {
#[serde(default, skip_serializing_if = "InviteState::is_empty")]
pub invite_state: InviteState,
}
impl InvitedRoom {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.invite_state.is_empty()
}
}
impl From<InviteState> for InvitedRoom {
fn from(invite_state: InviteState) -> Self {
InvitedRoom { invite_state, ..Default::default() }
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct InviteState {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyStrippedStateEvent>>,
}
impl InviteState {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
impl From<Vec<Raw<AnyStrippedStateEvent>>> for InviteState {
fn from(events: Vec<Raw<AnyStrippedStateEvent>>) -> Self {
InviteState { events, ..Default::default() }
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct Presence {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<PresenceEvent>>,
}
impl Presence {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct ToDevice {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<Raw<AnyToDeviceEvent>>,
}
impl ToDevice {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
#[cfg(test)]
mod tests {
use assign::assign;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::Timeline;
#[test]
fn timeline_serde() {
let timeline = assign!(Timeline::new(), { limited: true });
let timeline_serialized = json!({ "events": [], "limited": true });
assert_eq!(to_json_value(timeline).unwrap(), timeline_serialized);
let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
assert!(timeline_deserialized.limited);
let timeline_default = Timeline::default();
assert_eq!(to_json_value(timeline_default).unwrap(), json!({ "events": [] }));
let timeline_default_deserialized =
from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
assert!(!timeline_default_deserialized.limited);
}
}
#[cfg(all(test, feature = "client"))]
mod client_tests {
use std::{borrow::Cow, time::Duration};
use assert_matches2::assert_matches;
use ruma_common::{
RoomVersionId,
api::{
IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SupportedVersions,
auth_scheme::SendAccessToken,
},
event_id, room_id, user_id,
};
use ruma_events::AnyStrippedStateEvent;
use serde_json::{Value as JsonValue, json, to_vec as to_json_vec};
use super::{Filter, PresenceState, Request, Response, State};
fn sync_state_event() -> JsonValue {
json!({
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 1_432_735_824,
"sender": "@alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234,
"membership": "join",
},
})
}
#[test]
fn serialize_request_all_params() {
let supported = SupportedVersions {
versions: [MatrixVersion::V1_1].into(),
features: Default::default(),
};
let req: http::Request<Vec<u8>> = Request {
filter: Some(Filter::FilterId("66696p746572".to_owned())),
since: Some("s72594_4483_1934".to_owned()),
full_state: true,
set_presence: PresenceState::Offline,
timeout: Some(Duration::from_millis(30000)),
use_state_after: true,
}
.try_into_http_request(
"https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"),
Cow::Owned(supported),
)
.unwrap();
let uri = req.uri();
let query = uri.query().unwrap();
assert_eq!(uri.path(), "/_matrix/client/v3/sync");
assert!(query.contains("filter=66696p746572"));
assert!(query.contains("since=s72594_4483_1934"));
assert!(query.contains("full_state=true"));
assert!(query.contains("set_presence=offline"));
assert!(query.contains("timeout=30000"));
assert!(query.contains("use_state_after=true"));
}
#[test]
fn deserialize_response_invite() {
let creator = user_id!("@creator:localhost");
let invitee = user_id!("@invitee:localhost");
let room_id = room_id!("!privateroom:localhost");
let event_id = event_id!("$invite");
let body = json!({
"next_batch": "a00",
"rooms": {
"invite": {
room_id: {
"invite_state": {
"events": [
{
"content": {
"room_version": "11",
},
"type": "m.room.create",
"state_key": "",
"sender": creator,
},
{
"content": {
"membership": "invite",
},
"type": "m.room.member",
"state_key": invitee,
"sender": creator,
"origin_server_ts": 4_345_456,
"event_id": event_id,
},
],
},
},
},
},
});
let http_response = http::Response::new(to_json_vec(&body).unwrap());
let response = Response::try_from_http_response(http_response).unwrap();
assert_eq!(response.next_batch, "a00");
let private_room = response.rooms.invite.get(room_id).unwrap();
let first_event = private_room.invite_state.events[0].deserialize().unwrap();
assert_matches!(first_event, AnyStrippedStateEvent::RoomCreate(create_event));
assert_eq!(create_event.sender, creator);
assert_eq!(create_event.content.room_version, RoomVersionId::V11);
}
#[test]
fn deserialize_response_no_state() {
let joined_room_id = room_id!("!joined:localhost");
let left_room_id = room_id!("!left:localhost");
let event = sync_state_event();
let body = json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"timeline": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"timeline": {
"events": [
event,
],
},
},
},
},
});
let http_response = http::Response::new(to_json_vec(&body).unwrap());
let response = Response::try_from_http_response(http_response).unwrap();
assert_eq!(response.next_batch, "aaa");
let joined_room = response.rooms.join.get(joined_room_id).unwrap();
assert_eq!(joined_room.timeline.events.len(), 1);
assert!(joined_room.state.is_before_and_empty());
let left_room = response.rooms.leave.get(left_room_id).unwrap();
assert_eq!(left_room.timeline.events.len(), 1);
assert!(left_room.state.is_before_and_empty());
}
#[test]
fn deserialize_response_state_before() {
let joined_room_id = room_id!("!joined:localhost");
let left_room_id = room_id!("!left:localhost");
let event = sync_state_event();
let body = json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"state": {
"events": [
event,
],
},
},
},
},
});
let http_response = http::Response::new(to_json_vec(&body).unwrap());
let response = Response::try_from_http_response(http_response).unwrap();
assert_eq!(response.next_batch, "aaa");
let joined_room = response.rooms.join.get(joined_room_id).unwrap();
assert!(joined_room.timeline.is_empty());
assert_matches!(&joined_room.state, State::Before(state));
assert_eq!(state.events.len(), 1);
let left_room = response.rooms.leave.get(left_room_id).unwrap();
assert!(left_room.timeline.is_empty());
assert_matches!(&left_room.state, State::Before(state));
assert_eq!(state.events.len(), 1);
}
#[test]
fn deserialize_response_empty_state_after() {
let joined_room_id = room_id!("!joined:localhost");
let left_room_id = room_id!("!left:localhost");
let body = json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state_after": {},
},
},
"leave": {
left_room_id: {
"state_after": {},
},
},
},
});
let http_response = http::Response::new(to_json_vec(&body).unwrap());
let response = Response::try_from_http_response(http_response).unwrap();
assert_eq!(response.next_batch, "aaa");
let joined_room = response.rooms.join.get(joined_room_id).unwrap();
assert!(joined_room.timeline.is_empty());
assert_matches!(&joined_room.state, State::After(state));
assert_eq!(state.events.len(), 0);
let left_room = response.rooms.leave.get(left_room_id).unwrap();
assert!(left_room.timeline.is_empty());
assert_matches!(&left_room.state, State::After(state));
assert_eq!(state.events.len(), 0);
}
#[test]
fn deserialize_response_non_empty_state_after() {
let joined_room_id = room_id!("!joined:localhost");
let left_room_id = room_id!("!left:localhost");
let event = sync_state_event();
let body = json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state_after": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"state_after": {
"events": [
event,
],
},
},
},
},
});
let http_response = http::Response::new(to_json_vec(&body).unwrap());
let response = Response::try_from_http_response(http_response).unwrap();
assert_eq!(response.next_batch, "aaa");
let joined_room = response.rooms.join.get(joined_room_id).unwrap();
assert!(joined_room.timeline.is_empty());
assert_matches!(&joined_room.state, State::After(state));
assert_eq!(state.events.len(), 1);
let left_room = response.rooms.leave.get(left_room_id).unwrap();
assert!(left_room.timeline.is_empty());
assert_matches!(&left_room.state, State::After(state));
assert_eq!(state.events.len(), 1);
}
}
#[cfg(all(test, feature = "server"))]
mod server_tests {
use std::time::Duration;
use assert_matches2::assert_matches;
use ruma_common::{
api::{IncomingRequest as _, OutgoingResponse as _},
owned_room_id,
presence::PresenceState,
serde::Raw,
};
use ruma_events::AnySyncStateEvent;
use serde_json::{Value as JsonValue, from_slice as from_json_slice, json};
use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
fn sync_state_event() -> Raw<AnySyncStateEvent> {
Raw::new(&json!({
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 1_432_735_824,
"sender": "@alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234,
"membership": "join",
},
}))
.unwrap()
.cast_unchecked()
}
#[test]
fn deserialize_request_all_query_params() {
let uri = http::Uri::builder()
.scheme("https")
.authority("matrix.org")
.path_and_query(
"/_matrix/client/r0/sync\
?filter=myfilter\
&since=myts\
&full_state=false\
&set_presence=offline\
&timeout=5000",
)
.build()
.unwrap();
let req = Request::try_from_http_request(
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
&[] as &[String],
)
.unwrap();
assert_matches!(req.filter, Some(Filter::FilterId(id)));
assert_eq!(id, "myfilter");
assert_eq!(req.since.as_deref(), Some("myts"));
assert!(!req.full_state);
assert_eq!(req.set_presence, PresenceState::Offline);
assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
}
#[test]
fn deserialize_request_no_query_params() {
let uri = http::Uri::builder()
.scheme("https")
.authority("matrix.org")
.path_and_query("/_matrix/client/r0/sync")
.build()
.unwrap();
let req = Request::try_from_http_request(
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
&[] as &[String],
)
.unwrap();
assert_matches!(req.filter, None);
assert_eq!(req.since, None);
assert!(!req.full_state);
assert_eq!(req.set_presence, PresenceState::Online);
assert_eq!(req.timeout, None);
}
#[test]
fn deserialize_request_some_query_params() {
let uri = http::Uri::builder()
.scheme("https")
.authority("matrix.org")
.path_and_query(
"/_matrix/client/r0/sync\
?filter=EOKFFmdZYF\
&timeout=0",
)
.build()
.unwrap();
let req = Request::try_from_http_request(
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
&[] as &[String],
)
.unwrap();
assert_matches!(req.filter, Some(Filter::FilterId(id)));
assert_eq!(id, "EOKFFmdZYF");
assert_eq!(req.since, None);
assert!(!req.full_state);
assert_eq!(req.set_presence, PresenceState::Online);
assert_eq!(req.timeout, Some(Duration::from_millis(0)));
}
#[test]
fn serialize_response_no_state() {
let joined_room_id = owned_room_id!("!joined:localhost");
let left_room_id = owned_room_id!("!left:localhost");
let event = sync_state_event();
let mut response = Response::new("aaa".to_owned());
let mut joined_room = JoinedRoom::new();
joined_room.timeline.events.push(event.clone().cast());
response.rooms.join.insert(joined_room_id.clone(), joined_room);
let mut left_room = LeftRoom::new();
left_room.timeline.events.push(event.clone().cast());
response.rooms.leave.insert(left_room_id.clone(), left_room);
let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(
from_json_slice::<JsonValue>(http_response.body()).unwrap(),
json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"timeline": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"timeline": {
"events": [
event,
],
},
},
},
},
})
);
}
#[test]
fn serialize_response_state_before() {
let joined_room_id = owned_room_id!("!joined:localhost");
let left_room_id = owned_room_id!("!left:localhost");
let event = sync_state_event();
let mut response = Response::new("aaa".to_owned());
let mut joined_room = JoinedRoom::new();
joined_room.state = State::Before(vec![event.clone()].into());
response.rooms.join.insert(joined_room_id.clone(), joined_room);
let mut left_room = LeftRoom::new();
left_room.state = State::Before(vec![event.clone()].into());
response.rooms.leave.insert(left_room_id.clone(), left_room);
let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(
from_json_slice::<JsonValue>(http_response.body()).unwrap(),
json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"state": {
"events": [
event,
],
},
},
},
},
})
);
}
#[test]
fn serialize_response_empty_state_after() {
let joined_room_id = owned_room_id!("!joined:localhost");
let left_room_id = owned_room_id!("!left:localhost");
let mut response = Response::new("aaa".to_owned());
let mut joined_room = JoinedRoom::new();
joined_room.state = State::After(Default::default());
response.rooms.join.insert(joined_room_id.clone(), joined_room);
let mut left_room = LeftRoom::new();
left_room.state = State::After(Default::default());
response.rooms.leave.insert(left_room_id.clone(), left_room);
let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(
from_json_slice::<JsonValue>(http_response.body()).unwrap(),
json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state_after": {},
},
},
"leave": {
left_room_id: {
"state_after": {},
},
},
},
})
);
}
#[test]
fn serialize_response_non_empty_state_after() {
let joined_room_id = owned_room_id!("!joined:localhost");
let left_room_id = owned_room_id!("!left:localhost");
let event = sync_state_event();
let mut response = Response::new("aaa".to_owned());
let mut joined_room = JoinedRoom::new();
joined_room.state = State::After(vec![event.clone()].into());
response.rooms.join.insert(joined_room_id.clone(), joined_room);
let mut left_room = LeftRoom::new();
left_room.state = State::After(vec![event.clone()].into());
response.rooms.leave.insert(left_room_id.clone(), left_room);
let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(
from_json_slice::<JsonValue>(http_response.body()).unwrap(),
json!({
"next_batch": "aaa",
"rooms": {
"join": {
joined_room_id: {
"state_after": {
"events": [
event,
],
},
},
},
"leave": {
left_room_id: {
"state_after": {
"events": [
event,
],
},
},
},
},
})
);
}
}