1use crate::ids::{DeviceId, EventId, RoomId, UserId};
22use serde::{Deserialize, Serialize};
23use std::collections::BTreeMap;
24
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33pub struct RawEvent {
34 pub event_id: EventId,
36 #[serde(rename = "type")]
39 pub event_type: String,
40 pub sender: UserId,
42 pub origin_server_ts: i64,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub state_key: Option<String>,
48 #[serde(default)]
50 pub content: serde_json::Value,
51 #[serde(default)]
53 pub unsigned: Unsigned,
54}
55
56impl RawEvent {
57 pub fn relates_to(&self) -> Option<RelatesTo> {
61 RelatesTo::from_content(&self.content)
62 }
63}
64
65#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
68pub struct Unsigned {
69 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub age: Option<i64>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub transaction_id: Option<String>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub redacted_because: Option<Box<RawEvent>>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub prev_content: Option<serde_json::Value>,
87 #[serde(rename = "m.relations", default, skip_serializing_if = "Option::is_none")]
94 pub relations: Option<serde_json::Value>,
95}
96
97#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
103pub struct StrippedStateEvent {
104 #[serde(rename = "type")]
106 pub event_type: String,
107 pub state_key: String,
109 pub sender: UserId,
111 #[serde(default)]
114 pub content: serde_json::Value,
115}
116
117#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
122pub struct BasicEvent {
123 #[serde(rename = "type")]
125 pub event_type: String,
126 #[serde(default)]
128 pub content: serde_json::Value,
129}
130
131#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
135pub struct ToDeviceEvent {
136 pub sender: UserId,
138 #[serde(rename = "type")]
140 pub event_type: String,
141 #[serde(default)]
143 pub content: serde_json::Value,
144}
145
146#[derive(Clone, Debug, PartialEq)]
155pub enum RelatesTo {
156 Annotation {
159 event_id: EventId,
161 key: String,
163 },
164 Replace {
170 event_id: EventId,
172 },
173 Thread {
175 event_id: EventId,
177 is_falling_back: bool,
181 in_reply_to: Option<InReplyTo>,
184 },
185 InReplyTo(InReplyTo),
189 Unknown(serde_json::Value),
192}
193
194impl RelatesTo {
195 pub fn from_content(content: &serde_json::Value) -> Option<Self> {
200 content.get("m.relates_to").map(RelatesTo::from_value)
201 }
202
203 fn from_value(value: &serde_json::Value) -> Self {
204 let rel_type = value.get("rel_type").and_then(serde_json::Value::as_str);
205 match rel_type {
206 Some("m.annotation") => {
207 let event_id = value.get("event_id").and_then(serde_json::Value::as_str).and_then(|s| EventId::parse(s).ok());
208 let key = value.get("key").and_then(serde_json::Value::as_str);
209 if let (Some(event_id), Some(key)) = (event_id, key) {
210 return RelatesTo::Annotation { event_id, key: key.to_string() };
211 }
212 }
213 Some("m.replace") => {
214 if let Some(event_id) =
215 value.get("event_id").and_then(serde_json::Value::as_str).and_then(|s| EventId::parse(s).ok())
216 {
217 return RelatesTo::Replace { event_id };
218 }
219 }
220 Some("m.thread") => {
221 if let Some(event_id) =
222 value.get("event_id").and_then(serde_json::Value::as_str).and_then(|s| EventId::parse(s).ok())
223 {
224 let is_falling_back =
225 value.get("is_falling_back").and_then(serde_json::Value::as_bool).unwrap_or(false);
226 let in_reply_to = value.get("m.in_reply_to").and_then(InReplyTo::from_value);
227 return RelatesTo::Thread { event_id, is_falling_back, in_reply_to };
228 }
229 }
230 None => {
231 if let Some(in_reply_to) = value.get("m.in_reply_to").and_then(InReplyTo::from_value) {
232 return RelatesTo::InReplyTo(in_reply_to);
233 }
234 }
235 Some(_) => {}
236 }
237 RelatesTo::Unknown(value.clone())
238 }
239}
240
241impl Serialize for RelatesTo {
242 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243 where
244 S: serde::Serializer,
245 {
246 let value = match self {
247 RelatesTo::Annotation { event_id, key } => {
248 serde_json::json!({ "rel_type": "m.annotation", "event_id": event_id.as_str(), "key": key })
249 }
250 RelatesTo::Replace { event_id } => {
251 serde_json::json!({ "rel_type": "m.replace", "event_id": event_id.as_str() })
252 }
253 RelatesTo::Thread { event_id, is_falling_back, in_reply_to } => {
254 let mut value = serde_json::json!({
255 "rel_type": "m.thread",
256 "event_id": event_id.as_str(),
257 "is_falling_back": is_falling_back,
258 });
259 if let Some(in_reply_to) = in_reply_to {
260 value["m.in_reply_to"] = serde_json::json!({ "event_id": in_reply_to.event_id.as_str() });
261 }
262 value
263 }
264 RelatesTo::InReplyTo(reply) => {
265 serde_json::json!({ "m.in_reply_to": { "event_id": reply.event_id.as_str() } })
266 }
267 RelatesTo::Unknown(value) => value.clone(),
268 };
269 value.serialize(serializer)
270 }
271}
272
273impl<'de> Deserialize<'de> for RelatesTo {
274 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
275 where
276 D: serde::Deserializer<'de>,
277 {
278 let value = serde_json::Value::deserialize(deserializer)?;
279 Ok(RelatesTo::from_value(&value))
280 }
281}
282
283#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
286pub struct InReplyTo {
287 pub event_id: EventId,
289}
290
291impl InReplyTo {
292 fn from_value(value: &serde_json::Value) -> Option<Self> {
293 let event_id = value.get("event_id")?.as_str()?;
294 EventId::parse(event_id).ok().map(|event_id| InReplyTo { event_id })
295 }
296}
297
298fn serialize_tagged<S, T>(serializer: S, tag_field: &str, tag_value: &str, inner: &T) -> Result<S::Ok, S::Error>
303where
304 S: serde::Serializer,
305 T: Serialize,
306{
307 let mut value = serde_json::to_value(inner).map_err(serde::ser::Error::custom)?;
308 if let serde_json::Value::Object(map) = &mut value {
309 map.insert(tag_field.to_string(), serde_json::Value::String(tag_value.to_string()));
310 }
311 value.serialize(serializer)
312}
313
314fn default_room_version() -> String {
319 "11".to_string()
320}
321
322fn default_true() -> bool {
323 true
324}
325
326fn default_power_level_50() -> i64 {
327 50
328}
329
330fn default_rotation_period_ms() -> i64 {
331 604_800_000
332}
333
334fn default_rotation_period_msgs() -> i64 {
335 100
336}
337
338#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
343pub struct RoomCreateContent {
344 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub creator: Option<UserId>,
347 #[serde(default = "default_room_version")]
349 pub room_version: String,
350 #[serde(rename = "m.federate", default = "default_true")]
354 pub federate: bool,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
357 pub predecessor: Option<RoomCreatePredecessor>,
358}
359
360#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
362pub struct RoomCreatePredecessor {
363 pub room_id: RoomId,
365 pub event_id: EventId,
367}
368
369#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
371#[serde(rename_all = "lowercase")]
372pub enum Membership {
373 Invite,
375 Join,
377 Knock,
379 Leave,
381 Ban,
383 #[serde(other)]
386 Unknown,
387}
388
389#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
391pub struct RoomMemberContent {
392 pub membership: Membership,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub displayname: Option<String>,
397 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub is_direct: Option<bool>,
400}
401
402#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
406pub struct RoomPowerLevelsNotifications {
407 #[serde(default = "default_power_level_50")]
409 pub room: i64,
410}
411
412impl Default for RoomPowerLevelsNotifications {
413 fn default() -> Self {
414 Self { room: 50 }
415 }
416}
417
418#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
422pub struct RoomPowerLevelsContent {
423 #[serde(default = "default_power_level_50")]
425 pub ban: i64,
426 #[serde(default)]
428 pub events: BTreeMap<String, i64>,
429 #[serde(default)]
432 pub events_default: i64,
433 #[serde(default = "default_power_level_50")]
435 pub invite: i64,
436 #[serde(default = "default_power_level_50")]
438 pub kick: i64,
439 #[serde(default = "default_power_level_50")]
441 pub redact: i64,
442 #[serde(default = "default_power_level_50")]
445 pub state_default: i64,
446 #[serde(default)]
448 pub users: BTreeMap<UserId, i64>,
449 #[serde(default)]
451 pub users_default: i64,
452 #[serde(default)]
454 pub notifications: RoomPowerLevelsNotifications,
455}
456
457#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
459#[serde(rename_all = "snake_case")]
460pub enum JoinRule {
461 Public,
463 Invite,
465 Knock,
467 Private,
470 Restricted,
473 KnockRestricted,
476 #[serde(other)]
478 Unknown,
479}
480
481#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
484pub struct JoinRuleAllow {
485 #[serde(rename = "type")]
488 pub allow_type: String,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
492 pub room_id: Option<RoomId>,
493}
494
495#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
497pub struct RoomJoinRulesContent {
498 pub join_rule: JoinRule,
500 #[serde(default, skip_serializing_if = "Vec::is_empty")]
503 pub allow: Vec<JoinRuleAllow>,
504}
505
506#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
508#[serde(rename_all = "snake_case")]
509pub enum HistoryVisibility {
510 Invited,
512 Joined,
514 Shared,
517 WorldReadable,
519 #[serde(other)]
521 Unknown,
522}
523
524#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
526pub struct RoomHistoryVisibilityContent {
527 pub history_visibility: HistoryVisibility,
529}
530
531#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
533pub struct RoomNameContent {
534 pub name: String,
536}
537
538#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
540pub struct RoomTopicContent {
541 pub topic: String,
543}
544
545#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
549pub struct RoomEncryptionContent {
550 pub algorithm: String,
553 #[serde(default = "default_rotation_period_ms")]
556 pub rotation_period_ms: i64,
557 #[serde(default = "default_rotation_period_msgs")]
560 pub rotation_period_msgs: i64,
561}
562
563#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
565pub struct RoomPinnedEventsContent {
566 pub pinned: Vec<EventId>,
568}
569
570#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
577pub struct TextLikeMessageContent {
578 pub body: String,
580 #[serde(default, skip_serializing_if = "Option::is_none")]
583 pub format: Option<String>,
584 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub formatted_body: Option<String>,
587}
588
589#[derive(Clone, Debug, PartialEq)]
593pub enum RoomMessageContent {
594 Text(TextLikeMessageContent),
596 Notice(TextLikeMessageContent),
599 Emote(TextLikeMessageContent),
601 Unknown(serde_json::Value),
604}
605
606impl Serialize for RoomMessageContent {
607 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
608 where
609 S: serde::Serializer,
610 {
611 match self {
612 RoomMessageContent::Text(inner) => serialize_tagged(serializer, "msgtype", "m.text", inner),
613 RoomMessageContent::Notice(inner) => serialize_tagged(serializer, "msgtype", "m.notice", inner),
614 RoomMessageContent::Emote(inner) => serialize_tagged(serializer, "msgtype", "m.emote", inner),
615 RoomMessageContent::Unknown(value) => value.serialize(serializer),
616 }
617 }
618}
619
620impl<'de> Deserialize<'de> for RoomMessageContent {
621 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
622 where
623 D: serde::Deserializer<'de>,
624 {
625 let value = serde_json::Value::deserialize(deserializer)?;
626 let msgtype = value.get("msgtype").and_then(serde_json::Value::as_str).map(str::to_owned);
627 match msgtype.as_deref() {
628 Some("m.text") => serde_json::from_value(value).map(RoomMessageContent::Text).map_err(serde::de::Error::custom),
629 Some("m.notice") => {
630 serde_json::from_value(value).map(RoomMessageContent::Notice).map_err(serde::de::Error::custom)
631 }
632 Some("m.emote") => {
633 serde_json::from_value(value).map(RoomMessageContent::Emote).map_err(serde::de::Error::custom)
634 }
635 _ => Ok(RoomMessageContent::Unknown(value)),
636 }
637 }
638}
639
640#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
648pub struct MegolmEncryptedContent {
649 pub ciphertext: String,
651 pub session_id: String,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub sender_key: Option<String>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
659 pub device_id: Option<DeviceId>,
660 #[serde(rename = "m.relates_to", default, skip_serializing_if = "Option::is_none")]
667 pub relates_to: Option<RelatesTo>,
668}
669
670#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
673pub struct OlmCiphertextInfo {
674 #[serde(rename = "type")]
676 pub message_type: u8,
677 pub body: String,
679}
680
681#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
686pub struct OlmEncryptedContent {
687 pub sender_key: String,
689 pub ciphertext: BTreeMap<String, OlmCiphertextInfo>,
691}
692
693#[derive(Clone, Debug, PartialEq)]
697pub enum RoomEncryptedContent {
698 Megolm(MegolmEncryptedContent),
700 Olm(OlmEncryptedContent),
702 Unknown(serde_json::Value),
704}
705
706impl Serialize for RoomEncryptedContent {
707 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
708 where
709 S: serde::Serializer,
710 {
711 match self {
712 RoomEncryptedContent::Megolm(inner) => {
713 serialize_tagged(serializer, "algorithm", "m.megolm.v1.aes-sha2", inner)
714 }
715 RoomEncryptedContent::Olm(inner) => {
716 serialize_tagged(serializer, "algorithm", "m.olm.v1.curve25519-aes-sha2", inner)
717 }
718 RoomEncryptedContent::Unknown(value) => value.serialize(serializer),
719 }
720 }
721}
722
723impl<'de> Deserialize<'de> for RoomEncryptedContent {
724 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
725 where
726 D: serde::Deserializer<'de>,
727 {
728 let value = serde_json::Value::deserialize(deserializer)?;
729 let algorithm = value.get("algorithm").and_then(serde_json::Value::as_str).map(str::to_owned);
730 match algorithm.as_deref() {
731 Some("m.megolm.v1.aes-sha2") => {
732 serde_json::from_value(value).map(RoomEncryptedContent::Megolm).map_err(serde::de::Error::custom)
733 }
734 Some("m.olm.v1.curve25519-aes-sha2") => {
735 serde_json::from_value(value).map(RoomEncryptedContent::Olm).map_err(serde::de::Error::custom)
736 }
737 _ => Ok(RoomEncryptedContent::Unknown(value)),
738 }
739 }
740}
741
742#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
744pub struct ReactionContent {
745 #[serde(rename = "m.relates_to")]
747 pub relates_to: RelatesTo,
748}
749
750#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
753pub struct RedactionContent {
754 pub redacts: EventId,
756 #[serde(default, skip_serializing_if = "Option::is_none")]
758 pub reason: Option<String>,
759}
760
761#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
768pub struct RoomKeyContent {
769 pub algorithm: String,
771 pub room_id: RoomId,
773 pub session_id: String,
775 pub session_key: String,
777}
778
779#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
783pub struct ForwardedRoomKeyContent {
784 pub algorithm: String,
786 pub room_id: RoomId,
788 pub session_id: String,
790 pub session_key: String,
792 pub sender_key: String,
794 #[serde(default, skip_serializing_if = "Option::is_none")]
796 pub sender_claimed_ed25519_key: Option<String>,
797 #[serde(default)]
800 pub forwarding_curve25519_key_chain: Vec<String>,
801}
802
803#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
805#[serde(rename_all = "snake_case")]
806pub enum RoomKeyRequestAction {
807 Request,
809 RequestCancellation,
811 #[serde(other)]
813 Unknown,
814}
815
816#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
819pub struct RoomKeyRequestBody {
820 pub algorithm: String,
822 pub room_id: RoomId,
824 pub session_id: String,
826 pub sender_key: String,
828}
829
830#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
836pub struct RoomKeyRequestContent {
837 pub action: RoomKeyRequestAction,
839 #[serde(default, skip_serializing_if = "Option::is_none")]
841 pub body: Option<RoomKeyRequestBody>,
842 pub request_id: String,
845 pub requesting_device_id: DeviceId,
847}
848
849#[derive(Clone, Debug, PartialEq, Eq)]
854pub enum WithheldCode {
855 Blacklisted,
857 Unverified,
859 Unauthorised,
861 Unavailable,
863 NoOlm,
865 Unknown(String),
867}
868
869impl WithheldCode {
870 pub fn as_str(&self) -> &str {
872 match self {
873 WithheldCode::Blacklisted => "m.blacklisted",
874 WithheldCode::Unverified => "m.unverified",
875 WithheldCode::Unauthorised => "m.unauthorised",
876 WithheldCode::Unavailable => "m.unavailable",
877 WithheldCode::NoOlm => "m.no_olm",
878 WithheldCode::Unknown(raw) => raw.as_str(),
879 }
880 }
881}
882
883impl Serialize for WithheldCode {
884 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
885 where
886 S: serde::Serializer,
887 {
888 serializer.serialize_str(self.as_str())
889 }
890}
891
892impl<'de> Deserialize<'de> for WithheldCode {
893 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
894 where
895 D: serde::Deserializer<'de>,
896 {
897 let raw = String::deserialize(deserializer)?;
898 Ok(match raw.as_str() {
899 "m.blacklisted" => WithheldCode::Blacklisted,
900 "m.unverified" => WithheldCode::Unverified,
901 "m.unauthorised" => WithheldCode::Unauthorised,
902 "m.unavailable" => WithheldCode::Unavailable,
903 "m.no_olm" => WithheldCode::NoOlm,
904 _ => WithheldCode::Unknown(raw),
905 })
906 }
907}
908
909#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
911pub struct RoomKeyWithheldContent {
912 pub algorithm: String,
914 #[serde(default, skip_serializing_if = "Option::is_none")]
916 pub room_id: Option<RoomId>,
917 #[serde(default, skip_serializing_if = "Option::is_none")]
919 pub session_id: Option<String>,
920 pub sender_key: String,
922 pub code: WithheldCode,
924 #[serde(default, skip_serializing_if = "Option::is_none")]
926 pub reason: Option<String>,
927}
928
929#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
932pub struct DummyContent {}
933
934#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
940#[serde(transparent)]
941pub struct DirectContent(pub BTreeMap<UserId, Vec<RoomId>>);
942
943#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
945pub struct TagInfo {
946 #[serde(default, skip_serializing_if = "Option::is_none")]
949 pub order: Option<f64>,
950}
951
952#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
954pub struct TagContent {
955 #[serde(default)]
957 pub tags: BTreeMap<String, TagInfo>,
958}
959
960#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
964pub struct FullyReadContent {
965 pub event_id: EventId,
967}
968
969#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
976pub struct TypingContent {
977 #[serde(default)]
979 pub user_ids: Vec<UserId>,
980}
981
982#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
984pub struct ReceiptEntry {
985 pub ts: i64,
987 #[serde(default, skip_serializing_if = "Option::is_none")]
990 pub thread_id: Option<String>,
991}
992
993#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
997#[serde(transparent)]
998pub struct ReceiptContent(pub BTreeMap<EventId, BTreeMap<String, BTreeMap<UserId, ReceiptEntry>>>);
999
1000#[cfg(test)]
1001mod tests {
1002 use super::*;
1003
1004 #[test]
1005 fn room_create_content_decodes_without_a_creator_field() {
1006 let v11: RoomCreateContent = serde_json::from_value(serde_json::json!({ "room_version": "11" })).expect("v11 create content decodes");
1008 assert_eq!(v11.creator, None);
1009 assert_eq!(v11.room_version, "11");
1010 let v10: RoomCreateContent =
1011 serde_json::from_value(serde_json::json!({ "creator": "@a:example.org", "room_version": "10" })).expect("v10 create content decodes");
1012 assert_eq!(v10.creator.as_ref().map(UserId::as_str), Some("@a:example.org"));
1013 }
1014
1015 #[test]
1016 fn parses_relates_to_annotation_replace_thread() {
1017 let annotation = serde_json::json!({
1018 "m.relates_to": { "rel_type": "m.annotation", "event_id": "$target:example.org", "key": "👍" }
1019 });
1020 match RelatesTo::from_content(&annotation) {
1021 Some(RelatesTo::Annotation { event_id, key }) => {
1022 assert_eq!(event_id.as_str(), "$target:example.org");
1023 assert_eq!(key, "👍");
1024 }
1025 other => panic!("expected Annotation, got {other:?}"),
1026 }
1027
1028 let replace = serde_json::json!({
1029 "m.relates_to": { "rel_type": "m.replace", "event_id": "$original:example.org" },
1030 "m.new_content": { "msgtype": "m.text", "body": "edited" }
1031 });
1032 match RelatesTo::from_content(&replace) {
1033 Some(RelatesTo::Replace { event_id }) => assert_eq!(event_id.as_str(), "$original:example.org"),
1034 other => panic!("expected Replace, got {other:?}"),
1035 }
1036
1037 let thread = serde_json::json!({
1038 "m.relates_to": {
1039 "rel_type": "m.thread",
1040 "event_id": "$root:example.org",
1041 "is_falling_back": true,
1042 "m.in_reply_to": { "event_id": "$latest:example.org" }
1043 }
1044 });
1045 match RelatesTo::from_content(&thread) {
1046 Some(RelatesTo::Thread { event_id, is_falling_back, in_reply_to }) => {
1047 assert_eq!(event_id.as_str(), "$root:example.org");
1048 assert!(is_falling_back);
1049 assert_eq!(in_reply_to.expect("in_reply_to present").event_id.as_str(), "$latest:example.org");
1050 }
1051 other => panic!("expected Thread, got {other:?}"),
1052 }
1053
1054 let legacy_reply = serde_json::json!({
1055 "m.relates_to": { "m.in_reply_to": { "event_id": "$legacy:example.org" } }
1056 });
1057 match RelatesTo::from_content(&legacy_reply) {
1058 Some(RelatesTo::InReplyTo(reply)) => assert_eq!(reply.event_id.as_str(), "$legacy:example.org"),
1059 other => panic!("expected InReplyTo, got {other:?}"),
1060 }
1061
1062 let no_relation = serde_json::json!({ "msgtype": "m.text", "body": "plain" });
1063 assert_eq!(RelatesTo::from_content(&no_relation), None);
1064 }
1065
1066 #[test]
1067 fn unrecognized_relation_shape_becomes_unknown_not_a_parse_failure() {
1068 let content = serde_json::json!({ "m.relates_to": { "rel_type": "org.example.future_relation", "foo": "bar" } });
1069 match RelatesTo::from_content(&content) {
1070 Some(RelatesTo::Unknown(value)) => {
1071 assert_eq!(value["rel_type"], "org.example.future_relation");
1072 }
1073 other => panic!("expected Unknown, got {other:?}"),
1074 }
1075 }
1076
1077 #[test]
1078 fn unknown_event_type_is_kept_raw() {
1079 let json = serde_json::json!({
1080 "event_id": "$abc:example.org",
1081 "type": "org.example.made_up_event",
1082 "sender": "@alice:example.org",
1083 "origin_server_ts": 1_700_000_000_000i64,
1084 "content": { "whatever": ["this", "is"], "n": 3 },
1085 "unsigned": { "age": 12 }
1086 });
1087 let event: RawEvent = serde_json::from_value(json).expect("unrecognized type still parses");
1088 assert_eq!(event.event_type, "org.example.made_up_event");
1089 assert_eq!(event.content["whatever"][0], "this");
1090 assert_eq!(event.content["n"], 3);
1091 assert_eq!(event.unsigned.age, Some(12));
1092 assert_eq!(event.state_key, None);
1093 }
1094
1095 #[test]
1096 fn redacted_because_nests_the_redaction_event() {
1097 let json = serde_json::json!({
1098 "event_id": "$abc:example.org",
1099 "type": "m.room.message",
1100 "sender": "@alice:example.org",
1101 "origin_server_ts": 1,
1102 "content": {},
1103 "unsigned": {
1104 "redacted_because": {
1105 "event_id": "$redaction:example.org",
1106 "type": "m.room.redaction",
1107 "sender": "@mod:example.org",
1108 "origin_server_ts": 2,
1109 "content": { "redacts": "$abc:example.org" }
1110 }
1111 }
1112 });
1113 let event: RawEvent = serde_json::from_value(json).expect("valid event");
1114 let redaction = event.unsigned.redacted_because.expect("redacted_because present");
1115 assert_eq!(redaction.event_type, "m.room.redaction");
1116 let redaction_content: RedactionContent =
1117 serde_json::from_value(redaction.content.clone()).expect("valid redaction content");
1118 assert_eq!(redaction_content.redacts.as_str(), "$abc:example.org");
1119 }
1120
1121 #[test]
1122 fn encrypted_content_parses_megolm_and_olm_shapes() {
1123 let megolm = serde_json::json!({
1124 "algorithm": "m.megolm.v1.aes-sha2",
1125 "ciphertext": "cipher-bytes-b64",
1126 "session_id": "session-1",
1127 "sender_key": "sender-curve25519",
1128 "device_id": "DEVICE1"
1129 });
1130 match serde_json::from_value::<RoomEncryptedContent>(megolm).expect("valid megolm content") {
1131 RoomEncryptedContent::Megolm(inner) => {
1132 assert_eq!(inner.ciphertext, "cipher-bytes-b64");
1133 assert_eq!(inner.session_id, "session-1");
1134 assert_eq!(inner.sender_key.as_deref(), Some("sender-curve25519"));
1135 assert_eq!(inner.device_id.map(|d| d.as_str().to_string()), Some("DEVICE1".to_string()));
1136 }
1137 other => panic!("expected Megolm, got {other:?}"),
1138 }
1139
1140 let olm = serde_json::json!({
1141 "algorithm": "m.olm.v1.curve25519-aes-sha2",
1142 "sender_key": "sender-curve25519",
1143 "ciphertext": {
1144 "recipient-curve25519": { "type": 0, "body": "prekey-message-b64" }
1145 }
1146 });
1147 match serde_json::from_value::<RoomEncryptedContent>(olm).expect("valid olm content") {
1148 RoomEncryptedContent::Olm(inner) => {
1149 assert_eq!(inner.sender_key, "sender-curve25519");
1150 let entry = inner.ciphertext.get("recipient-curve25519").expect("recipient entry present");
1151 assert_eq!(entry.message_type, 0);
1152 assert_eq!(entry.body, "prekey-message-b64");
1153 }
1154 other => panic!("expected Olm, got {other:?}"),
1155 }
1156
1157 let unknown = serde_json::json!({ "algorithm": "org.example.future-algorithm", "opaque": true });
1158 match serde_json::from_value::<RoomEncryptedContent>(unknown).expect("unrecognized algorithm still parses") {
1159 RoomEncryptedContent::Unknown(value) => assert_eq!(value["opaque"], true),
1160 other => panic!("expected Unknown, got {other:?}"),
1161 }
1162 }
1163
1164 #[test]
1165 fn room_message_unknown_msgtype_is_kept_raw() {
1166 let image = serde_json::json!({ "msgtype": "m.image", "body": "cat.png", "url": "mxc://example.org/abc" });
1167 match serde_json::from_value::<RoomMessageContent>(image).expect("unrecognized msgtype still parses") {
1168 RoomMessageContent::Unknown(value) => assert_eq!(value["url"], "mxc://example.org/abc"),
1169 other => panic!("expected Unknown, got {other:?}"),
1170 }
1171
1172 let text = serde_json::json!({ "msgtype": "m.text", "body": "hi", "formatted_body": "<b>hi</b>", "format": "org.matrix.custom.html" });
1173 match serde_json::from_value::<RoomMessageContent>(text).expect("valid text content") {
1174 RoomMessageContent::Text(inner) => {
1175 assert_eq!(inner.body, "hi");
1176 assert_eq!(inner.formatted_body.as_deref(), Some("<b>hi</b>"));
1177 }
1178 other => panic!("expected Text, got {other:?}"),
1179 }
1180 }
1181
1182 #[test]
1183 fn power_levels_defaults_apply() {
1184 let content: RoomPowerLevelsContent = serde_json::from_value(serde_json::json!({})).expect("empty object parses");
1185 assert_eq!(content.ban, 50);
1186 assert_eq!(content.invite, 50);
1187 assert_eq!(content.kick, 50);
1188 assert_eq!(content.redact, 50);
1189 assert_eq!(content.state_default, 50);
1190 assert_eq!(content.events_default, 0);
1191 assert_eq!(content.users_default, 0);
1192 assert_eq!(content.notifications.room, 50);
1193 assert!(content.events.is_empty());
1194 assert!(content.users.is_empty());
1195 }
1196
1197 #[test]
1198 fn power_levels_overrides_the_spec_defaults() {
1199 let content: RoomPowerLevelsContent = serde_json::from_value(serde_json::json!({
1200 "ban": 100,
1201 "users": { "@admin:example.org": 100 },
1202 "events": { "m.room.name": 50 }
1203 }))
1204 .expect("valid power levels");
1205 assert_eq!(content.ban, 100);
1206 let admin = UserId::parse("@admin:example.org").expect("valid user id");
1207 assert_eq!(content.users.get(&admin), Some(&100));
1208 assert_eq!(content.events.get("m.room.name"), Some(&50));
1209 assert_eq!(content.invite, 50);
1211 }
1212
1213 #[test]
1214 fn receipt_content_parses_nested_map() {
1215 let json = serde_json::json!({
1216 "$event1:example.org": {
1217 "m.read": {
1218 "@alice:example.org": { "ts": 1_436_451_550_453i64, "thread_id": "main" }
1219 },
1220 "m.read.private": {
1221 "@alice:example.org": { "ts": 1_436_451_551_000i64 }
1222 }
1223 }
1224 });
1225 let receipts: ReceiptContent = serde_json::from_value(json).expect("valid receipt content");
1226 let event_id = EventId::parse("$event1:example.org").expect("valid event id");
1227 let alice = UserId::parse("@alice:example.org").expect("valid user id");
1228 let by_type = receipts.0.get(&event_id).expect("event present");
1229 let read = by_type.get("m.read").expect("m.read present").get(&alice).expect("alice present");
1230 assert_eq!(read.ts, 1_436_451_550_453);
1231 assert_eq!(read.thread_id.as_deref(), Some("main"));
1232 let read_private =
1233 by_type.get("m.read.private").expect("m.read.private present").get(&alice).expect("alice present");
1234 assert_eq!(read_private.ts, 1_436_451_551_000);
1235 assert_eq!(read_private.thread_id, None);
1236 }
1237
1238 #[test]
1239 fn withheld_codes_parse_including_unknown() {
1240 let cases = [
1241 ("m.blacklisted", WithheldCode::Blacklisted),
1242 ("m.unverified", WithheldCode::Unverified),
1243 ("m.unauthorised", WithheldCode::Unauthorised),
1244 ("m.unavailable", WithheldCode::Unavailable),
1245 ("m.no_olm", WithheldCode::NoOlm),
1246 ];
1247 for (wire, expected) in cases {
1248 let parsed: WithheldCode = serde_json::from_value(serde_json::json!(wire)).expect("known code parses");
1249 assert_eq!(parsed, expected);
1250 assert_eq!(parsed.as_str(), wire);
1251 }
1252
1253 let unknown: WithheldCode =
1254 serde_json::from_value(serde_json::json!("m.some_future_code")).expect("unknown code still parses");
1255 assert_eq!(unknown, WithheldCode::Unknown("m.some_future_code".to_string()));
1256 assert_eq!(unknown.as_str(), "m.some_future_code");
1257
1258 let content: RoomKeyWithheldContent = serde_json::from_value(serde_json::json!({
1259 "algorithm": "m.megolm.v1.aes-sha2",
1260 "room_id": "!room:example.org",
1261 "session_id": "session-1",
1262 "sender_key": "sender-curve25519",
1263 "code": "m.unverified",
1264 "reason": "device not verified"
1265 }))
1266 .expect("valid withheld content");
1267 assert_eq!(content.code, WithheldCode::Unverified);
1268 assert_eq!(content.reason.as_deref(), Some("device not verified"));
1269 }
1270
1271}