1use js_int::UInt;
4use serde::{Deserialize, Serialize};
5
6mod filter_room_type_serde;
7mod room_network_serde;
8
9use crate::{
10 OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr,
11 room::{JoinRuleKind, RoomSummary, RoomType},
12};
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
20#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
21pub struct PublicRoomsChunk {
22 #[serde(skip_serializing_if = "Option::is_none")]
24 #[cfg_attr(
25 feature = "compat-empty-string-null",
26 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
27 )]
28 pub canonical_alias: Option<OwnedRoomAliasId>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub name: Option<String>,
33
34 pub num_joined_members: UInt,
36
37 pub room_id: OwnedRoomId,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub topic: Option<String>,
43
44 pub world_readable: bool,
46
47 pub guest_can_join: bool,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
57 #[cfg_attr(
58 feature = "compat-empty-string-null",
59 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
60 )]
61 pub avatar_url: Option<OwnedMxcUri>,
62
63 #[serde(default, skip_serializing_if = "crate::serde::is_default")]
65 pub join_rule: JoinRuleKind,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub room_type: Option<RoomType>,
70}
71
72#[derive(Debug)]
77#[allow(clippy::exhaustive_structs)]
78pub struct PublicRoomsChunkInit {
79 pub num_joined_members: UInt,
81
82 pub room_id: OwnedRoomId,
84
85 pub world_readable: bool,
87
88 pub guest_can_join: bool,
92}
93
94impl From<PublicRoomsChunkInit> for PublicRoomsChunk {
95 fn from(init: PublicRoomsChunkInit) -> Self {
96 let PublicRoomsChunkInit { num_joined_members, room_id, world_readable, guest_can_join } =
97 init;
98
99 Self {
100 canonical_alias: None,
101 name: None,
102 num_joined_members,
103 room_id,
104 topic: None,
105 world_readable,
106 guest_can_join,
107 avatar_url: None,
108 join_rule: JoinRuleKind::default(),
109 room_type: None,
110 }
111 }
112}
113
114impl From<RoomSummary> for PublicRoomsChunk {
115 fn from(value: RoomSummary) -> Self {
116 let RoomSummary {
117 room_id,
118 canonical_alias,
119 name,
120 topic,
121 avatar_url,
122 room_type,
123 num_joined_members,
124 join_rule,
125 world_readable,
126 guest_can_join,
127 ..
128 } = value;
129
130 Self {
131 canonical_alias,
132 name,
133 num_joined_members,
134 room_id,
135 topic,
136 world_readable,
137 guest_can_join,
138 avatar_url,
139 join_rule: join_rule.as_str().into(),
140 room_type,
141 }
142 }
143}
144
145impl From<PublicRoomsChunk> for RoomSummary {
146 fn from(value: PublicRoomsChunk) -> Self {
147 let PublicRoomsChunk {
148 room_id,
149 canonical_alias,
150 name,
151 topic,
152 avatar_url,
153 room_type,
154 num_joined_members,
155 join_rule,
156 world_readable,
157 guest_can_join,
158 } = value;
159
160 Self {
161 canonical_alias,
162 name,
163 num_joined_members,
164 room_id,
165 topic,
166 world_readable,
167 guest_can_join,
168 avatar_url,
169 join_rule: join_rule.into(),
170 room_type,
171 encryption: None,
172 room_version: None,
173 }
174 }
175}
176
177#[derive(Clone, Debug, Default, Deserialize, Serialize)]
179#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
180pub struct Filter {
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub generic_search_term: Option<String>,
184
185 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 #[cfg_attr(feature = "compat-null", serde(deserialize_with = "crate::serde::none_as_default"))]
193 pub room_types: Vec<RoomTypeFilter>,
194}
195
196impl Filter {
197 pub fn new() -> Self {
199 Default::default()
200 }
201
202 pub fn is_empty(&self) -> bool {
204 self.generic_search_term.is_none()
205 }
206}
207
208#[derive(Clone, Debug, Default, PartialEq, Eq)]
211#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
212pub enum RoomNetwork {
213 #[default]
215 Matrix,
216
217 All,
219
220 ThirdParty(String),
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
233#[non_exhaustive]
234pub enum RoomTypeFilter {
235 Default,
237
238 Space,
240
241 #[cfg(feature = "unstable-msc3417")]
245 Call,
246
247 #[doc(hidden)]
249 _Custom(PrivOwnedStr),
250}
251
252impl RoomTypeFilter {
253 pub fn as_str(&self) -> Option<&str> {
257 match self {
258 RoomTypeFilter::Default => None,
259 RoomTypeFilter::Space => Some("m.space"),
260 #[cfg(feature = "unstable-msc3417")]
261 RoomTypeFilter::Call => Some("org.matrix.msc3417.call"),
262 RoomTypeFilter::_Custom(s) => Some(&s.0),
263 }
264 }
265}
266
267impl<T> From<Option<T>> for RoomTypeFilter
268where
269 T: AsRef<str> + Into<Box<str>>,
270{
271 fn from(s: Option<T>) -> Self {
272 match s {
273 None => Self::Default,
274 Some(s) => match s.as_ref() {
275 "m.space" => Self::Space,
276 #[cfg(feature = "unstable-msc3417")]
277 "org.matrix.msc3417.call" => Self::Call,
278 _ => Self::_Custom(PrivOwnedStr(s.into())),
279 },
280 }
281 }
282}
283
284impl From<Option<RoomType>> for RoomTypeFilter {
285 fn from(t: Option<RoomType>) -> Self {
286 match t {
287 None => Self::Default,
288 Some(s) => match s {
289 RoomType::Space => Self::Space,
290 #[cfg(feature = "unstable-msc3417")]
291 RoomType::Call => Self::Call,
292 _ => Self::from(Some(s.as_str())),
293 },
294 }
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use assert_matches2::assert_matches;
301 use serde_json::{from_value as from_json_value, json};
302
303 use super::{Filter, RoomNetwork, RoomTypeFilter};
304 use crate::{assert_to_canonical_json_eq, room::RoomType};
305
306 #[test]
307 fn test_from_room_type() {
308 let test = RoomType::Space;
309 let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
310 assert_eq!(other, RoomTypeFilter::Space);
311 }
312
313 #[test]
314 #[cfg(feature = "unstable-msc3417")]
315 fn test_from_call_room_type() {
316 let test = RoomType::Call;
317 let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
318 assert_eq!(other, RoomTypeFilter::Call);
319 }
320
321 #[test]
322 fn serialize_matrix_network_only() {
323 assert_to_canonical_json_eq!(RoomNetwork::Matrix, json!({}));
324 }
325
326 #[test]
327 fn deserialize_matrix_network_only() {
328 let json = json!({ "include_all_networks": false });
329 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
330 }
331
332 #[test]
333 fn serialize_default_network_is_empty() {
334 assert_to_canonical_json_eq!(RoomNetwork::default(), json!({}));
335 }
336
337 #[test]
338 fn deserialize_empty_network_is_default() {
339 let json = json!({});
340 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
341 }
342
343 #[test]
344 fn serialize_include_all_networks() {
345 assert_to_canonical_json_eq!(RoomNetwork::All, json!({ "include_all_networks": true }));
346 }
347
348 #[test]
349 fn deserialize_include_all_networks() {
350 let json = json!({ "include_all_networks": true });
351 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::All);
352 }
353
354 #[test]
355 fn serialize_third_party_network() {
356 assert_to_canonical_json_eq!(
357 RoomNetwork::ThirdParty("freenode".to_owned()),
358 json!({ "third_party_instance_id": "freenode" }),
359 );
360 }
361
362 #[test]
363 fn deserialize_third_party_network() {
364 let json = json!({ "third_party_instance_id": "freenode" });
365 assert_eq!(
366 from_json_value::<RoomNetwork>(json).unwrap(),
367 RoomNetwork::ThirdParty("freenode".into())
368 );
369 }
370
371 #[test]
372 fn deserialize_include_all_networks_and_third_party_exclusivity() {
373 let json = json!({ "include_all_networks": true, "third_party_instance_id": "freenode" });
374 assert_eq!(
375 from_json_value::<RoomNetwork>(json).unwrap_err().to_string().as_str(),
376 "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive."
377 );
378 }
379
380 #[test]
381 fn serialize_filter_empty() {
382 assert_to_canonical_json_eq!(Filter::default(), json!({}));
383 }
384
385 #[test]
386 fn deserialize_filter_empty() {
387 let json = json!({});
388 let filter = from_json_value::<Filter>(json).unwrap();
389 assert_eq!(filter.generic_search_term, None);
390 assert_eq!(filter.room_types.len(), 0);
391 }
392
393 #[test]
394 fn serialize_filter_room_types() {
395 let filter = Filter {
396 generic_search_term: None,
397 room_types: vec![
398 RoomTypeFilter::Default,
399 RoomTypeFilter::Space,
400 Some("custom_type").into(),
401 ],
402 };
403 assert_to_canonical_json_eq!(
404 filter,
405 json!({ "room_types": [null, "m.space", "custom_type"] }),
406 );
407 }
408
409 #[test]
410 #[cfg(feature = "unstable-msc3417")]
411 fn serialize_filter_call_room_types() {
412 let filter = Filter {
413 generic_search_term: None,
414 room_types: vec![RoomTypeFilter::Default, RoomTypeFilter::Call],
415 };
416 assert_to_canonical_json_eq!(
417 filter,
418 json!({ "room_types": [null, "org.matrix.msc3417.call"] }),
419 );
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
433 #[test]
434 #[cfg(feature = "unstable-msc3417")]
435 fn deserialize_filter_call_room_types() {
436 let json = json!({ "room_types": [null, "m.space", "org.matrix.msc3417.call"] });
437 let filter = from_json_value::<Filter>(json).unwrap();
438 assert_eq!(filter.room_types.len(), 3);
439 assert_eq!(filter.room_types[0], RoomTypeFilter::Default);
440 assert_eq!(filter.room_types[1], RoomTypeFilter::Space);
441 assert_matches!(&filter.room_types[2], RoomTypeFilter::Call);
442 }
443}