Skip to main content

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