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::{request, response, Metadata},
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 const METADATA: Metadata = metadata! {
35 method: PUT,
36 rate_limited: false,
37 authentication: AccessToken,
38 history: {
39 1.0 => "/_matrix/app/v1/transactions/{txn_id}",
40 }
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<AnyToDeviceEvent>>,
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 self.changed.is_empty() && self.left.is_empty()
157 }
158 }
159
160 #[derive(Clone, Debug, Serialize)]
162 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
163 #[serde(untagged)]
164 pub enum EphemeralData {
165 Presence(PresenceEvent),
167
168 Receipt(ReceiptEvent),
170
171 Typing(TypingEvent),
173
174 #[doc(hidden)]
175 _Custom(_CustomEphemeralData),
176 }
177
178 impl EphemeralData {
179 pub fn data_type(&self) -> &str {
181 match self {
182 Self::Presence(_) => "m.presence",
183 Self::Receipt(_) => "m.receipt",
184 Self::Typing(_) => "m.typing",
185 Self::_Custom(c) => &c.data_type,
186 }
187 }
188
189 pub fn data(&self) -> Cow<'_, JsonObject> {
194 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
195 match serde_json::to_value(obj).expect("ephemeral data serialization to succeed") {
196 JsonValue::Object(obj) => obj,
197 _ => panic!("all ephemeral data types must serialize to objects"),
198 }
199 }
200
201 match self {
202 Self::Presence(d) => Cow::Owned(serialize(d)),
203 Self::Receipt(d) => Cow::Owned(serialize(d)),
204 Self::Typing(d) => Cow::Owned(serialize(d)),
205 Self::_Custom(c) => Cow::Borrowed(&c.data),
206 }
207 }
208 }
209
210 impl<'de> Deserialize<'de> for EphemeralData {
211 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212 where
213 D: Deserializer<'de>,
214 {
215 #[derive(Deserialize)]
216 struct EphemeralDataDeHelper {
217 #[serde(rename = "type")]
219 data_type: String,
220 }
221
222 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
223 let EphemeralDataDeHelper { data_type } = from_raw_json_value(&json)?;
224
225 Ok(match data_type.as_ref() {
226 "m.presence" => Self::Presence(from_raw_json_value(&json)?),
227 "m.receipt" => Self::Receipt(from_raw_json_value(&json)?),
228 "m.typing" => Self::Typing(from_raw_json_value(&json)?),
229 _ => Self::_Custom(_CustomEphemeralData {
230 data_type,
231 data: from_raw_json_value(&json)?,
232 }),
233 })
234 }
235 }
236
237 #[doc(hidden)]
239 #[derive(Debug, Clone)]
240 pub struct _CustomEphemeralData {
241 data_type: String,
243 data: JsonObject,
245 }
246
247 impl Serialize for _CustomEphemeralData {
248 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
249 where
250 S: serde::Serializer,
251 {
252 self.data.serialize(serializer)
253 }
254 }
255
256 #[cfg(test)]
257 mod tests {
258 use assert_matches2::assert_matches;
259 use js_int::uint;
260 use ruma_common::{event_id, room_id, user_id, MilliSecondsSinceUnixEpoch};
261 use ruma_events::receipt::ReceiptType;
262 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
263
264 use super::EphemeralData;
265
266 #[cfg(feature = "client")]
267 #[test]
268 fn request_contains_events_field() {
269 use ruma_common::api::{
270 MatrixVersion, OutgoingRequest, SendAccessToken, SupportedVersions,
271 };
272
273 let dummy_event_json = json!({
274 "type": "m.room.message",
275 "event_id": "$143273582443PhrSn:example.com",
276 "origin_server_ts": 1,
277 "room_id": "!roomid:room.com",
278 "sender": "@user:example.com",
279 "content": {
280 "body": "test",
281 "msgtype": "m.text",
282 },
283 });
284 let dummy_event = from_json_value(dummy_event_json.clone()).unwrap();
285 let events = vec![dummy_event];
286 let supported = SupportedVersions {
287 versions: [MatrixVersion::V1_1].into(),
288 features: Default::default(),
289 };
290
291 let req = super::Request::new("any_txn_id".into(), events)
292 .try_into_http_request::<Vec<u8>>(
293 "https://homeserver.tld",
294 SendAccessToken::IfRequired("auth_tok"),
295 &supported,
296 )
297 .unwrap();
298 let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();
299
300 assert_eq!(
301 json_body,
302 json!({
303 "events": [
304 dummy_event_json,
305 ]
306 })
307 );
308 }
309
310 #[test]
311 fn serde_ephemeral_data() {
312 let room_id = room_id!("!jEsUZKDJdhlrceRyVU:server.local");
313 let user_id = user_id!("@alice:server.local");
314 let event_id = event_id!("$1435641916114394fHBL");
315
316 let typing_json = json!({
318 "type": "m.typing",
319 "room_id": room_id,
320 "content": {
321 "user_ids": [user_id],
322 },
323 });
324
325 let data = from_json_value::<EphemeralData>(typing_json.clone()).unwrap();
326 assert_matches!(&data, EphemeralData::Typing(typing));
327 assert_eq!(typing.room_id, room_id);
328 assert_eq!(typing.content.user_ids, &[user_id.to_owned()]);
329
330 let serialized_data = to_json_value(data).unwrap();
331 assert_eq!(serialized_data, typing_json);
332
333 let receipt_json = json!({
335 "type": "m.receipt",
336 "room_id": room_id,
337 "content": {
338 event_id: {
339 "m.read": {
340 user_id: {
341 "ts": 453,
342 },
343 },
344 },
345 },
346 });
347
348 let data = from_json_value::<EphemeralData>(receipt_json.clone()).unwrap();
349 assert_matches!(&data, EphemeralData::Receipt(receipt));
350 assert_eq!(receipt.room_id, room_id);
351 let event_receipts = receipt.content.get(event_id).unwrap();
352 let event_read_receipts = event_receipts.get(&ReceiptType::Read).unwrap();
353 let event_user_read_receipt = event_read_receipts.get(user_id).unwrap();
354 assert_eq!(event_user_read_receipt.ts, Some(MilliSecondsSinceUnixEpoch(uint!(453))));
355
356 let serialized_data = to_json_value(data).unwrap();
357 assert_eq!(serialized_data, receipt_json);
358
359 let presence_json = json!({
361 "type": "m.presence",
362 "sender": user_id,
363 "content": {
364 "avatar_url": "mxc://localhost/wefuiwegh8742w",
365 "currently_active": false,
366 "last_active_ago": 785,
367 "presence": "online",
368 "status_msg": "Making cupcakes",
369 },
370 });
371
372 let data = from_json_value::<EphemeralData>(presence_json.clone()).unwrap();
373 assert_matches!(&data, EphemeralData::Presence(presence));
374 assert_eq!(presence.sender, user_id);
375 assert_eq!(presence.content.currently_active, Some(false));
376
377 let serialized_data = to_json_value(data).unwrap();
378 assert_eq!(serialized_data, presence_json);
379
380 let custom_json = json!({
382 "type": "dev.ruma.custom",
383 "key": "value",
384 "content": {
385 "foo": "bar",
386 },
387 });
388
389 let data = from_json_value::<EphemeralData>(custom_json.clone()).unwrap();
390
391 let serialized_data = to_json_value(data).unwrap();
392 assert_eq!(serialized_data, custom_json);
393 }
394 }
395}