pub mod v1 {
#[cfg(any(feature = "unstable-msc2409", feature = "unstable-msc3202"))]
use std::collections::BTreeMap;
#[cfg(feature = "unstable-msc2409")]
use std::{
collections::btree_map,
ops::{Deref, DerefMut},
};
#[cfg(any(feature = "unstable-msc2409", feature = "unstable-msc3202"))]
use js_int::UInt;
#[cfg(any(feature = "unstable-msc2409", feature = "unstable-msc3202"))]
use ruma_common::OwnedUserId;
use ruma_common::{
api::{request, response, Metadata},
metadata,
serde::Raw,
OwnedTransactionId,
};
#[cfg(feature = "unstable-msc2409")]
use ruma_common::{
presence::PresenceState, serde::from_raw_json_value, OwnedEventId, OwnedRoomId,
};
#[cfg(feature = "unstable-msc3202")]
use ruma_common::{OneTimeKeyAlgorithm, OwnedDeviceId};
use ruma_events::AnyTimelineEvent;
#[cfg(feature = "unstable-msc2409")]
use ruma_events::{receipt::Receipt, AnyToDeviceEvent};
#[cfg(feature = "unstable-msc2409")]
use serde::Deserializer;
#[cfg(any(feature = "unstable-msc2409", feature = "unstable-msc3202"))]
use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-msc2409")]
use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};
const METADATA: Metadata = metadata! {
method: PUT,
rate_limited: false,
authentication: AccessToken,
history: {
1.0 => "/_matrix/app/v1/transactions/:txn_id",
}
};
#[request]
pub struct Request {
#[ruma_api(path)]
pub txn_id: OwnedTransactionId,
pub events: Vec<Raw<AnyTimelineEvent>>,
#[cfg(feature = "unstable-msc3202")]
#[serde(
default,
skip_serializing_if = "DeviceLists::is_empty",
rename = "org.matrix.msc3202.device_lists"
)]
pub device_lists: DeviceLists,
#[cfg(feature = "unstable-msc3202")]
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
rename = "org.matrix.msc3202.device_one_time_keys_count"
)]
pub device_one_time_keys_count:
BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<OneTimeKeyAlgorithm, UInt>>>,
#[cfg(feature = "unstable-msc3202")]
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
rename = "org.matrix.msc3202.device_unused_fallback_key_types"
)]
pub device_unused_fallback_key_types:
BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<OneTimeKeyAlgorithm>>>,
#[cfg(feature = "unstable-msc2409")]
#[serde(
default,
skip_serializing_if = "<[_]>::is_empty",
rename = "de.sorunome.msc2409.ephemeral"
)]
pub ephemeral: Vec<Edu>,
#[cfg(feature = "unstable-msc2409")]
#[serde(
default,
skip_serializing_if = "<[_]>::is_empty",
rename = "de.sorunome.msc2409.to_device"
)]
pub to_device: Vec<Raw<AnyToDeviceEvent>>,
}
#[response]
#[derive(Default)]
pub struct Response {}
impl Request {
pub fn new(txn_id: OwnedTransactionId, events: Vec<Raw<AnyTimelineEvent>>) -> Request {
Request {
txn_id,
events,
#[cfg(feature = "unstable-msc3202")]
device_lists: DeviceLists::new(),
#[cfg(feature = "unstable-msc3202")]
device_one_time_keys_count: BTreeMap::new(),
#[cfg(feature = "unstable-msc3202")]
device_unused_fallback_key_types: BTreeMap::new(),
#[cfg(feature = "unstable-msc2409")]
ephemeral: Vec::new(),
#[cfg(feature = "unstable-msc2409")]
to_device: Vec::new(),
}
}
}
impl Response {
pub fn new() -> Self {
Self {}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[cfg(feature = "unstable-msc3202")]
pub struct DeviceLists {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub changed: Vec<OwnedUserId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub left: Vec<OwnedUserId>,
}
#[cfg(feature = "unstable-msc3202")]
impl DeviceLists {
pub fn new() -> Self {
Default::default()
}
pub fn is_empty(&self) -> bool {
self.changed.is_empty() && self.left.is_empty()
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Serialize)]
#[non_exhaustive]
pub enum Edu {
Presence(PresenceContent),
Receipt(ReceiptContent),
Typing(TypingContent),
#[doc(hidden)]
#[serde(skip)]
_Custom(JsonValue),
}
#[derive(Debug, Deserialize)]
#[cfg(feature = "unstable-msc2409")]
struct EduDeHelper {
r#type: String,
content: Box<RawJsonValue>,
}
#[cfg(feature = "unstable-msc2409")]
impl<'de> Deserialize<'de> for Edu {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EduDeHelper { r#type, content } = from_raw_json_value(&json)?;
Ok(match r#type.as_ref() {
"m.presence" => Self::Presence(from_raw_json_value(&content)?),
"m.receipt" => Self::Receipt(from_raw_json_value(&content)?),
"m.typing" => Self::Typing(from_raw_json_value(&content)?),
_ => Self::_Custom(from_raw_json_value(&content)?),
})
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PresenceContent {
pub push: Vec<PresenceUpdate>,
}
#[cfg(feature = "unstable-msc2409")]
impl PresenceContent {
pub fn new(push: Vec<PresenceUpdate>) -> Self {
Self { push }
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PresenceUpdate {
pub user_id: OwnedUserId,
pub presence: PresenceState,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_msg: Option<String>,
pub last_active_ago: UInt,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub currently_active: bool,
}
#[cfg(feature = "unstable-msc2409")]
impl PresenceUpdate {
pub fn new(user_id: OwnedUserId, presence: PresenceState, last_activity: UInt) -> Self {
Self {
user_id,
presence,
last_active_ago: last_activity,
status_msg: None,
currently_active: false,
}
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(transparent)]
pub struct ReceiptContent(pub BTreeMap<OwnedRoomId, ReceiptMap>);
#[cfg(feature = "unstable-msc2409")]
impl ReceiptContent {
pub fn new(receipts: BTreeMap<OwnedRoomId, ReceiptMap>) -> Self {
Self(receipts)
}
}
#[cfg(feature = "unstable-msc2409")]
impl Deref for ReceiptContent {
type Target = BTreeMap<OwnedRoomId, ReceiptMap>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "unstable-msc2409")]
impl DerefMut for ReceiptContent {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "unstable-msc2409")]
impl IntoIterator for ReceiptContent {
type Item = (OwnedRoomId, ReceiptMap);
type IntoIter = btree_map::IntoIter<OwnedRoomId, ReceiptMap>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[cfg(feature = "unstable-msc2409")]
impl FromIterator<(OwnedRoomId, ReceiptMap)> for ReceiptContent {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (OwnedRoomId, ReceiptMap)>,
{
Self(BTreeMap::from_iter(iter))
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ReceiptMap {
#[serde(rename = "m.read")]
pub read: BTreeMap<OwnedUserId, ReceiptData>,
}
#[cfg(feature = "unstable-msc2409")]
impl ReceiptMap {
pub fn new(read: BTreeMap<OwnedUserId, ReceiptData>) -> Self {
Self { read }
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct ReceiptData {
pub data: Receipt,
pub event_ids: Vec<OwnedEventId>,
}
#[cfg(feature = "unstable-msc2409")]
impl ReceiptData {
pub fn new(data: Receipt, event_ids: Vec<OwnedEventId>) -> Self {
Self { data, event_ids }
}
}
#[cfg(feature = "unstable-msc2409")]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct TypingContent {
pub room_id: OwnedRoomId,
pub user_id: OwnedUserId,
pub typing: bool,
}
#[cfg(feature = "unstable-msc2409")]
impl TypingContent {
pub fn new(room_id: OwnedRoomId, user_id: OwnedUserId, typing: bool) -> Self {
Self { room_id, user_id, typing }
}
}
#[cfg(feature = "server")]
#[cfg(test)]
mod tests {
use ruma_common::api::{OutgoingRequest, SendAccessToken};
use serde_json::json;
use super::Request;
#[test]
fn decode_request_contains_events_field() {
let dummy_event = serde_json::from_value(json!({
"type": "m.room.message",
"event_id": "$143273582443PhrSn:example.com",
"origin_server_ts": 1,
"room_id": "!roomid:room.com",
"sender": "@user:example.com",
"content": {
"body": "test",
"msgtype": "m.text",
},
}))
.unwrap();
let events = vec![dummy_event];
let req = Request::new("any_txn_id".into(), events)
.try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"),
&[ruma_common::api::MatrixVersion::V1_1],
)
.unwrap();
let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
assert_eq!(
1,
json_body.as_object().unwrap().get("events").unwrap().as_array().unwrap().len()
);
}
}
}