ruma_appservice_api/event/
push_events.rspub mod v1 {
use std::borrow::Cow;
#[cfg(feature = "unstable-msc3202")]
use std::collections::BTreeMap;
#[cfg(feature = "unstable-msc3202")]
use js_int::UInt;
#[cfg(feature = "unstable-msc3202")]
use ruma_common::OwnedUserId;
use ruma_common::{
api::{request, response, Metadata},
metadata,
serde::{from_raw_json_value, JsonObject, Raw},
OwnedTransactionId,
};
#[cfg(feature = "unstable-msc3202")]
use ruma_common::{OneTimeKeyAlgorithm, OwnedDeviceId};
#[cfg(feature = "unstable-msc4203")]
use ruma_events::AnyToDeviceEvent;
use ruma_events::{
presence::PresenceEvent, receipt::ReceiptEvent, typing::TypingEvent, AnyTimelineEvent,
};
use serde::{Deserialize, Deserializer, Serialize};
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>>>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub ephemeral: Vec<EphemeralData>,
#[cfg(feature = "unstable-msc4203")]
#[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(),
ephemeral: Vec::new(),
#[cfg(feature = "unstable-msc4203")]
to_device: Vec::new(),
}
}
}
impl Response {
pub fn new() -> Self {
Self {}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(ruma_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()
}
}
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[serde(untagged)]
pub enum EphemeralData {
Presence(PresenceEvent),
Receipt(ReceiptEvent),
Typing(TypingEvent),
#[doc(hidden)]
_Custom(_CustomEphemeralData),
}
impl EphemeralData {
pub fn data_type(&self) -> &str {
match self {
Self::Presence(_) => "m.presence",
Self::Receipt(_) => "m.receipt",
Self::Typing(_) => "m.typing",
Self::_Custom(c) => &c.data_type,
}
}
pub fn data(&self) -> Cow<'_, JsonObject> {
fn serialize<T: Serialize>(obj: &T) -> JsonObject {
match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
JsonValue::Object(obj) => obj,
_ => panic!("all ephemeral data types must serialize to objects"),
}
}
match self {
Self::Presence(d) => Cow::Owned(serialize(d)),
Self::Receipt(d) => Cow::Owned(serialize(d)),
Self::Typing(d) => Cow::Owned(serialize(d)),
Self::_Custom(c) => Cow::Borrowed(&c.data),
}
}
}
impl<'de> Deserialize<'de> for EphemeralData {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct EphemeralDataDeHelper {
#[serde(rename = "type")]
data_type: String,
}
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
Ok(match data_type.as_ref() {
"m.presence" => Self::Presence(from_raw_json_value(&json)?),
"m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
"m.typing" => Self::Typing(from_raw_json_value(&json)?),
_ => Self::_Custom(_CustomEphemeralData {
data_type,
data: from_raw_json_value(&json)?,
}),
})
}
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct _CustomEphemeralData {
data_type: String,
data: JsonObject,
}
impl Serialize for _CustomEphemeralData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.data.serialize(serializer)
}
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use js_int::uint;
use ruma_common::{event_id, room_id, user_id, MilliSecondsSinceUnixEpoch};
use ruma_events::receipt::ReceiptType;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::EphemeralData;
#[cfg(feature = "client")]
#[test]
fn request_contains_events_field() {
use ruma_common::api::{OutgoingRequest, SendAccessToken};
let dummy_event_json = 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",
},
});
let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
let events = vec![dummy_event];
let req = super::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!(
json_body,
json!({
"events": [
dummy_event_json,
]
})
);
}
#[test]
fn serde_ephemeral_data() {
let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
let user_id = user_id!("@alice:server.local");
let event_id = event_id!("$1435641916114394fHBL");
let typing_json = json!({
"type": "m.typing",
"room_id": room_id,
"content": {
"user_ids": [user_id],
},
});
let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
assert_matches!(&data, EphemeralData::Typing(typing));
assert_eq!(typing.room_id, room_id);
assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
let serialized_data = to_json_value(data).unwrap();
assert_eq!(serialized_data, typing_json);
let receipt_json = json!({
"type": "m.receipt",
"room_id": room_id,
"content": {
event_id: {
"m.read": {
user_id: {
"ts": 453,
},
},
},
},
});
let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
assert_matches!(&data, EphemeralData::Receipt(receipt));
assert_eq!(receipt.room_id, room_id);
let event_receipts = receipt.content.get(event_id).unwrap();
let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
let serialized_data = to_json_value(data).unwrap();
assert_eq!(serialized_data, receipt_json);
let presence_json = json!({
"type": "m.presence",
"sender": user_id,
"content": {
"avatar_url": "mxc://localhost/wefuiwegh8742w",
"currently_active": false,
"last_active_ago": 785,
"presence": "online",
"status_msg": "Making cupcakes",
},
});
let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
assert_matches!(&data, EphemeralData::Presence(presence));
assert_eq!(presence.sender, user_id);
assert_eq!(presence.content.currently_active, Some(false));
let serialized_data = to_json_value(data).unwrap();
assert_eq!(serialized_data, presence_json);
let custom_json = json!({
"type": "dev.ruma.custom",
"key": "value",
"content": {
"foo": "bar",
},
});
let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
let serialized_data = to_json_value(data).unwrap();
assert_eq!(serialized_data, custom_json);
}
}
}