ruma_appservice_api/event/
push_events.rs1pub mod v1 {
6 use std::borrow::Cow;
11 #[cfg(feature = "unstable-msc3202")]
12 use std::collections::BTreeMap;
13
14 #[cfg(feature = "unstable-msc3202")]
15 use js_int::UInt;
16 #[cfg(feature = "unstable-msc3202")]
17 use ruma_common::OwnedUserId;
18 use ruma_common::{
19 api::{auth_scheme::AccessToken, request, response},
20 metadata,
21 serde::{from_raw_json_value, JsonObject, Raw},
22 OwnedTransactionId,
23 };
24 #[cfg(feature = "unstable-msc3202")]
25 use ruma_common::{OneTimeKeyAlgorithm, OwnedDeviceId};
26 #[cfg(feature = "unstable-msc4203")]
27 use ruma_events::AnyToDeviceEvent;
28 use ruma_events::{
29 presence::PresenceEvent, receipt::ReceiptEvent, typing::TypingEvent, AnyTimelineEvent,
30 };
31 use serde::{Deserialize, Deserializer, Serialize};
32 use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
33
34 metadata! {
35 method: PUT,
36 rate_limited: false,
37 authentication: AccessToken,
38 path: "/_matrix/app/v1/transactions/{txn_id}",
39 }
40
41 #[request]
43 pub struct Request {
44 #[ruma_api(path)]
48 pub txn_id: OwnedTransactionId,
49
50 pub events: Vec<Raw<AnyTimelineEvent>>,
52
53 #[cfg(feature = "unstable-msc3202")]
55 #[serde(
56 default,
57 skip_serializing_if = "DeviceLists::is_empty",
58 rename = "org.matrix.msc3202.device_lists"
59 )]
60 pub device_lists: DeviceLists,
61
62 #[cfg(feature = "unstable-msc3202")]
65 #[serde(
66 default,
67 skip_serializing_if = "BTreeMap::is_empty",
68 rename = "org.matrix.msc3202.device_one_time_keys_count"
69 )]
70 pub device_one_time_keys_count:
71 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<OneTimeKeyAlgorithm, UInt>>>,
72
73 #[cfg(feature = "unstable-msc3202")]
76 #[serde(
77 default,
78 skip_serializing_if = "BTreeMap::is_empty",
79 rename = "org.matrix.msc3202.device_unused_fallback_key_types"
80 )]
81 pub device_unused_fallback_key_types:
82 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<OneTimeKeyAlgorithm>>>,
83
84 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
86 pub ephemeral: Vec<Raw<EphemeralData>>,
87
88 #[cfg(feature = "unstable-msc4203")]
90 #[serde(
91 default,
92 skip_serializing_if = "<[_]>::is_empty",
93 rename = "de.sorunome.msc2409.to_device"
94 )]
95 pub to_device: Vec<Raw<AnyToDeviceEvent>>,
96 }
97
98 #[response]
100 #[derive(Default)]
101 pub struct Response {}
102
103 impl Request {
104 pub fn new(txn_id: OwnedTransactionId, events: Vec<Raw<AnyTimelineEvent>>) -> Request {
106 Request {
107 txn_id,
108 events,
109 #[cfg(feature = "unstable-msc3202")]
110 device_lists: DeviceLists::new(),
111 #[cfg(feature = "unstable-msc3202")]
112 device_one_time_keys_count: BTreeMap::new(),
113 #[cfg(feature = "unstable-msc3202")]
114 device_unused_fallback_key_types: BTreeMap::new(),
115 ephemeral: Vec::new(),
116 #[cfg(feature = "unstable-msc4203")]
117 to_device: Vec::new(),
118 }
119 }
120 }
121
122 impl Response {
123 pub fn new() -> Self {
125 Self {}
126 }
127 }
128
129 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
131 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
132 #[cfg(feature = "unstable-msc3202")]
133 pub struct DeviceLists {
134 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub changed: Vec<OwnedUserId>,
138
139 #[serde(default, skip_serializing_if = "Vec::is_empty")]
142 pub left: Vec<OwnedUserId>,
143 }
144
145 #[cfg(feature = "unstable-msc3202")]
146 impl DeviceLists {
147 pub fn new() -> Self {
149 Default::default()
150 }
151
152 pub fn is_empty(&self) -> bool {
154 self.changed.is_empty() && self.left.is_empty()
155 }
156 }
157
158 #[derive(Clone, Debug, Serialize)]
160 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
161 #[serde(untagged)]
162 pub enum EphemeralData {
163 Presence(PresenceEvent),
165
166 Receipt(ReceiptEvent),
168
169 Typing(TypingEvent),
171
172 #[doc(hidden)]
173 _Custom(_CustomEphemeralData),
174 }
175
176 impl EphemeralData {
177 pub fn data_type(&self) -> &str {
179 match self {
180 Self::Presence(_) => "m.presence",
181 Self::Receipt(_) => "m.receipt",
182 Self::Typing(_) => "m.typing",
183 Self::_Custom(c) => &c.data_type,
184 }
185 }
186
187 pub fn data(&self) -> Cow<'_, JsonObject> {
192 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
193 match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
194 JsonValue::Object(obj) => obj,
195 _ => panic!("all ephemeral data types must serialize to objects"),
196 }
197 }
198
199 match self {
200 Self::Presence(d) => Cow::Owned(serialize(d)),
201 Self::Receipt(d) => Cow::Owned(serialize(d)),
202 Self::Typing(d) => Cow::Owned(serialize(d)),
203 Self::_Custom(c) => Cow::Borrowed(&c.data),
204 }
205 }
206 }
207
208 impl<'de> Deserialize<'de> for EphemeralData {
209 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210 where
211 D: Deserializer<'de>,
212 {
213 #[derive(Deserialize)]
214 struct EphemeralDataDeHelper {
215 #[serde(rename = "type")]
217 data_type: String,
218 }
219
220 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
221 let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
222
223 Ok(match data_type.as_ref() {
224 "m.presence" => Self::Presence(from_raw_json_value(&json)?),
225 "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
226 "m.typing" => Self::Typing(from_raw_json_value(&json)?),
227 _ => Self::_Custom(_CustomEphemeralData {
228 data_type,
229 data: from_raw_json_value(&json)?,
230 }),
231 })
232 }
233 }
234
235 #[doc(hidden)]
237 #[derive(Debug, Clone)]
238 pub struct _CustomEphemeralData {
239 data_type: String,
241 data: JsonObject,
243 }
244
245 impl Serialize for _CustomEphemeralData {
246 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247 where
248 S: serde::Serializer,
249 {
250 self.data.serialize(serializer)
251 }
252 }
253
254 #[cfg(test)]
255 mod tests {
256 use assert_matches2::assert_matches;
257 use js_int::uint;
258 use ruma_common::{event_id, room_id, user_id, MilliSecondsSinceUnixEpoch};
259 use ruma_events::receipt::ReceiptType;
260 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
261
262 use super::EphemeralData;
263
264 #[cfg(feature = "client")]
265 #[test]
266 fn request_contains_events_field() {
267 use ruma_common::api::{auth_scheme::SendAccessToken, OutgoingRequest};
268
269 let dummy_event_json = json!({
270 "type": "m.room.message",
271 "event_id": "$143273582443PhrSn:example.com",
272 "origin_server_ts": 1,
273 "room_id": "!roomid:room.com",
274 "sender": "@user:example.com",
275 "content": {
276 "body": "test",
277 "msgtype": "m.text",
278 },
279 });
280 let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
281 let events = vec![dummy_event];
282
283 let req = super::Request::new("any_txn_id".into(), events)
284 .try_into_http_request::<Vec<u8>>(
285 "https://homeserver.tld",
286 SendAccessToken::IfRequired("auth_tok"),
287 (),
288 )
289 .unwrap();
290 let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
291
292 assert_eq!(
293 json_body,
294 json!({
295 "events": [
296 dummy_event_json,
297 ]
298 })
299 );
300 }
301
302 #[test]
303 fn serde_ephemeral_data() {
304 let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
305 let user_id = user_id!("@alice:server.local");
306 let event_id = event_id!("$1435641916114394fHBL");
307
308 let typing_json = json!({
310 "type": "m.typing",
311 "room_id": room_id,
312 "content": {
313 "user_ids": [user_id],
314 },
315 });
316
317 let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
318 assert_matches!(&data, EphemeralData::Typing(typing));
319 assert_eq!(typing.room_id, room_id);
320 assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
321
322 let serialized_data = to_json_value(data).unwrap();
323 assert_eq!(serialized_data, typing_json);
324
325 let receipt_json = json!({
327 "type": "m.receipt",
328 "room_id": room_id,
329 "content": {
330 event_id: {
331 "m.read": {
332 user_id: {
333 "ts": 453,
334 },
335 },
336 },
337 },
338 });
339
340 let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
341 assert_matches!(&data, EphemeralData::Receipt(receipt));
342 assert_eq!(receipt.room_id, room_id);
343 let event_receipts = receipt.content.get(event_id).unwrap();
344 let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
345 let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
346 assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
347
348 let serialized_data = to_json_value(data).unwrap();
349 assert_eq!(serialized_data, receipt_json);
350
351 let presence_json = json!({
353 "type": "m.presence",
354 "sender": user_id,
355 "content": {
356 "avatar_url": "mxc://localhost/wefuiwegh8742w",
357 "currently_active": false,
358 "last_active_ago": 785,
359 "presence": "online",
360 "status_msg": "Making cupcakes",
361 },
362 });
363
364 let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
365 assert_matches!(&data, EphemeralData::Presence(presence));
366 assert_eq!(presence.sender, user_id);
367 assert_eq!(presence.content.currently_active, Some(false));
368
369 let serialized_data = to_json_value(data).unwrap();
370 assert_eq!(serialized_data, presence_json);
371
372 let custom_json = json!({
374 "type": "dev.ruma.custom",
375 "key": "value",
376 "content": {
377 "foo": "bar",
378 },
379 });
380
381 let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
382
383 let serialized_data = to_json_value(data).unwrap();
384 assert_eq!(serialized_data, custom_json);
385 }
386 }
387}