1use js_int::UInt;
4use serde::{Deserialize, Serialize};
5
6mod filter_room_type_serde;
7mod room_network_serde;
8
9use crate::{
10 room::{RoomSummary, RoomType},
11 serde::StringEnum,
12 space::SpaceRoomJoinRule,
13 OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr,
14};
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
22#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
23pub struct PublicRoomsChunk {
24 #[serde(skip_serializing_if = "Option::is_none")]
26 #[cfg_attr(
27 feature = "compat-empty-string-null",
28 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
29 )]
30 pub canonical_alias: Option<OwnedRoomAliasId>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub name: Option<String>,
35
36 pub num_joined_members: UInt,
38
39 pub room_id: OwnedRoomId,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub topic: Option<String>,
45
46 pub world_readable: bool,
48
49 pub guest_can_join: bool,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
59 #[cfg_attr(
60 feature = "compat-empty-string-null",
61 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
62 )]
63 pub avatar_url: Option<OwnedMxcUri>,
64
65 #[serde(default, skip_serializing_if = "crate::serde::is_default")]
67 pub join_rule: PublicRoomJoinRule,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub room_type: Option<RoomType>,
72}
73
74#[derive(Debug)]
79#[allow(clippy::exhaustive_structs)]
80pub struct PublicRoomsChunkInit {
81 pub num_joined_members: UInt,
83
84 pub room_id: OwnedRoomId,
86
87 pub world_readable: bool,
89
90 pub guest_can_join: bool,
94}
95
96impl From<PublicRoomsChunkInit> for PublicRoomsChunk {
97 fn from(init: PublicRoomsChunkInit) -> Self {
98 let PublicRoomsChunkInit { num_joined_members, room_id, world_readable, guest_can_join } =
99 init;
100
101 Self {
102 canonical_alias: None,
103 name: None,
104 num_joined_members,
105 room_id,
106 topic: None,
107 world_readable,
108 guest_can_join,
109 avatar_url: None,
110 join_rule: PublicRoomJoinRule::default(),
111 room_type: None,
112 }
113 }
114}
115
116impl From<RoomSummary> for PublicRoomsChunk {
117 fn from(value: RoomSummary) -> Self {
118 let RoomSummary {
119 room_id,
120 canonical_alias,
121 name,
122 topic,
123 avatar_url,
124 room_type,
125 num_joined_members,
126 join_rule,
127 world_readable,
128 guest_can_join,
129 ..
130 } = value;
131
132 Self {
133 canonical_alias,
134 name,
135 num_joined_members,
136 room_id,
137 topic,
138 world_readable,
139 guest_can_join,
140 avatar_url,
141 join_rule: join_rule.into(),
142 room_type,
143 }
144 }
145}
146
147#[derive(Clone, Debug, Default, Deserialize, Serialize)]
149#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
150pub struct Filter {
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub generic_search_term: Option<String>,
154
155 #[serde(default, skip_serializing_if = "Vec::is_empty")]
162 #[cfg_attr(feature = "compat-null", serde(deserialize_with = "crate::serde::none_as_default"))]
163 pub room_types: Vec<RoomTypeFilter>,
164}
165
166impl Filter {
167 pub fn new() -> Self {
169 Default::default()
170 }
171
172 pub fn is_empty(&self) -> bool {
174 self.generic_search_term.is_none()
175 }
176}
177
178#[derive(Clone, Debug, Default, PartialEq, Eq)]
181#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
182pub enum RoomNetwork {
183 #[default]
185 Matrix,
186
187 All,
189
190 ThirdParty(String),
192}
193
194#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
196#[derive(Clone, Default, PartialEq, Eq, StringEnum)]
197#[ruma_enum(rename_all = "snake_case")]
198#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
199pub enum PublicRoomJoinRule {
200 Invite,
203
204 Knock,
208
209 Private,
211
212 Restricted,
215
216 KnockRestricted,
219
220 #[default]
222 Public,
223
224 #[doc(hidden)]
225 _Custom(PrivOwnedStr),
226}
227
228impl From<PublicRoomJoinRule> for SpaceRoomJoinRule {
229 fn from(value: PublicRoomJoinRule) -> Self {
230 match value {
231 PublicRoomJoinRule::Invite => Self::Invite,
232 PublicRoomJoinRule::Knock => Self::Knock,
233 PublicRoomJoinRule::Private => Self::Private,
234 PublicRoomJoinRule::Restricted => Self::Restricted,
235 PublicRoomJoinRule::KnockRestricted => Self::KnockRestricted,
236 PublicRoomJoinRule::Public => Self::Public,
237 PublicRoomJoinRule::_Custom(custom) => Self::_Custom(custom),
238 }
239 }
240}
241
242impl From<SpaceRoomJoinRule> for PublicRoomJoinRule {
243 fn from(value: SpaceRoomJoinRule) -> Self {
244 match value {
245 SpaceRoomJoinRule::Invite => Self::Invite,
246 SpaceRoomJoinRule::Knock => Self::Knock,
247 SpaceRoomJoinRule::Private => Self::Private,
248 SpaceRoomJoinRule::Restricted => Self::Restricted,
249 SpaceRoomJoinRule::KnockRestricted => Self::KnockRestricted,
250 SpaceRoomJoinRule::Public => Self::Public,
251 SpaceRoomJoinRule::_Custom(custom) => Self::_Custom(custom),
252 }
253 }
254}
255
256#[derive(Clone, Debug, PartialEq, Eq)]
265#[non_exhaustive]
266pub enum RoomTypeFilter {
267 Default,
269
270 Space,
272
273 #[doc(hidden)]
275 _Custom(PrivOwnedStr),
276}
277
278impl RoomTypeFilter {
279 pub fn as_str(&self) -> Option<&str> {
283 match self {
284 RoomTypeFilter::Default => None,
285 RoomTypeFilter::Space => Some("m.space"),
286 RoomTypeFilter::_Custom(s) => Some(&s.0),
287 }
288 }
289}
290
291impl<T> From<Option<T>> for RoomTypeFilter
292where
293 T: AsRef<str> + Into<Box<str>>,
294{
295 fn from(s: Option<T>) -> Self {
296 match s {
297 None => Self::Default,
298 Some(s) => match s.as_ref() {
299 "m.space" => Self::Space,
300 _ => Self::_Custom(PrivOwnedStr(s.into())),
301 },
302 }
303 }
304}
305
306impl From<Option<RoomType>> for RoomTypeFilter {
307 fn from(t: Option<RoomType>) -> Self {
308 match t {
309 None => Self::Default,
310 Some(s) => match s {
311 RoomType::Space => Self::Space,
312 _ => Self::from(Some(s.as_str())),
313 },
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use assert_matches2::assert_matches;
321 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
322
323 use super::{Filter, RoomNetwork, RoomTypeFilter};
324 use crate::room::RoomType;
325
326 #[test]
327 fn test_from_room_type() {
328 let test = RoomType::Space;
329 let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
330 assert_eq!(other, RoomTypeFilter::Space);
331 }
332
333 #[test]
334 fn serialize_matrix_network_only() {
335 let json = json!({});
336 assert_eq!(to_json_value(RoomNetwork::Matrix).unwrap(), json);
337 }
338
339 #[test]
340 fn deserialize_matrix_network_only() {
341 let json = json!({ "include_all_networks": false });
342 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
343 }
344
345 #[test]
346 fn serialize_default_network_is_empty() {
347 let json = json!({});
348 assert_eq!(to_json_value(RoomNetwork::default()).unwrap(), json);
349 }
350
351 #[test]
352 fn deserialize_empty_network_is_default() {
353 let json = json!({});
354 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
355 }
356
357 #[test]
358 fn serialize_include_all_networks() {
359 let json = json!({ "include_all_networks": true });
360 assert_eq!(to_json_value(RoomNetwork::All).unwrap(), json);
361 }
362
363 #[test]
364 fn deserialize_include_all_networks() {
365 let json = json!({ "include_all_networks": true });
366 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::All);
367 }
368
369 #[test]
370 fn serialize_third_party_network() {
371 let json = json!({ "third_party_instance_id": "freenode" });
372 assert_eq!(to_json_value(RoomNetwork::ThirdParty("freenode".to_owned())).unwrap(), json);
373 }
374
375 #[test]
376 fn deserialize_third_party_network() {
377 let json = json!({ "third_party_instance_id": "freenode" });
378 assert_eq!(
379 from_json_value::<RoomNetwork>(json).unwrap(),
380 RoomNetwork::ThirdParty("freenode".into())
381 );
382 }
383
384 #[test]
385 fn deserialize_include_all_networks_and_third_party_exclusivity() {
386 let json = json!({ "include_all_networks": true, "third_party_instance_id": "freenode" });
387 assert_eq!(
388 from_json_value::<RoomNetwork>(json).unwrap_err().to_string().as_str(),
389 "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive."
390 );
391 }
392
393 #[test]
394 fn serialize_filter_empty() {
395 let filter = Filter::default();
396 let json = json!({});
397 assert_eq!(to_json_value(filter).unwrap(), json);
398 }
399
400 #[test]
401 fn deserialize_filter_empty() {
402 let json = json!({});
403 let filter = from_json_value::<Filter>(json).unwrap();
404 assert_eq!(filter.generic_search_term, None);
405 assert_eq!(filter.room_types.len(), 0);
406 }
407
408 #[test]
409 fn serialize_filter_room_types() {
410 let filter = Filter {
411 generic_search_term: None,
412 room_types: vec![
413 RoomTypeFilter::Default,
414 RoomTypeFilter::Space,
415 Some("custom_type").into(),
416 ],
417 };
418 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
419 assert_eq!(to_json_value(filter).unwrap(), json);
420 }
421
422 #[test]
423 fn deserialize_filter_room_types() {
424 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
425 let filter = from_json_value::<Filter>(json).unwrap();
426 assert_eq!(filter.room_types.len(), 3);
427 assert_eq!(filter.room_types[0], RoomTypeFilter::Default);
428 assert_eq!(filter.room_types[1], RoomTypeFilter::Space);
429 assert_matches!(&filter.room_types[2], RoomTypeFilter::_Custom(_));
430 assert_eq!(filter.room_types[2].as_str(), Some("custom_type"));
431 }
432}