1use js_int::UInt;
9use ruma_common::{OwnedEventId, OwnedRoomId};
10use ruma_macros::EventContent;
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
17#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
18#[ruma_event(
19 type = "org.matrix.msc4471.stream.update",
20 alias = "m.stream.update",
21 kind = ToDevice,
22)]
23pub struct ToDeviceStreamUpdateEventContent {
24 pub room_id: OwnedRoomId,
26
27 pub event_id: OwnedEventId,
29
30 pub seq: UInt,
36
37 #[serde(flatten)]
39 pub operation: StreamUpdateOperation,
40}
41
42impl ToDeviceStreamUpdateEventContent {
43 pub fn new(
46 room_id: OwnedRoomId,
47 event_id: OwnedEventId,
48 seq: UInt,
49 operation: StreamUpdateOperation,
50 ) -> Self {
51 Self { room_id, event_id, seq, operation }
52 }
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
57#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
58#[serde(tag = "op", content = "content", rename_all = "snake_case")]
59pub enum StreamUpdateOperation {
60 Replace(StreamUpdateContent),
62
63 Append(StreamUpdateContent),
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
69#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
70pub struct StreamUpdateContent {
71 pub body: String,
73}
74
75impl StreamUpdateContent {
76 pub fn new(body: String) -> Self {
78 Self { body }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use assert_matches2::assert_matches;
85 use js_int::uint;
86 use ruma_common::{
87 canonical_json::assert_to_canonical_json_eq, owned_event_id, owned_room_id, serde::Raw,
88 };
89 use serde_json::{from_value as from_json_value, json};
90
91 use super::{StreamUpdateContent, StreamUpdateOperation, ToDeviceStreamUpdateEventContent};
92 use crate::{AnyToDeviceEvent, ToDeviceEvent};
93
94 #[test]
95 fn replace_update_round_trip() {
96 let content = ToDeviceStreamUpdateEventContent::new(
97 owned_room_id!("!room:example.org"),
98 owned_event_id!("$event:example.org"),
99 uint!(1),
100 StreamUpdateOperation::Replace(StreamUpdateContent::new("hello".to_owned())),
101 );
102
103 assert_to_canonical_json_eq!(
104 content,
105 json!({
106 "room_id": "!room:example.org",
107 "event_id": "$event:example.org",
108 "seq": 1,
109 "op": "replace",
110 "content": {
111 "body": "hello",
112 },
113 })
114 );
115
116 let deserialized: ToDeviceStreamUpdateEventContent =
117 Raw::new(&content).unwrap().deserialize().unwrap();
118 assert_eq!(deserialized.seq, uint!(1));
119 assert_matches!(deserialized.operation, StreamUpdateOperation::Replace(payload));
120 assert_eq!(payload.body, "hello");
121 }
122
123 #[test]
124 fn replace_update_seq_zero_round_trip() {
125 let content = ToDeviceStreamUpdateEventContent::new(
126 owned_room_id!("!room:example.org"),
127 owned_event_id!("$event:example.org"),
128 uint!(0),
129 StreamUpdateOperation::Replace(StreamUpdateContent::new("hello".to_owned())),
130 );
131
132 assert_to_canonical_json_eq!(
133 content,
134 json!({
135 "room_id": "!room:example.org",
136 "event_id": "$event:example.org",
137 "seq": 0,
138 "op": "replace",
139 "content": {
140 "body": "hello",
141 },
142 })
143 );
144
145 let deserialized: ToDeviceStreamUpdateEventContent =
146 Raw::new(&content).unwrap().deserialize().unwrap();
147 assert_eq!(deserialized.seq, uint!(0));
148 assert_matches!(deserialized.operation, StreamUpdateOperation::Replace(payload));
149 assert_eq!(payload.body, "hello");
150 }
151
152 #[test]
153 fn append_update_round_trip() {
154 let content = ToDeviceStreamUpdateEventContent::new(
155 owned_room_id!("!room:example.org"),
156 owned_event_id!("$event:example.org"),
157 uint!(2),
158 StreamUpdateOperation::Append(StreamUpdateContent::new(" world".to_owned())),
159 );
160
161 assert_to_canonical_json_eq!(
162 content,
163 json!({
164 "room_id": "!room:example.org",
165 "event_id": "$event:example.org",
166 "seq": 2,
167 "op": "append",
168 "content": {
169 "body": " world",
170 },
171 })
172 );
173 }
174
175 #[test]
176 fn any_to_device_update() {
177 let event = json!({
178 "sender": "@alice:example.org",
179 "type": "org.matrix.msc4471.stream.update",
180 "content": {
181 "room_id": "!room:example.org",
182 "event_id": "$event:example.org",
183 "seq": 1,
184 "op": "replace",
185 "content": {
186 "body": "hello",
187 },
188 },
189 });
190
191 let event = from_json_value::<AnyToDeviceEvent>(event).unwrap();
192 assert_matches!(event, AnyToDeviceEvent::StreamUpdate(ToDeviceEvent { content, .. }));
193 assert_matches!(content.operation, StreamUpdateOperation::Replace(payload));
194 assert_eq!(payload.body, "hello");
195 }
196
197 #[test]
198 fn any_to_device_update_stable_alias() {
199 let event = json!({
200 "sender": "@alice:example.org",
201 "type": "m.stream.update",
202 "content": {
203 "room_id": "!room:example.org",
204 "event_id": "$event:example.org",
205 "seq": 1,
206 "op": "replace",
207 "content": {
208 "body": "hello",
209 },
210 },
211 });
212
213 let event = from_json_value::<AnyToDeviceEvent>(event).unwrap();
214 assert_matches!(event, AnyToDeviceEvent::StreamUpdate(ToDeviceEvent { content, .. }));
215 assert_matches!(content.operation, StreamUpdateOperation::Replace(payload));
216 assert_eq!(payload.body, "hello");
217 }
218}