1use js_int::UInt;
4use serde::{Deserialize, Serialize};
5
6mod filter_room_type_serde;
7mod room_network_serde;
8
9use crate::{
10 room::{JoinRuleKind, RoomSummary, RoomType},
11 OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr,
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 #[doc(hidden)]
243 _Custom(PrivOwnedStr),
244}
245
246impl RoomTypeFilter {
247 pub fn as_str(&self) -> Option<&str> {
251 match self {
252 RoomTypeFilter::Default => None,
253 RoomTypeFilter::Space => Some("m.space"),
254 RoomTypeFilter::_Custom(s) => Some(&s.0),
255 }
256 }
257}
258
259impl<T> From<Option<T>> for RoomTypeFilter
260where
261 T: AsRef<str> + Into<Box<str>>,
262{
263 fn from(s: Option<T>) -> Self {
264 match s {
265 None => Self::Default,
266 Some(s) => match s.as_ref() {
267 "m.space" => Self::Space,
268 _ => Self::_Custom(PrivOwnedStr(s.into())),
269 },
270 }
271 }
272}
273
274impl From<Option<RoomType>> for RoomTypeFilter {
275 fn from(t: Option<RoomType>) -> Self {
276 match t {
277 None => Self::Default,
278 Some(s) => match s {
279 RoomType::Space => Self::Space,
280 _ => Self::from(Some(s.as_str())),
281 },
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use assert_matches2::assert_matches;
289 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
290
291 use super::{Filter, RoomNetwork, RoomTypeFilter};
292 use crate::room::RoomType;
293
294 #[test]
295 fn test_from_room_type() {
296 let test = RoomType::Space;
297 let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
298 assert_eq!(other, RoomTypeFilter::Space);
299 }
300
301 #[test]
302 fn serialize_matrix_network_only() {
303 let json = json!({});
304 assert_eq!(to_json_value(RoomNetwork::Matrix).unwrap(), json);
305 }
306
307 #[test]
308 fn deserialize_matrix_network_only() {
309 let json = json!({ "include_all_networks": false });
310 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
311 }
312
313 #[test]
314 fn serialize_default_network_is_empty() {
315 let json = json!({});
316 assert_eq!(to_json_value(RoomNetwork::default()).unwrap(), json);
317 }
318
319 #[test]
320 fn deserialize_empty_network_is_default() {
321 let json = json!({});
322 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
323 }
324
325 #[test]
326 fn serialize_include_all_networks() {
327 let json = json!({ "include_all_networks": true });
328 assert_eq!(to_json_value(RoomNetwork::All).unwrap(), json);
329 }
330
331 #[test]
332 fn deserialize_include_all_networks() {
333 let json = json!({ "include_all_networks": true });
334 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::All);
335 }
336
337 #[test]
338 fn serialize_third_party_network() {
339 let json = json!({ "third_party_instance_id": "freenode" });
340 assert_eq!(to_json_value(RoomNetwork::ThirdParty("freenode".to_owned())).unwrap(), json);
341 }
342
343 #[test]
344 fn deserialize_third_party_network() {
345 let json = json!({ "third_party_instance_id": "freenode" });
346 assert_eq!(
347 from_json_value::<RoomNetwork>(json).unwrap(),
348 RoomNetwork::ThirdParty("freenode".into())
349 );
350 }
351
352 #[test]
353 fn deserialize_include_all_networks_and_third_party_exclusivity() {
354 let json = json!({ "include_all_networks": true, "third_party_instance_id": "freenode" });
355 assert_eq!(
356 from_json_value::<RoomNetwork>(json).unwrap_err().to_string().as_str(),
357 "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive."
358 );
359 }
360
361 #[test]
362 fn serialize_filter_empty() {
363 let filter = Filter::default();
364 let json = json!({});
365 assert_eq!(to_json_value(filter).unwrap(), json);
366 }
367
368 #[test]
369 fn deserialize_filter_empty() {
370 let json = json!({});
371 let filter = from_json_value::<Filter>(json).unwrap();
372 assert_eq!(filter.generic_search_term, None);
373 assert_eq!(filter.room_types.len(), 0);
374 }
375
376 #[test]
377 fn serialize_filter_room_types() {
378 let filter = Filter {
379 generic_search_term: None,
380 room_types: vec![
381 RoomTypeFilter::Default,
382 RoomTypeFilter::Space,
383 Some("custom_type").into(),
384 ],
385 };
386 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
387 assert_eq!(to_json_value(filter).unwrap(), json);
388 }
389
390 #[test]
391 fn deserialize_filter_room_types() {
392 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
393 let filter = from_json_value::<Filter>(json).unwrap();
394 assert_eq!(filter.room_types.len(), 3);
395 assert_eq!(filter.room_types[0], RoomTypeFilter::Default);
396 assert_eq!(filter.room_types[1], RoomTypeFilter::Space);
397 assert_matches!(&filter.room_types[2], RoomTypeFilter::_Custom(_));
398 assert_eq!(filter.room_types[2].as_str(), Some("custom_type"));
399 }
400}