client-api: Add sliding-sync endpoint
Co-authored-by: Matthew Hodgson <matthew@matrix.org>
This commit is contained in:
parent
ccb6babe9e
commit
23a608788b
7 changed files with 1119 additions and 750 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
.vscode
|
||||
target
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
Breaking changes:
|
||||
|
||||
* `UnreadNotificationsCount` has moved from `sync::sync_events::v3` to `sync::sync_events`
|
||||
* Remove `PartialEq` implementations for a number of types
|
||||
* If the lack of such an `impl` causes problems, please open a GitHub issue
|
||||
* Split `uiaa::UserIdentifier::ThirdParty` into two separate variants
|
||||
|
|
|
@ -29,6 +29,7 @@ unstable-msc2965 = []
|
|||
unstable-msc2967 = []
|
||||
unstable-msc3440 = []
|
||||
unstable-msc3488 = []
|
||||
unstable-msc3575 = []
|
||||
client = []
|
||||
server = []
|
||||
|
||||
|
|
|
@ -1,761 +1,34 @@
|
|||
//! `GET /_matrix/client/*/sync`
|
||||
|
||||
pub mod v3 {
|
||||
//! `/v3/` ([spec])
|
||||
//!
|
||||
//! [spec]: https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3sync
|
||||
use js_int::UInt;
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
pub mod v3;
|
||||
|
||||
use js_int::UInt;
|
||||
use ruma_common::{
|
||||
api::ruma_api,
|
||||
events::{
|
||||
presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||
AnyToDeviceEvent,
|
||||
},
|
||||
presence::PresenceState,
|
||||
serde::{Incoming, Raw},
|
||||
DeviceKeyAlgorithm, OwnedRoomId, OwnedUserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "unstable-msc3575")]
|
||||
pub mod v4;
|
||||
|
||||
use crate::filter::{FilterDefinition, IncomingFilterDefinition};
|
||||
/// Unread notifications count.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct UnreadNotificationsCount {
|
||||
/// The number of unread notifications for this room with the highlight flag set.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub highlight_count: Option<UInt>,
|
||||
|
||||
ruma_api! {
|
||||
metadata: {
|
||||
description: "Get all new events from all rooms since the last sync or a given point of time.",
|
||||
method: GET,
|
||||
name: "sync",
|
||||
r0_path: "/_matrix/client/r0/sync",
|
||||
stable_path: "/_matrix/client/v3/sync",
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
added: 1.0,
|
||||
}
|
||||
/// The total number of unread notifications for this room.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub notification_count: Option<UInt>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
request: {
|
||||
/// A filter represented either as its full JSON definition or the ID of a saved filter.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ruma_api(query)]
|
||||
pub filter: Option<&'a Filter<'a>>,
|
||||
|
||||
/// A point in time to continue a sync from.
|
||||
///
|
||||
/// Should be a token from the `next_batch` field of a previous `/sync`
|
||||
/// request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ruma_api(query)]
|
||||
pub since: Option<&'a str>,
|
||||
|
||||
/// Controls whether to include the full state for all rooms the user is a member of.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
#[ruma_api(query)]
|
||||
pub full_state: bool,
|
||||
|
||||
/// Controls whether the client is automatically marked as online by polling this API.
|
||||
///
|
||||
/// Defaults to `PresenceState::Online`.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
#[ruma_api(query)]
|
||||
pub set_presence: &'a PresenceState,
|
||||
|
||||
/// The maximum time to poll in milliseconds before returning this request.
|
||||
#[serde(
|
||||
with = "ruma_common::serde::duration::opt_ms",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
)]
|
||||
#[ruma_api(query)]
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
response: {
|
||||
/// The batch token to supply in the `since` param of the next `/sync` request.
|
||||
pub next_batch: String,
|
||||
|
||||
/// Updates to rooms.
|
||||
#[serde(default, skip_serializing_if = "Rooms::is_empty")]
|
||||
pub rooms: Rooms,
|
||||
|
||||
/// Updates to the presence status of other users.
|
||||
#[serde(default, skip_serializing_if = "Presence::is_empty")]
|
||||
pub presence: Presence,
|
||||
|
||||
/// The global private data created by this user.
|
||||
#[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
|
||||
pub account_data: GlobalAccountData,
|
||||
|
||||
/// Messages sent directly between devices.
|
||||
#[serde(default, skip_serializing_if = "ToDevice::is_empty")]
|
||||
pub to_device: ToDevice,
|
||||
|
||||
/// Information on E2E device updates.
|
||||
///
|
||||
/// Only present on an incremental sync.
|
||||
#[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
|
||||
pub device_lists: DeviceLists,
|
||||
|
||||
/// For each key algorithm, the number of unclaimed one-time keys
|
||||
/// currently held on the server for a device.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub device_one_time_keys_count: BTreeMap<DeviceKeyAlgorithm, UInt>,
|
||||
|
||||
/// For each key algorithm, the number of unclaimed one-time keys
|
||||
/// currently held on the server for a device.
|
||||
///
|
||||
/// The presence of this field indicates that the server supports
|
||||
/// fallback keys.
|
||||
pub device_unused_fallback_key_types: Option<Vec<DeviceKeyAlgorithm>>,
|
||||
}
|
||||
|
||||
error: crate::Error
|
||||
impl UnreadNotificationsCount {
|
||||
/// Creates an empty `UnreadNotificationsCount`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
impl Request<'_> {
|
||||
/// Creates an empty `Request`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given batch token.
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter represented either as its full JSON definition or the ID of a saved filter.
|
||||
#[derive(Clone, Debug, Incoming, Serialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[serde(untagged)]
|
||||
pub enum Filter<'a> {
|
||||
// The filter definition needs to be (de)serialized twice because it is a URL-encoded JSON
|
||||
// string. Since #[ruma_api(query)] only does the latter and this is a very uncommon
|
||||
// setup, we implement it through custom serde logic for this specific enum variant rather
|
||||
// than adding another ruma_api attribute.
|
||||
//
|
||||
// On the deserialization side, because this is an enum with #[serde(untagged)], serde
|
||||
// will try the variants in order (https://serde.rs/enum-representations.html). That means because
|
||||
// FilterDefinition is the first variant, JSON decoding is attempted first which is almost
|
||||
// functionally equivalent to looking at whether the first symbol is a '{' as the spec
|
||||
// says. (there are probably some corner cases like leading whitespace)
|
||||
/// A complete filter definition serialized to JSON.
|
||||
#[serde(with = "ruma_common::serde::json_string")]
|
||||
FilterDefinition(FilterDefinition<'a>),
|
||||
|
||||
/// The ID of a filter saved on the server.
|
||||
FilterId(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> From<FilterDefinition<'a>> for Filter<'a> {
|
||||
fn from(def: FilterDefinition<'a>) -> Self {
|
||||
Self::FilterDefinition(def)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Filter<'a> {
|
||||
fn from(id: &'a str) -> Self {
|
||||
Self::FilterId(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Rooms {
|
||||
/// The rooms that the user has left or been banned from.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
|
||||
|
||||
/// The rooms that the user has joined.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
|
||||
|
||||
/// The rooms that the user has been invited to.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
|
||||
|
||||
/// The rooms that the user has knocked on.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
/// Creates an empty `Rooms`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there is no update in any room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Historical updates to left rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct LeftRoom {
|
||||
/// The timeline of messages and state changes in the room up to the point when the user
|
||||
/// left.
|
||||
#[serde(default, skip_serializing_if = "Timeline::is_empty")]
|
||||
pub timeline: Timeline,
|
||||
|
||||
/// The state updates for the room up to the start of the timeline.
|
||||
#[serde(default, skip_serializing_if = "State::is_empty")]
|
||||
pub state: State,
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
|
||||
pub account_data: RoomAccountData,
|
||||
}
|
||||
|
||||
impl LeftRoom {
|
||||
/// Creates an empty `LeftRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are updates in the room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to joined rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct JoinedRoom {
|
||||
/// Information about the room which clients may need to correctly render it
|
||||
/// to users.
|
||||
#[serde(default, skip_serializing_if = "RoomSummary::is_empty")]
|
||||
pub summary: RoomSummary,
|
||||
|
||||
/// Counts of unread notifications for this room.
|
||||
#[serde(default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
|
||||
pub unread_notifications: UnreadNotificationsCount,
|
||||
|
||||
/// The timeline of messages and state changes in the room.
|
||||
#[serde(default, skip_serializing_if = "Timeline::is_empty")]
|
||||
pub timeline: Timeline,
|
||||
|
||||
/// Updates to the state, between the time indicated by the `since` parameter, and the
|
||||
/// start of the `timeline` (or all state up to the start of the `timeline`, if
|
||||
/// `since` is not given, or `full_state` is true).
|
||||
#[serde(default, skip_serializing_if = "State::is_empty")]
|
||||
pub state: State,
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
|
||||
pub account_data: RoomAccountData,
|
||||
|
||||
/// The ephemeral events in the room that aren't recorded in the timeline or state of the
|
||||
/// room.
|
||||
#[serde(default, skip_serializing_if = "Ephemeral::is_empty")]
|
||||
pub ephemeral: Ephemeral,
|
||||
|
||||
/// The number of unread events since the latest read receipt.
|
||||
///
|
||||
/// This uses the unstable prefix in [MSC2654].
|
||||
///
|
||||
/// [MSC2654]: https://github.com/matrix-org/matrix-spec-proposals/pull/2654
|
||||
#[cfg(feature = "unstable-msc2654")]
|
||||
#[serde(
|
||||
rename = "org.matrix.msc2654.unread_count",
|
||||
alias = "unread_count",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub unread_count: Option<UInt>,
|
||||
}
|
||||
|
||||
impl JoinedRoom {
|
||||
/// Creates an empty `JoinedRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no updates in the room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let is_empty = self.summary.is_empty()
|
||||
&& self.unread_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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to knocked rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct KnockedRoom {
|
||||
/// The knock state.
|
||||
pub knock_state: KnockState,
|
||||
}
|
||||
|
||||
/// A mapping from a key `events` to a list of `StrippedStateEvent`.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct KnockState {
|
||||
/// The list of events.
|
||||
pub events: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
}
|
||||
|
||||
/// Unread notifications count.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct UnreadNotificationsCount {
|
||||
/// The number of unread notifications for this room with the highlight flag set.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub highlight_count: Option<UInt>,
|
||||
|
||||
/// The total number of unread notifications for this room.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub notification_count: Option<UInt>,
|
||||
}
|
||||
|
||||
impl UnreadNotificationsCount {
|
||||
/// Creates an empty `UnreadNotificationsCount`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no notification count updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.highlight_count.is_none() && self.notification_count.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Events in the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Timeline {
|
||||
/// True if the number of events returned was limited by the `limit` on the filter.
|
||||
///
|
||||
/// Default to `false`.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
pub limited: bool,
|
||||
|
||||
/// A token that can be supplied to to the `from` parameter of the
|
||||
/// `/rooms/{roomId}/messages` endpoint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prev_batch: Option<String>,
|
||||
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncRoomEvent>>,
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
/// Creates an empty `Timeline`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no timeline updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// State events in the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct State {
|
||||
/// A list of state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncStateEvent>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates an empty `State`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no state updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// The global private data created by this user.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct GlobalAccountData {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
|
||||
}
|
||||
|
||||
impl GlobalAccountData {
|
||||
/// Creates an empty `GlobalAccountData`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no global account data updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct RoomAccountData {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
|
||||
}
|
||||
|
||||
impl RoomAccountData {
|
||||
/// Creates an empty `RoomAccountData`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no room account data updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ephemeral events not recorded in the timeline or state of the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Ephemeral {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
}
|
||||
|
||||
impl Ephemeral {
|
||||
/// Creates an empty `Ephemeral`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no ephemeral event updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about room for rendering to clients.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct RoomSummary {
|
||||
/// Users which can be used to generate a room name if the room does not have one.
|
||||
///
|
||||
/// Required if room name or canonical aliases are not set or empty.
|
||||
#[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub heroes: Vec<String>,
|
||||
|
||||
/// Number of users whose membership status is `join`.
|
||||
/// Required if field has changed since last sync; otherwise, it may be
|
||||
/// omitted.
|
||||
#[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
|
||||
pub joined_member_count: Option<UInt>,
|
||||
|
||||
/// Number of users whose membership status is `invite`.
|
||||
/// Required if field has changed since last sync; otherwise, it may be
|
||||
/// omitted.
|
||||
#[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
|
||||
pub invited_member_count: Option<UInt>,
|
||||
}
|
||||
|
||||
impl RoomSummary {
|
||||
/// Creates an empty `RoomSummary`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no room summary updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.heroes.is_empty()
|
||||
&& self.joined_member_count.is_none()
|
||||
&& self.invited_member_count.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to the rooms that the user has been invited to.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct InvitedRoom {
|
||||
/// The state of a room that the user has been invited to.
|
||||
#[serde(default, skip_serializing_if = "InviteState::is_empty")]
|
||||
pub invite_state: InviteState,
|
||||
}
|
||||
|
||||
impl InvitedRoom {
|
||||
/// Creates an empty `InvitedRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no updates to this room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.invite_state.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a room that the user has been invited to.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct InviteState {
|
||||
/// A list of state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
}
|
||||
|
||||
impl InviteState {
|
||||
/// Creates an empty `InviteState`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no state updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to the presence status of other users.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Presence {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<PresenceEvent>>,
|
||||
}
|
||||
|
||||
impl Presence {
|
||||
/// Creates an empty `Presence`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no presence updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages sent directly between devices.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct ToDevice {
|
||||
/// A list of to-device events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyToDeviceEvent>>,
|
||||
}
|
||||
|
||||
impl ToDevice {
|
||||
/// Creates an empty `ToDevice`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no to-device events.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct DeviceLists {
|
||||
/// List of users who have updated their device identity keys or who now
|
||||
/// share an encrypted room with the client since the previous sync
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub changed: Vec<OwnedUserId>,
|
||||
|
||||
/// List of users who no longer share encrypted rooms since the previous sync
|
||||
/// response.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub left: Vec<OwnedUserId>,
|
||||
}
|
||||
|
||||
impl DeviceLists {
|
||||
/// Creates an empty `DeviceLists`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no device list updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.changed.is_empty() && self.left.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!({ "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!({}));
|
||||
|
||||
let timeline_default_deserialized = from_json_value::<Timeline>(json!({})).unwrap();
|
||||
assert!(!timeline_default_deserialized.limited);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "client"))]
|
||||
mod client_tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use ruma_common::api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
|
||||
|
||||
use super::{Filter, PresenceState, Request};
|
||||
|
||||
#[test]
|
||||
fn serialize_all_params() {
|
||||
let req: http::Request<Vec<u8>> = Request {
|
||||
filter: Some(&Filter::FilterId("66696p746572")),
|
||||
since: Some("s72594_4483_1934"),
|
||||
full_state: true,
|
||||
set_presence: &PresenceState::Offline,
|
||||
timeout: Some(Duration::from_millis(30000)),
|
||||
}
|
||||
.try_into_http_request(
|
||||
"https://homeserver.tld",
|
||||
SendAccessToken::IfRequired("auth_tok"),
|
||||
&[MatrixVersion::V1_1],
|
||||
)
|
||||
.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"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "server"))]
|
||||
mod server_tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use ruma_common::{api::IncomingRequest as _, presence::PresenceState};
|
||||
|
||||
use super::{IncomingFilter, IncomingRequest};
|
||||
|
||||
#[test]
|
||||
fn deserialize_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 = IncomingRequest::try_from_http_request(
|
||||
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
|
||||
&[] as &[String],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let id = assert_matches!(req.filter, Some(IncomingFilter::FilterId(id)) => 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_no_query_params() {
|
||||
let uri = http::Uri::builder()
|
||||
.scheme("https")
|
||||
.authority("matrix.org")
|
||||
.path_and_query("/_matrix/client/r0/sync")
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let req = IncomingRequest::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_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 = IncomingRequest::try_from_http_request(
|
||||
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
|
||||
&[] as &[String],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let id = assert_matches!(req.filter, Some(IncomingFilter::FilterId(id)) => 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)));
|
||||
}
|
||||
/// Returns true if there are no notification count updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.highlight_count.is_none() && self.notification_count.is_none()
|
||||
}
|
||||
}
|
||||
|
|
756
crates/ruma-client-api/src/sync/sync_events/v3.rs
Normal file
756
crates/ruma-client-api/src/sync/sync_events/v3.rs
Normal file
|
@ -0,0 +1,756 @@
|
|||
//! `/v3/` ([spec])
|
||||
//!
|
||||
//! [spec]: https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3sync
|
||||
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
|
||||
use super::UnreadNotificationsCount;
|
||||
use js_int::UInt;
|
||||
use ruma_common::{
|
||||
api::ruma_api,
|
||||
events::{
|
||||
presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
|
||||
AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncRoomEvent, AnySyncStateEvent,
|
||||
AnyToDeviceEvent,
|
||||
},
|
||||
presence::PresenceState,
|
||||
serde::{Incoming, Raw},
|
||||
DeviceKeyAlgorithm, OwnedRoomId, OwnedUserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::filter::{FilterDefinition, IncomingFilterDefinition};
|
||||
|
||||
ruma_api! {
|
||||
metadata: {
|
||||
description: "Get all new events from all rooms since the last sync or a given point of time.",
|
||||
method: GET,
|
||||
name: "sync",
|
||||
r0_path: "/_matrix/client/r0/sync",
|
||||
stable_path: "/_matrix/client/v3/sync",
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
added: 1.0,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
request: {
|
||||
/// A filter represented either as its full JSON definition or the ID of a saved filter.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ruma_api(query)]
|
||||
pub filter: Option<&'a Filter<'a>>,
|
||||
|
||||
/// A point in time to continue a sync from.
|
||||
///
|
||||
/// Should be a token from the `next_batch` field of a previous `/sync`
|
||||
/// request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ruma_api(query)]
|
||||
pub since: Option<&'a str>,
|
||||
|
||||
/// Controls whether to include the full state for all rooms the user is a member of.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
#[ruma_api(query)]
|
||||
pub full_state: bool,
|
||||
|
||||
/// Controls whether the client is automatically marked as online by polling this API.
|
||||
///
|
||||
/// Defaults to `PresenceState::Online`.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
#[ruma_api(query)]
|
||||
pub set_presence: &'a PresenceState,
|
||||
|
||||
/// The maximum time to poll in milliseconds before returning this request.
|
||||
#[serde(
|
||||
with = "ruma_common::serde::duration::opt_ms",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
)]
|
||||
#[ruma_api(query)]
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
response: {
|
||||
/// The batch token to supply in the `since` param of the next `/sync` request.
|
||||
pub next_batch: String,
|
||||
|
||||
/// Updates to rooms.
|
||||
#[serde(default, skip_serializing_if = "Rooms::is_empty")]
|
||||
pub rooms: Rooms,
|
||||
|
||||
/// Updates to the presence status of other users.
|
||||
#[serde(default, skip_serializing_if = "Presence::is_empty")]
|
||||
pub presence: Presence,
|
||||
|
||||
/// The global private data created by this user.
|
||||
#[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
|
||||
pub account_data: GlobalAccountData,
|
||||
|
||||
/// Messages sent directly between devices.
|
||||
#[serde(default, skip_serializing_if = "ToDevice::is_empty")]
|
||||
pub to_device: ToDevice,
|
||||
|
||||
/// Information on E2E device updates.
|
||||
///
|
||||
/// Only present on an incremental sync.
|
||||
#[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
|
||||
pub device_lists: DeviceLists,
|
||||
|
||||
/// For each key algorithm, the number of unclaimed one-time keys
|
||||
/// currently held on the server for a device.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub device_one_time_keys_count: BTreeMap<DeviceKeyAlgorithm, UInt>,
|
||||
|
||||
/// For each key algorithm, the number of unclaimed one-time keys
|
||||
/// currently held on the server for a device.
|
||||
///
|
||||
/// The presence of this field indicates that the server supports
|
||||
/// fallback keys.
|
||||
pub device_unused_fallback_key_types: Option<Vec<DeviceKeyAlgorithm>>,
|
||||
}
|
||||
|
||||
error: crate::Error
|
||||
}
|
||||
|
||||
impl Request<'_> {
|
||||
/// Creates an empty `Request`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given batch token.
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter represented either as its full JSON definition or the ID of a saved filter.
|
||||
#[derive(Clone, Debug, Incoming, Serialize)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[serde(untagged)]
|
||||
pub enum Filter<'a> {
|
||||
// The filter definition needs to be (de)serialized twice because it is a URL-encoded JSON
|
||||
// string. Since #[ruma_api(query)] only does the latter and this is a very uncommon
|
||||
// setup, we implement it through custom serde logic for this specific enum variant rather
|
||||
// than adding another ruma_api attribute.
|
||||
//
|
||||
// On the deserialization side, because this is an enum with #[serde(untagged)], serde
|
||||
// will try the variants in order (https://serde.rs/enum-representations.html). That means because
|
||||
// FilterDefinition is the first variant, JSON decoding is attempted first which is almost
|
||||
// functionally equivalent to looking at whether the first symbol is a '{' as the spec
|
||||
// says. (there are probably some corner cases like leading whitespace)
|
||||
/// A complete filter definition serialized to JSON.
|
||||
#[serde(with = "ruma_common::serde::json_string")]
|
||||
FilterDefinition(FilterDefinition<'a>),
|
||||
|
||||
/// The ID of a filter saved on the server.
|
||||
FilterId(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> From<FilterDefinition<'a>> for Filter<'a> {
|
||||
fn from(def: FilterDefinition<'a>) -> Self {
|
||||
Self::FilterDefinition(def)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Filter<'a> {
|
||||
fn from(id: &'a str) -> Self {
|
||||
Self::FilterId(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Rooms {
|
||||
/// The rooms that the user has left or been banned from.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
|
||||
|
||||
/// The rooms that the user has joined.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
|
||||
|
||||
/// The rooms that the user has been invited to.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
|
||||
|
||||
/// The rooms that the user has knocked on.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
/// Creates an empty `Rooms`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there is no update in any room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Historical updates to left rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct LeftRoom {
|
||||
/// The timeline of messages and state changes in the room up to the point when the user
|
||||
/// left.
|
||||
#[serde(default, skip_serializing_if = "Timeline::is_empty")]
|
||||
pub timeline: Timeline,
|
||||
|
||||
/// The state updates for the room up to the start of the timeline.
|
||||
#[serde(default, skip_serializing_if = "State::is_empty")]
|
||||
pub state: State,
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
|
||||
pub account_data: RoomAccountData,
|
||||
}
|
||||
|
||||
impl LeftRoom {
|
||||
/// Creates an empty `LeftRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are updates in the room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to joined rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct JoinedRoom {
|
||||
/// Information about the room which clients may need to correctly render it
|
||||
/// to users.
|
||||
#[serde(default, skip_serializing_if = "RoomSummary::is_empty")]
|
||||
pub summary: RoomSummary,
|
||||
|
||||
/// Counts of unread notifications for this room.
|
||||
#[serde(default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
|
||||
pub unread_notifications: UnreadNotificationsCount,
|
||||
|
||||
/// The timeline of messages and state changes in the room.
|
||||
#[serde(default, skip_serializing_if = "Timeline::is_empty")]
|
||||
pub timeline: Timeline,
|
||||
|
||||
/// Updates to the state, between the time indicated by the `since` parameter, and the
|
||||
/// start of the `timeline` (or all state up to the start of the `timeline`, if
|
||||
/// `since` is not given, or `full_state` is true).
|
||||
#[serde(default, skip_serializing_if = "State::is_empty")]
|
||||
pub state: State,
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
|
||||
pub account_data: RoomAccountData,
|
||||
|
||||
/// The ephemeral events in the room that aren't recorded in the timeline or state of the
|
||||
/// room.
|
||||
#[serde(default, skip_serializing_if = "Ephemeral::is_empty")]
|
||||
pub ephemeral: Ephemeral,
|
||||
|
||||
/// The number of unread events since the latest read receipt.
|
||||
///
|
||||
/// This uses the unstable prefix in [MSC2654].
|
||||
///
|
||||
/// [MSC2654]: https://github.com/matrix-org/matrix-spec-proposals/pull/2654
|
||||
#[cfg(feature = "unstable-msc2654")]
|
||||
#[serde(
|
||||
rename = "org.matrix.msc2654.unread_count",
|
||||
alias = "unread_count",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub unread_count: Option<UInt>,
|
||||
}
|
||||
|
||||
impl JoinedRoom {
|
||||
/// Creates an empty `JoinedRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no updates in the room.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let is_empty = self.summary.is_empty()
|
||||
&& self.unread_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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to knocked rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct KnockedRoom {
|
||||
/// The knock state.
|
||||
pub knock_state: KnockState,
|
||||
}
|
||||
|
||||
/// A mapping from a key `events` to a list of `StrippedStateEvent`.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct KnockState {
|
||||
/// The list of events.
|
||||
pub events: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
}
|
||||
|
||||
/// Events in the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Timeline {
|
||||
/// True if the number of events returned was limited by the `limit` on the filter.
|
||||
///
|
||||
/// Default to `false`.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
pub limited: bool,
|
||||
|
||||
/// A token that can be supplied to to the `from` parameter of the
|
||||
/// `/rooms/{roomId}/messages` endpoint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prev_batch: Option<String>,
|
||||
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncRoomEvent>>,
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
/// Creates an empty `Timeline`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no timeline updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// State events in the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct State {
|
||||
/// A list of state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncStateEvent>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates an empty `State`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no state updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
|
||||
/// Creates a `State` with events
|
||||
pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
|
||||
State { events, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Raw<AnySyncStateEvent>>> for State {
|
||||
fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
|
||||
State::with_events(events)
|
||||
}
|
||||
}
|
||||
|
||||
/// The global private data created by this user.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct GlobalAccountData {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
|
||||
}
|
||||
|
||||
impl GlobalAccountData {
|
||||
/// Creates an empty `GlobalAccountData`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no global account data updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// The private data that this user has attached to this room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct RoomAccountData {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
|
||||
}
|
||||
|
||||
impl RoomAccountData {
|
||||
/// Creates an empty `RoomAccountData`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no room account data updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ephemeral events not recorded in the timeline or state of the room.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Ephemeral {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
}
|
||||
|
||||
impl Ephemeral {
|
||||
/// Creates an empty `Ephemeral`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no ephemeral event updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about room for rendering to clients.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct RoomSummary {
|
||||
/// Users which can be used to generate a room name if the room does not have one.
|
||||
///
|
||||
/// Required if room name or canonical aliases are not set or empty.
|
||||
#[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub heroes: Vec<String>,
|
||||
|
||||
/// Number of users whose membership status is `join`.
|
||||
/// Required if field has changed since last sync; otherwise, it may be
|
||||
/// omitted.
|
||||
#[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
|
||||
pub joined_member_count: Option<UInt>,
|
||||
|
||||
/// Number of users whose membership status is `invite`.
|
||||
/// Required if field has changed since last sync; otherwise, it may be
|
||||
/// omitted.
|
||||
#[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
|
||||
pub invited_member_count: Option<UInt>,
|
||||
}
|
||||
|
||||
impl RoomSummary {
|
||||
/// Creates an empty `RoomSummary`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no room summary updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.heroes.is_empty()
|
||||
&& self.joined_member_count.is_none()
|
||||
&& self.invited_member_count.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to the rooms that the user has been invited to.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct InvitedRoom {
|
||||
/// The state of a room that the user has been invited to.
|
||||
#[serde(default, skip_serializing_if = "InviteState::is_empty")]
|
||||
pub invite_state: InviteState,
|
||||
}
|
||||
|
||||
impl InvitedRoom {
|
||||
/// Creates an empty `InvitedRoom`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no updates to this room.
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a room that the user has been invited to.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct InviteState {
|
||||
/// A list of state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
}
|
||||
|
||||
impl InviteState {
|
||||
/// Creates an empty `InviteState`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no state updates.
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates to the presence status of other users.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct Presence {
|
||||
/// A list of events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<PresenceEvent>>,
|
||||
}
|
||||
|
||||
impl Presence {
|
||||
/// Creates an empty `Presence`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no presence updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages sent directly between devices.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct ToDevice {
|
||||
/// A list of to-device events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub events: Vec<Raw<AnyToDeviceEvent>>,
|
||||
}
|
||||
|
||||
impl ToDevice {
|
||||
/// Creates an empty `ToDevice`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no to-device events.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Information on E2E device updates.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct DeviceLists {
|
||||
/// List of users who have updated their device identity keys or who now
|
||||
/// share an encrypted room with the client since the previous sync
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub changed: Vec<OwnedUserId>,
|
||||
|
||||
/// List of users who no longer share encrypted rooms since the previous sync
|
||||
/// response.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub left: Vec<OwnedUserId>,
|
||||
}
|
||||
|
||||
impl DeviceLists {
|
||||
/// Creates an empty `DeviceLists`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns true if there are no device list updates.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.changed.is_empty() && self.left.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!({ "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!({}));
|
||||
|
||||
let timeline_default_deserialized = from_json_value::<Timeline>(json!({})).unwrap();
|
||||
assert!(!timeline_default_deserialized.limited);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "client"))]
|
||||
mod client_tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use ruma_common::api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
|
||||
|
||||
use super::{Filter, PresenceState, Request};
|
||||
|
||||
#[test]
|
||||
fn serialize_all_params() {
|
||||
let req: http::Request<Vec<u8>> = Request {
|
||||
filter: Some(&Filter::FilterId("66696p746572")),
|
||||
since: Some("s72594_4483_1934"),
|
||||
full_state: true,
|
||||
set_presence: &PresenceState::Offline,
|
||||
timeout: Some(Duration::from_millis(30000)),
|
||||
}
|
||||
.try_into_http_request(
|
||||
"https://homeserver.tld",
|
||||
SendAccessToken::IfRequired("auth_tok"),
|
||||
&[MatrixVersion::V1_1],
|
||||
)
|
||||
.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"));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "server"))]
|
||||
mod server_tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use ruma_common::{api::IncomingRequest as _, presence::PresenceState};
|
||||
|
||||
use super::{IncomingFilter, IncomingRequest};
|
||||
|
||||
#[test]
|
||||
fn deserialize_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 = IncomingRequest::try_from_http_request(
|
||||
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
|
||||
&[] as &[String],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let id = assert_matches!(req.filter, Some(IncomingFilter::FilterId(id)) => 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_no_query_params() {
|
||||
let uri = http::Uri::builder()
|
||||
.scheme("https")
|
||||
.authority("matrix.org")
|
||||
.path_and_query("/_matrix/client/r0/sync")
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let req = IncomingRequest::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_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 = IncomingRequest::try_from_http_request(
|
||||
http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
|
||||
&[] as &[String],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let id = assert_matches!(req.filter, Some(IncomingFilter::FilterId(id)) => 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)));
|
||||
}
|
||||
}
|
335
crates/ruma-client-api/src/sync/sync_events/v4.rs
Normal file
335
crates/ruma-client-api/src/sync/sync_events/v4.rs
Normal file
|
@ -0,0 +1,335 @@
|
|||
//! [POST /_matrix/client/unstable/org.matrix.msc3575/sync](https://github.com/matrix-org/matrix-doc/blob/kegan/sync-v3/proposals/3575-sync.md)
|
||||
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
|
||||
use super::UnreadNotificationsCount;
|
||||
use js_int::UInt;
|
||||
use ruma_common::{
|
||||
api::ruma_api,
|
||||
events::{AnyStrippedStateEvent, AnySyncRoomEvent, AnySyncStateEvent, RoomEventType},
|
||||
serde::{duration::opt_ms, Raw},
|
||||
OwnedRoomId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
ruma_api! {
|
||||
metadata: {
|
||||
description: "Get all new events in a sliding window of rooms since the last sync or a given point of time.",
|
||||
method: POST,
|
||||
name: "sync",
|
||||
// added: 1.4,
|
||||
// stable_path: "/_matrix/client/v4/sync",
|
||||
unstable_path: "/_matrix/client/unstable/org.matrix.msc3575/sync",
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
request: {
|
||||
/// A point in time to continue a sync from.
|
||||
///
|
||||
/// Should be a token from the `pos` field of a previous `/sync`
|
||||
/// response.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ruma_api(query)]
|
||||
pub pos: Option<&'a str>,
|
||||
|
||||
/// Allows clients to know what request params reached the server,
|
||||
/// functionally similar to txn IDs on /send for events.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub txn_id: Option<&'a str>,
|
||||
|
||||
/// The maximum time to poll before responding to this request.
|
||||
#[serde(
|
||||
with = "opt_ms",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
)]
|
||||
#[ruma_api(query)]
|
||||
pub timeout: Option<Duration>,
|
||||
|
||||
/// The lists of rooms we're interested in.
|
||||
pub lists: &'a [SyncRequestList],
|
||||
|
||||
/// Specific rooms and event types that we want to receive events from.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub room_subscriptions: BTreeMap<OwnedRoomId, RoomSubscription>,
|
||||
|
||||
/// Specific rooms we no longer want to receive events from.
|
||||
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
|
||||
pub unsubscribe_rooms: &'a [OwnedRoomId],
|
||||
|
||||
/// Extensions API.
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub extensions: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
response: {
|
||||
/// Whether this response describes an initial sync (i.e. after the `pos` token has been
|
||||
/// discard by the server?).
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
pub initial: bool,
|
||||
|
||||
/// The token to supply in the `pos` param of the next `/sync` request.
|
||||
pub pos: String,
|
||||
|
||||
/// Updates to the sliding room list.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub lists: Vec<SyncList>,
|
||||
|
||||
/// The updates on rooms.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub rooms: BTreeMap<OwnedRoomId, SlidingSyncRoom>,
|
||||
}
|
||||
|
||||
error: crate::Error
|
||||
}
|
||||
|
||||
impl Request<'_> {
|
||||
/// Creates an empty `Request`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Creates a new `Response` with the given pos.
|
||||
pub fn new(pos: String) -> Self {
|
||||
Self {
|
||||
initial: Default::default(),
|
||||
pos,
|
||||
lists: Default::default(),
|
||||
rooms: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Filter for a sliding sync list, set at request.
|
||||
///
|
||||
/// All fields are applied with AND operators, hence if `is_dm` is `true` and `is_encrypted` is
|
||||
/// `true` then only encrypted DM rooms will be returned. The absence of fields implies no filter
|
||||
/// on that criteria: it does NOT imply `false`.
|
||||
///
|
||||
/// Filters are considered _sticky_, meaning that the filter only has to be provided once and their
|
||||
/// parameters 'sticks' for future requests until a new filter overwrites them.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct SyncRequestListFilters {
|
||||
/// Whether to return DMs, non-DM rooms or both.
|
||||
///
|
||||
/// Flag which only returns rooms present (or not) in the DM section of account data.
|
||||
/// If unset, both DM rooms and non-DM rooms are returned. If false, only non-DM rooms
|
||||
/// are returned. If true, only DM rooms are returned.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_dm: Option<bool>,
|
||||
|
||||
/// Only list rooms that are spaces of these or all.
|
||||
///
|
||||
/// A list of spaces which target rooms must be a part of. For every invited/joined
|
||||
/// room for this user, ensure that there is a parent space event which is in this list. If
|
||||
/// unset, all rooms are included. Servers MUST NOT navigate subspaces. It is up to the
|
||||
/// client to give a complete list of spaces to navigate. Only rooms directly in these
|
||||
/// spaces will be returned.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub spaces: Vec<String>,
|
||||
|
||||
/// Whether to return encrypted, non-encrypted rooms or both.
|
||||
///
|
||||
/// Flag which only returns rooms which have an `m.room.encryption` state event. If
|
||||
/// unset, both encrypted and unencrypted rooms are returned. If false, only unencrypted
|
||||
/// rooms are returned. If true, only encrypted rooms are returned.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_encrypted: Option<bool>,
|
||||
|
||||
/// Whether to return invited Rooms, only joined rooms or both.
|
||||
///
|
||||
/// Flag which only returns rooms the user is currently invited to. If unset, both
|
||||
/// invited and joined rooms are returned. If false, no invited rooms are returned. If
|
||||
/// true, only invited rooms are returned.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_invite: Option<bool>,
|
||||
|
||||
/// Whether to return Rooms with tombstones, only rooms without tombstones or both.
|
||||
///
|
||||
/// Flag which only returns rooms which have an `m.room.tombstone` state event. If unset,
|
||||
/// both tombstoned and un-tombstoned rooms are returned. If false, only un-tombstoned rooms
|
||||
/// are returned. If true, only tombstoned rooms are returned.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_tombstoned: Option<bool>,
|
||||
|
||||
/// Only list rooms of given create-types or all.
|
||||
///
|
||||
/// If specified, only rooms where the `m.room.create` event has a `type` matching one
|
||||
/// of the strings in this array will be returned. If this field is unset, all rooms are
|
||||
/// returned regardless of type. This can be used to get the initial set of spaces for an
|
||||
/// account.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub room_types: Vec<String>,
|
||||
|
||||
/// Only list rooms that are not of these create-types, or all.
|
||||
///
|
||||
/// Same as "room_types" but inverted. This can be used to filter out spaces from the room
|
||||
/// list.
|
||||
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
|
||||
pub not_room_types: Vec<String>,
|
||||
|
||||
/// Only list rooms matching the given string, or all.
|
||||
///
|
||||
/// Filter the room name. Case-insensitive partial matching e.g 'foo' matches 'abFooab'.
|
||||
/// The term 'like' is inspired by SQL 'LIKE', and the text here is similar to '%foo%'.
|
||||
pub room_name_like: Option<String>,
|
||||
|
||||
/// Extensions may add further fields to the filters.
|
||||
#[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub extensions: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Sliding Sync Request for each list.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct SyncRequestList {
|
||||
/// Put this list into the all-rooms-mode.
|
||||
///
|
||||
/// Settings this to true will inform the server that, no matter how slow
|
||||
/// that might be, the clients wants all rooms the filters apply to. When operating
|
||||
/// in this mode, `ranges` and `sort` will be ignored there will be no movement operations
|
||||
/// (`DELETE` followed by `INSERT`) as the client has the entire list and can work out whatever
|
||||
/// sort order they wish. There will still be `DELETE` and `INSERT` operations when rooms are
|
||||
/// left or joined respectively. In addition, there will be an initial `SYNC` operation to let
|
||||
/// the client know which rooms in the rooms object were from this list.
|
||||
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
|
||||
pub slow_get_all_rooms: bool,
|
||||
|
||||
/// The ranges of rooms we're interested in.
|
||||
pub ranges: Vec<(UInt, UInt)>,
|
||||
|
||||
/// The sort ordering applied to this list of rooms. Sticky.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub sort: Vec<String>,
|
||||
|
||||
/// Required state for each room returned. An array of event type and state key tuples.
|
||||
/// Note that elements of this array are NOT sticky so they must be specified in full when they
|
||||
/// are changed. Sticky.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub required_state: Vec<(RoomEventType, String)>,
|
||||
|
||||
/// The maximum number of timeline events to return per room. Sticky.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeline_limit: Option<UInt>,
|
||||
|
||||
/// Filters to apply to the list before sorting. Sticky.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filters: Option<SyncRequestListFilters>,
|
||||
}
|
||||
|
||||
/// The RoomSubscriptions of the SlidingSync Request
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct RoomSubscription {
|
||||
/// Required state for each room returned. An array of event type and state key tuples.
|
||||
///
|
||||
/// Note that elements of this array are NOT sticky so they must be specified in full when they
|
||||
/// are changed. Sticky.
|
||||
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub required_state: Vec<(RoomEventType, String)>,
|
||||
|
||||
/// The maximum number of timeline events to return per room. Sticky.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeline_limit: Option<UInt>,
|
||||
}
|
||||
|
||||
/// Operation applied to the specific SlidingSyncList
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub enum SlidingOp {
|
||||
/// Full reset of the given window.
|
||||
Sync,
|
||||
/// Insert an item at the given point, moves all following entry by
|
||||
/// one to the next Empty or Invalid field.
|
||||
Insert,
|
||||
/// Drop this entry, moves all following entry up by one.
|
||||
Delete,
|
||||
/// Mark these as invaldiated.
|
||||
Invalidate,
|
||||
}
|
||||
|
||||
/// Updates to joined rooms.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct SyncList {
|
||||
/// The sync operation to apply, if any.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub ops: Vec<SyncOp>,
|
||||
|
||||
/// The total number of rooms found for this filter.
|
||||
pub count: UInt,
|
||||
}
|
||||
|
||||
/// Updates to joined rooms.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct SyncOp {
|
||||
/// The sync operation to apply.
|
||||
pub op: SlidingOp,
|
||||
|
||||
/// The range this list update applies to.
|
||||
pub range: Option<(UInt, UInt)>,
|
||||
|
||||
/// Or the specific index the update applies to.
|
||||
pub index: Option<UInt>,
|
||||
|
||||
/// The list of room_ids updates to apply.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub room_ids: Vec<OwnedRoomId>,
|
||||
|
||||
/// On insert and delete we are only receiving exactly one room_id.
|
||||
pub room_id: Option<OwnedRoomId>,
|
||||
}
|
||||
|
||||
/// Updates to joined rooms.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
pub struct SlidingSyncRoom {
|
||||
/// The name of the room as calculated by the server.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Was this an initial response.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub initial: Option<bool>,
|
||||
|
||||
/// This is a direct message.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_dm: Option<bool>,
|
||||
|
||||
/// This is not-yet-accepted invite, with the following sync state events
|
||||
/// the room must be considered in invite state as long as the Option is not None
|
||||
/// even if there are no state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub invite_state: Vec<Raw<AnyStrippedStateEvent>>,
|
||||
|
||||
/// Counts of unread notifications for this room.
|
||||
#[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
|
||||
pub unread_notifications: UnreadNotificationsCount,
|
||||
|
||||
/// The timeline of messages and state changes in the room.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub timeline: Vec<Raw<AnySyncRoomEvent>>,
|
||||
|
||||
/// Updates to the state at the beginning of the `timeline`.
|
||||
/// A list of state events.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub required_state: Vec<Raw<AnySyncStateEvent>>,
|
||||
|
||||
/// The prev_batch allowing you to paginate through the messages before the given ones.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prev_batch: Option<String>,
|
||||
}
|
||||
|
||||
impl SlidingSyncRoom {
|
||||
/// Creates an empty `Room`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -164,6 +164,7 @@ unstable-msc3551 = ["ruma-common/unstable-msc3551"]
|
|||
unstable-msc3552 = ["ruma-common/unstable-msc3552"]
|
||||
unstable-msc3553 = ["ruma-common/unstable-msc3553"]
|
||||
unstable-msc3554 = ["ruma-common/unstable-msc3554"]
|
||||
unstable-msc3575 = ["ruma-client-api?/unstable-msc3575"]
|
||||
unstable-msc3618 = ["ruma-federation-api?/unstable-msc3618"]
|
||||
unstable-msc3723 = ["ruma-federation-api?/unstable-msc3723"]
|
||||
unstable-msc3786 = ["ruma-common/unstable-msc3786"]
|
||||
|
@ -194,6 +195,7 @@ __ci = [
|
|||
"unstable-msc3552",
|
||||
"unstable-msc3553",
|
||||
"unstable-msc3554",
|
||||
"unstable-msc3575",
|
||||
"unstable-msc3618",
|
||||
"unstable-msc3723",
|
||||
"unstable-msc3786",
|
||||
|
|
Loading…
Reference in a new issue