ruma_federation_api/
membership.rs

1//! Room membership endpoints.
2
3use ruma_common::serde::{from_raw_json_value, Raw};
4use ruma_events::AnyStrippedStateEvent;
5use serde::{de, Deserialize, Serialize};
6use serde_json::value::RawValue as RawJsonValue;
7
8pub mod create_invite;
9pub mod create_join_event;
10pub mod create_knock_event;
11pub mod create_leave_event;
12pub mod prepare_join_event;
13pub mod prepare_knock_event;
14pub mod prepare_leave_event;
15
16/// Possible event formats that may appear in stripped state.
17#[derive(Clone, Debug, Serialize)]
18#[serde(untagged)]
19#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
20pub enum RawStrippedState {
21    /// A stripped state event.
22    #[deprecated = "Since Matrix 1.16, stripped state events are required to be sent over federation as full PDUs.\
23                    It is still possible to receive this variant for backwards compatibility."]
24    Stripped(Raw<AnyStrippedStateEvent>),
25
26    /// A full federation PDU.
27    Pdu(Box<RawJsonValue>),
28}
29
30impl<'de> Deserialize<'de> for RawStrippedState {
31    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
32    where
33        D: ::serde::Deserializer<'de>,
34    {
35        #[derive(Deserialize)]
36        struct PotentialPduDeHelper {
37            auth_events: Option<de::IgnoredAny>,
38            prev_events: Option<de::IgnoredAny>,
39            signatures: Option<de::IgnoredAny>,
40            hashes: Option<de::IgnoredAny>,
41        }
42
43        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
44
45        let PotentialPduDeHelper { auth_events, prev_events, signatures, hashes } =
46            from_raw_json_value(&json)?;
47
48        if auth_events.is_some()
49            && prev_events.is_some()
50            && signatures.is_some()
51            && hashes.is_some()
52        {
53            Ok(Self::Pdu(json))
54        } else {
55            #[allow(deprecated)]
56            Ok(Self::Stripped(Raw::from_json(json)))
57        }
58    }
59}
60
61impl From<Raw<AnyStrippedStateEvent>> for RawStrippedState {
62    fn from(value: Raw<AnyStrippedStateEvent>) -> Self {
63        #[allow(deprecated)]
64        Self::Stripped(value)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use assert_matches2::assert_matches;
71    use ruma_common::{serde::Raw, user_id};
72    use ruma_events::{room::member::MembershipState, AnyStrippedStateEvent};
73    use serde_json::{from_value as from_json_value, json};
74
75    use super::RawStrippedState;
76
77    #[test]
78    #[allow(deprecated)]
79    fn deserialize_stripped_state() {
80        let user_id = user_id!("@patrick:localhost");
81        let content = json!({
82            "membership": "join",
83        });
84
85        // Stripped format.
86        let stripped_event_json = json!({
87            "content": content,
88            "sender": user_id,
89            "state_key": user_id,
90            "type": "m.room.member",
91        });
92        assert_matches!(
93            from_json_value::<RawStrippedState>(stripped_event_json).unwrap(),
94            RawStrippedState::Stripped(raw_stripped_event)
95        );
96        assert_matches!(
97            raw_stripped_event.deserialize().unwrap(),
98            AnyStrippedStateEvent::RoomMember(stripped_member_event)
99        );
100        assert_eq!(stripped_member_event.sender, user_id);
101        assert_eq!(stripped_member_event.state_key, user_id);
102        assert_eq!(stripped_member_event.content.membership, MembershipState::Join);
103
104        // PDU format
105        let pdu_event_json = json!({
106            "auth_events": [
107                "$one",
108                "$two",
109                "$three"
110            ],
111            "content": content,
112            "depth": 10,
113            "hashes": {
114                "sha256": "thisisahash"
115            },
116            "origin_server_ts": 1_000_000,
117            "prev_events": [
118                "$one",
119                "$two",
120                "$three"
121            ],
122            "room_id": "!room:localhost",
123            "sender": user_id,
124            "signatures": {
125                "localhost": {
126                    "ed25519:1": "thisisakey"
127                }
128            },
129            "state_key": user_id,
130            "type": "m.room.member",
131        });
132        assert_matches!(
133            from_json_value::<RawStrippedState>(pdu_event_json).unwrap(),
134            RawStrippedState::Pdu(_pdu_member_event)
135        );
136    }
137
138    #[test]
139    fn serialize_stripped_state() {
140        let user_id = user_id!("@patrick:localhost");
141        let content = json!({
142            "membership": "join",
143        });
144
145        // Stripped format.
146        let stripped_event_json = json!({
147            "content": content,
148            "sender": user_id,
149            "state_key": user_id,
150            "type": "m.room.member",
151        });
152        let raw_stripped_event =
153            Raw::new(&stripped_event_json).unwrap().cast_unchecked::<AnyStrippedStateEvent>();
154        let stripped_state = RawStrippedState::from(raw_stripped_event);
155
156        let stripped_event_json = serde_json::to_string(&stripped_state).unwrap();
157        assert_eq!(
158            stripped_event_json,
159            r#"{"content":{"membership":"join"},"sender":"@patrick:localhost","state_key":"@patrick:localhost","type":"m.room.member"}"#
160        );
161    }
162}