Skip to main content

tuwunel_service/rooms/state_res/events/
member.rs

1//! Types to deserialize `m.room.member` events.
2
3use std::ops::Deref;
4
5use ruma::{
6	CanonicalJsonObject, OwnedUserId, events::room::member::MembershipState,
7	serde::from_raw_json_value, signatures::to_canonical_json_string_for_signing,
8};
9use serde::Deserialize;
10use serde_json::value::RawValue as RawJsonValue;
11use tuwunel_core::{Err, Error, Result, err, matrix::Event};
12
13/// A helper type for an [`Event`] of type `m.room.member`.
14///
15/// This is a type that deserializes each field lazily, as requested.
16#[derive(Debug, Clone)]
17pub struct RoomMemberEvent<E: Event>(E);
18
19impl<E: Event> RoomMemberEvent<E> {
20	/// Construct a new `RoomMemberEvent` around the given event.
21	#[inline]
22	pub fn new(event: E) -> Self { Self(event) }
23
24	/// The membership of the user.
25	#[inline]
26	pub fn membership(&self) -> Result<MembershipState> {
27		RoomMemberEventContent(self.content()).membership()
28	}
29
30	/// If this is a `join` event, the ID of a user on the homeserver that
31	/// authorized it.
32	#[inline]
33	pub fn join_authorised_via_users_server(&self) -> Result<Option<OwnedUserId>> {
34		RoomMemberEventContent(self.content()).join_authorised_via_users_server()
35	}
36
37	/// If this is an `invite` event, details about the third-party invite that
38	/// resulted in this event.
39	#[inline]
40	pub fn third_party_invite(&self) -> Result<Option<ThirdPartyInvite>> {
41		RoomMemberEventContent(self.content()).third_party_invite()
42	}
43}
44
45impl<E: Event> Deref for RoomMemberEvent<E> {
46	type Target = E;
47
48	#[inline]
49	fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52/// Helper trait for `Option<RoomMemberEvent<E>>`.
53pub(crate) trait RoomMemberEventResultExt {
54	/// The membership of the user.
55	///
56	/// Defaults to `leave` if there is no `m.room.member` event.
57	fn membership(self) -> Result<MembershipState>;
58}
59
60impl<E: Event> RoomMemberEventResultExt for Result<RoomMemberEvent<E>> {
61	fn membership(self) -> Result<MembershipState> {
62		match self {
63			| Ok(event) => event.membership(),
64			| Err(error) if error.is_not_found() => Ok(MembershipState::Leave),
65			| Err(error) => Err(error),
66		}
67	}
68}
69
70/// A helper type for the raw JSON content of an event of type `m.room.member`.
71pub struct RoomMemberEventContent<'a>(&'a RawJsonValue);
72
73impl<'a> RoomMemberEventContent<'a> {
74	/// Construct a new `RoomMemberEventContent` around the given raw JSON
75	/// content.
76	#[inline]
77	#[must_use]
78	pub fn new(content: &'a RawJsonValue) -> Self { Self(content) }
79}
80
81impl RoomMemberEventContent<'_> {
82	/// The membership of the user.
83	pub fn membership(&self) -> Result<MembershipState> {
84		#[derive(Deserialize)]
85		struct RoomMemberContentMembership {
86			membership: MembershipState,
87		}
88
89		let content: RoomMemberContentMembership =
90			from_raw_json_value(self.0).map_err(|err: Error| {
91				err!(Request(InvalidParam(
92					"missing or invalid `membership` field in `m.room.member` event: {err}"
93				)))
94			})?;
95
96		Ok(content.membership)
97	}
98
99	/// If this is a `join` event, the ID of a user on the homeserver that
100	/// authorized it.
101	pub fn join_authorised_via_users_server(&self) -> Result<Option<OwnedUserId>> {
102		#[derive(Deserialize)]
103		struct RoomMemberContentJoinAuthorizedViaUsersServer {
104			join_authorised_via_users_server: Option<OwnedUserId>,
105		}
106
107		let content: RoomMemberContentJoinAuthorizedViaUsersServer = from_raw_json_value(self.0)
108			.map_err(|err: Error| {
109				err!(Request(InvalidParam(
110					"invalid `join_authorised_via_users_server` field in `m.room.member` event: \
111					 {err}"
112				)))
113			})?;
114
115		Ok(content.join_authorised_via_users_server)
116	}
117
118	/// If this is an `invite` event, details about the third-party invite that
119	/// resulted in this event.
120	pub fn third_party_invite(&self) -> Result<Option<ThirdPartyInvite>> {
121		#[derive(Deserialize)]
122		struct RoomMemberContentThirdPartyInvite {
123			third_party_invite: Option<ThirdPartyInvite>,
124		}
125
126		let content: RoomMemberContentThirdPartyInvite =
127			from_raw_json_value(self.0).map_err(|err: Error| {
128				err!(Request(InvalidParam(
129					"invalid `third_party_invite` field in `m.room.member` event: {err}"
130				)))
131			})?;
132
133		Ok(content.third_party_invite)
134	}
135}
136
137/// Details about a third-party invite.
138#[derive(Deserialize)]
139pub struct ThirdPartyInvite {
140	/// Signed details about the third-party invite.
141	signed: CanonicalJsonObject,
142}
143
144impl ThirdPartyInvite {
145	/// The unique identifier for the third-party invite.
146	pub fn token(&self) -> Result<&str> {
147		let Some(token_value) = self.signed.get("token") else {
148			return Err!(Request(InvalidParam(
149				"missing `token` field in `third_party_invite.signed` of `m.room.member` event"
150			)));
151		};
152
153		token_value.as_str().ok_or_else(|| {
154			err!(Request(InvalidParam(
155				"unexpected format of `token` field in `third_party_invite.signed` of \
156				 `m.room.member` event: expected string, got {token_value:?}"
157			)))
158		})
159	}
160
161	/// The Matrix ID of the user that was invited.
162	pub fn mxid(&self) -> Result<&str> {
163		let Some(mxid_value) = self.signed.get("mxid") else {
164			return Err!(Request(InvalidParam(
165				"missing `mxid` field in `third_party_invite.signed` of `m.room.member` event"
166			)));
167		};
168
169		mxid_value.as_str().ok_or_else(|| {
170			err!(Request(InvalidParam(
171				"unexpected format of `mxid` field in `third_party_invite.signed` of \
172				 `m.room.member` event: expected string, got {mxid_value:?}"
173			)))
174		})
175	}
176
177	/// The signatures of the event.
178	pub fn signatures(&self) -> Result<&CanonicalJsonObject> {
179		let Some(signatures_value) = self.signed.get("signatures") else {
180			return Err!(Request(InvalidParam(
181				"missing `signatures` field in `third_party_invite.signed` of `m.room.member` \
182				 event"
183			)));
184		};
185
186		signatures_value.as_object().ok_or_else(|| {
187			err!(Request(InvalidParam(
188				"unexpected format of `signatures` field in `third_party_invite.signed` of \
189				 `m.room.member` event: expected object, got {signatures_value:?}"
190			)))
191		})
192	}
193
194	/// The `signed` object as canonical JSON string to verify the signatures.
195	pub fn signed_canonical_json(&self) -> Result<String> {
196		to_canonical_json_string_for_signing(&self.signed).map_err(|error| {
197			err!(Request(InvalidParam(
198				"invalid `third_party_invite.signed` field in `m.room.member` event: {error}"
199			)))
200		})
201	}
202}