1pub 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::OneTimeKeyAlgorithm;
18 #[cfg(any(feature = "unstable-msc3202", feature = "unstable-msc4203"))]
19 use ruma_common::{OwnedDeviceId, OwnedUserId};
20 use ruma_common::{
21 OwnedTransactionId,
22 api::{auth_scheme::AccessToken, request, response},
23 metadata,
24 serde::{JsonObject, Raw, from_raw_json_value},
25 };
26 #[cfg(feature = "unstable-msc4203")]
27 use ruma_common::{UserId, serde::JsonCastable};
28 use ruma_events::{
29 AnyTimelineEvent, presence::PresenceEvent, receipt::ReceiptEvent, typing::TypingEvent,
30 };
31 #[cfg(feature = "unstable-msc4203")]
32 use ruma_events::{AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEventType};
33 use serde::{Deserialize, Deserializer, Serialize};
34 use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
35
36 metadata! {
37 method: PUT,
38 rate_limited: false,
39 authentication: AccessToken,
40 path: "/_matrix/app/v1/transactions/{txn_id}",
41 }
42
43 #[request]
45 pub struct Request {
46 #[ruma_api(path)]
50 pub txn_id: OwnedTransactionId,
51
52 pub events: Vec<Raw<AnyTimelineEvent>>,
54
55 #[cfg(feature = "unstable-msc3202")]
57 #[serde(
58 default,
59 skip_serializing_if = "DeviceLists::is_empty",
60 rename = "org.matrix.msc3202.device_lists"
61 )]
62 pub device_lists: DeviceLists,
63
64 #[cfg(feature = "unstable-msc3202")]
67 #[serde(
68 default,
69 skip_serializing_if = "BTreeMap::is_empty",
70 rename = "org.matrix.msc3202.device_one_time_keys_count"
71 )]
72 pub device_one_time_keys_count:
73 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, BTreeMap<OneTimeKeyAlgorithm, UInt>>>,
74
75 #[cfg(feature = "unstable-msc3202")]
78 #[serde(
79 default,
80 skip_serializing_if = "BTreeMap::is_empty",
81 rename = "org.matrix.msc3202.device_unused_fallback_key_types"
82 )]
83 pub device_unused_fallback_key_types:
84 BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Vec<OneTimeKeyAlgorithm>>>,
85
86 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
88 pub ephemeral: Vec<Raw<EphemeralData>>,
89
90 #[cfg(feature = "unstable-msc4203")]
92 #[serde(
93 default,
94 skip_serializing_if = "<[_]>::is_empty",
95 rename = "de.sorunome.msc2409.to_device"
96 )]
97 pub to_device: Vec<Raw<AnyAppserviceToDeviceEvent>>,
98 }
99
100 #[response]
102 #[derive(Default)]
103 pub struct Response {}
104
105 impl Request {
106 pub fn new(txn_id: OwnedTransactionId, events: Vec<Raw<AnyTimelineEvent>>) -> Request {
108 Request {
109 txn_id,
110 events,
111 #[cfg(feature = "unstable-msc3202")]
112 device_lists: DeviceLists::new(),
113 #[cfg(feature = "unstable-msc3202")]
114 device_one_time_keys_count: BTreeMap::new(),
115 #[cfg(feature = "unstable-msc3202")]
116 device_unused_fallback_key_types: BTreeMap::new(),
117 ephemeral: Vec::new(),
118 #[cfg(feature = "unstable-msc4203")]
119 to_device: Vec::new(),
120 }
121 }
122 }
123
124 impl Response {
125 pub fn new() -> Self {
127 Self {}
128 }
129 }
130
131 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
133 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
134 #[cfg(feature = "unstable-msc3202")]
135 pub struct DeviceLists {
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
139 pub changed: Vec<OwnedUserId>,
140
141 #[serde(default, skip_serializing_if = "Vec::is_empty")]
144 pub left: Vec<OwnedUserId>,
145 }
146
147 #[cfg(feature = "unstable-msc3202")]
148 impl DeviceLists {
149 pub fn new() -> Self {
151 Default::default()
152 }
153
154 pub fn is_empty(&self) -> bool {
156 let Self { changed, left } = self;
157 changed.is_empty() && left.is_empty()
158 }
159 }
160
161 #[derive(Clone, Debug, Serialize)]
163 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
164 #[serde(untagged)]
165 pub enum EphemeralData {
166 Presence(PresenceEvent),
168
169 Receipt(ReceiptEvent),
171
172 Typing(TypingEvent),
174
175 #[doc(hidden)]
176 _Custom(_CustomEphemeralData),
177 }
178
179 impl EphemeralData {
180 pub fn data_type(&self) -> &str {
182 match self {
183 Self::Presence(_) => "m.presence",
184 Self::Receipt(_) => "m.receipt",
185 Self::Typing(_) => "m.typing",
186 Self::_Custom(c) => &c.data_type,
187 }
188 }
189
190 pub fn data(&self) -> Cow<'_, JsonObject> {
195 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
196 match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
197 JsonValue::Object(obj) => obj,
198 _ => panic!("all ephemeral data types must serialize to objects"),
199 }
200 }
201
202 match self {
203 Self::Presence(d) => Cow::Owned(serialize(d)),
204 Self::Receipt(d) => Cow::Owned(serialize(d)),
205 Self::Typing(d) => Cow::Owned(serialize(d)),
206 Self::_Custom(c) => Cow::Borrowed(&c.data),
207 }
208 }
209 }
210
211 impl<'de> Deserialize<'de> for EphemeralData {
212 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213 where
214 D: Deserializer<'de>,
215 {
216 #[derive(Deserialize)]
217 struct EphemeralDataDeHelper {
218 #[serde(rename = "type")]
220 data_type: String,
221 }
222
223 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
224 let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
225
226 Ok(match data_type.as_ref() {
227 "m.presence" => Self::Presence(from_raw_json_value(&json)?),
228 "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
229 "m.typing" => Self::Typing(from_raw_json_value(&json)?),
230 _ => Self::_Custom(_CustomEphemeralData {
231 data_type,
232 data: from_raw_json_value(&json)?,
233 }),
234 })
235 }
236 }
237
238 #[doc(hidden)]
240 #[derive(Debug, Clone)]
241 pub struct _CustomEphemeralData {
242 data_type: String,
244 data: JsonObject,
246 }
247
248 impl Serialize for _CustomEphemeralData {
249 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250 where
251 S: serde::Serializer,
252 {
253 self.data.serialize(serializer)
254 }
255 }
256
257 #[derive(Clone, Debug)]
260 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261 #[cfg(feature = "unstable-msc4203")]
262 pub struct AnyAppserviceToDeviceEvent {
263 pub event: AnyToDeviceEvent,
265
266 pub to_user_id: OwnedUserId,
268
269 pub to_device_id: OwnedDeviceId,
271 }
272
273 #[cfg(feature = "unstable-msc4203")]
274 impl AnyAppserviceToDeviceEvent {
275 pub fn new(
278 event: AnyToDeviceEvent,
279 to_user_id: OwnedUserId,
280 to_device_id: OwnedDeviceId,
281 ) -> Self {
282 Self { event, to_user_id, to_device_id }
283 }
284
285 pub fn sender(&self) -> &UserId {
287 self.event.sender()
288 }
289
290 pub fn event_type(&self) -> ToDeviceEventType {
292 self.event.event_type()
293 }
294
295 pub fn content(&self) -> AnyToDeviceEventContent {
297 self.event.content()
298 }
299 }
300
301 #[cfg(feature = "unstable-msc4203")]
302 impl<'de> Deserialize<'de> for AnyAppserviceToDeviceEvent {
303 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304 where
305 D: Deserializer<'de>,
306 {
307 #[derive(Deserialize)]
308 struct AppserviceFields {
309 to_user_id: OwnedUserId,
310 to_device_id: OwnedDeviceId,
311 }
312
313 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
314
315 let event = from_raw_json_value(&json)?;
316
317 let AppserviceFields { to_user_id, to_device_id } = from_raw_json_value(&json)?;
318
319 Ok(AnyAppserviceToDeviceEvent::new(event, to_user_id, to_device_id))
320 }
321 }
322
323 #[cfg(feature = "unstable-msc4203")]
324 impl JsonCastable<JsonObject> for AnyAppserviceToDeviceEvent {}
325 #[cfg(feature = "unstable-msc4203")]
326 impl JsonCastable<AnyToDeviceEvent> for AnyAppserviceToDeviceEvent {}
327
328 #[cfg(test)]
329 mod tests {
330 use assert_matches2::assert_let;
331 use js_int::uint;
332 use ruma_common::{
333 MilliSecondsSinceUnixEpoch, canonical_json::assert_to_canonical_json_eq, event_id,
334 room_id, user_id,
335 };
336 use ruma_events::receipt::ReceiptType;
337 use serde_json::{from_value as from_json_value, json};
338
339 use super::EphemeralData;
340
341 #[cfg(feature = "client")]
342 #[test]
343 fn request_contains_events_field() {
344 use ruma_common::api::{OutgoingRequest, auth_scheme::SendAccessToken};
345
346 let dummy_event_json = json!({
347 "type": "m.room.message",
348 "event_id": "$143273582443PhrSn:example.com",
349 "origin_server_ts": 1,
350 "room_id": "!roomid:room.com",
351 "sender": "@user:example.com",
352 "content": {
353 "body": "test",
354 "msgtype": "m.text",
355 },
356 });
357 let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
358 let events = vec![dummy_event];
359
360 let req = super::Request::new("any_txn_id".into(), events)
361 .try_into_http_request::<Vec<u8>>(
362 "https://homeserver.tld",
363 SendAccessToken::IfRequired("auth_tok"),
364 (),
365 )
366 .unwrap();
367 let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
368
369 assert_eq!(
370 json_body,
371 json!({
372 "events": [
373 dummy_event_json,
374 ]
375 })
376 );
377 }
378
379 #[test]
380 fn serde_ephemeral_data() {
381 let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
382 let user_id = user_id!("@alice:server.local");
383 let event_id = event_id!("$1435641916114394fHBL");
384
385 let typing_json = json!({
387 "type": "m.typing",
388 "room_id": room_id,
389 "content": {
390 "user_ids": [user_id],
391 },
392 });
393
394 let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
395 assert_let!(EphemeralData::Typing(typing) = &data);
396 assert_eq!(typing.room_id, room_id);
397 assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
398
399 assert_to_canonical_json_eq!(data, typing_json);
400
401 let receipt_json = json!({
403 "type": "m.receipt",
404 "room_id": room_id,
405 "content": {
406 event_id: {
407 "m.read": {
408 user_id: {
409 "ts": 453,
410 },
411 },
412 },
413 },
414 });
415
416 let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
417 assert_let!(EphemeralData::Receipt(receipt) = &data);
418 assert_eq!(receipt.room_id, room_id);
419 let event_receipts = receipt.content.get(event_id).unwrap();
420 let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
421 let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
422 assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
423
424 assert_to_canonical_json_eq!(data, receipt_json);
425
426 let presence_json = json!({
428 "type": "m.presence",
429 "sender": user_id,
430 "content": {
431 "avatar_url": "mxc://localhost/wefuiwegh8742w",
432 "currently_active": false,
433 "last_active_ago": 785,
434 "presence": "online",
435 "status_msg": "Making cupcakes",
436 },
437 });
438
439 let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
440 assert_let!(EphemeralData::Presence(presence) = &data);
441 assert_eq!(presence.sender, user_id);
442 assert_eq!(presence.content.currently_active, Some(false));
443
444 assert_to_canonical_json_eq!(data, presence_json);
445
446 let custom_json = json!({
448 "type": "dev.ruma.custom",
449 "key": "value",
450 "content": {
451 "foo": "bar",
452 },
453 });
454
455 let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
456
457 assert_to_canonical_json_eq!(data, custom_json);
458 }
459
460 #[test]
461 #[cfg(feature = "unstable-msc4203")]
462 fn serde_any_appservice_to_device_event() {
463 use ruma_common::{device_id, user_id};
464
465 use super::AnyAppserviceToDeviceEvent;
466
467 let event_json = json!({
468 "type": "m.key.verification.request",
469 "sender": "@alice:example.org",
470 "content": {
471 "from_device": "AliceDevice2",
472 "methods": [
473 "m.sas.v1"
474 ],
475 "timestamp": 1_559_598_944_869_i64,
476 "transaction_id": "S0meUniqueAndOpaqueString"
477 },
478 "to_user_id": "@bob:example.org",
479 "to_device_id": "DEVICEID"
480 });
481
482 let event = from_json_value::<AnyAppserviceToDeviceEvent>(event_json.clone()).unwrap();
484 assert_eq!(event.sender(), user_id!("@alice:example.org"));
485 assert_eq!(event.to_user_id, user_id!("@bob:example.org"));
486 assert_eq!(event.to_device_id, device_id!("DEVICEID"));
487 assert_eq!(event.event_type().to_string(), "m.key.verification.request");
488 }
489 }
490}