1use js_int::UInt;
4use serde::{Deserialize, Serialize};
5
6mod filter_room_type_serde;
7mod room_network_serde;
8
9use crate::{
10 room::RoomType, serde::StringEnum, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr,
11};
12
13#[derive(Clone, Debug, Deserialize, Serialize)]
18#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
19pub struct PublicRoomsChunk {
20 #[serde(skip_serializing_if = "Option::is_none")]
22 #[cfg_attr(
23 feature = "compat-empty-string-null",
24 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
25 )]
26 pub canonical_alias: Option<OwnedRoomAliasId>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub name: Option<String>,
31
32 pub num_joined_members: UInt,
34
35 pub room_id: OwnedRoomId,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub topic: Option<String>,
41
42 pub world_readable: bool,
44
45 pub guest_can_join: bool,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
55 #[cfg_attr(
56 feature = "compat-empty-string-null",
57 serde(default, deserialize_with = "crate::serde::empty_string_as_none")
58 )]
59 pub avatar_url: Option<OwnedMxcUri>,
60
61 #[serde(default, skip_serializing_if = "crate::serde::is_default")]
63 pub join_rule: PublicRoomJoinRule,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub room_type: Option<RoomType>,
68}
69
70#[derive(Debug)]
75#[allow(clippy::exhaustive_structs)]
76pub struct PublicRoomsChunkInit {
77 pub num_joined_members: UInt,
79
80 pub room_id: OwnedRoomId,
82
83 pub world_readable: bool,
85
86 pub guest_can_join: bool,
90}
91
92impl From<PublicRoomsChunkInit> for PublicRoomsChunk {
93 fn from(init: PublicRoomsChunkInit) -> Self {
94 let PublicRoomsChunkInit { num_joined_members, room_id, world_readable, guest_can_join } =
95 init;
96
97 Self {
98 canonical_alias: None,
99 name: None,
100 num_joined_members,
101 room_id,
102 topic: None,
103 world_readable,
104 guest_can_join,
105 avatar_url: None,
106 join_rule: PublicRoomJoinRule::default(),
107 room_type: None,
108 }
109 }
110}
111
112#[derive(Clone, Debug, Default, Deserialize, Serialize)]
114#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
115pub struct Filter {
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub generic_search_term: Option<String>,
119
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
127 #[cfg_attr(feature = "compat-null", serde(deserialize_with = "crate::serde::none_as_default"))]
128 pub room_types: Vec<RoomTypeFilter>,
129}
130
131impl Filter {
132 pub fn new() -> Self {
134 Default::default()
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.generic_search_term.is_none()
140 }
141}
142
143#[derive(Clone, Debug, Default, PartialEq, Eq)]
146#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
147pub enum RoomNetwork {
148 #[default]
150 Matrix,
151
152 All,
154
155 ThirdParty(String),
157}
158
159#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
161#[derive(Clone, Default, PartialEq, Eq, StringEnum)]
162#[ruma_enum(rename_all = "snake_case")]
163#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
164pub enum PublicRoomJoinRule {
165 Knock,
167
168 #[default]
170 Public,
171
172 #[doc(hidden)]
173 _Custom(PrivOwnedStr),
174}
175
176#[derive(Clone, Debug, PartialEq, Eq)]
185#[non_exhaustive]
186pub enum RoomTypeFilter {
187 Default,
189
190 Space,
192
193 #[doc(hidden)]
195 _Custom(PrivOwnedStr),
196}
197
198impl RoomTypeFilter {
199 pub fn as_str(&self) -> Option<&str> {
203 match self {
204 RoomTypeFilter::Default => None,
205 RoomTypeFilter::Space => Some("m.space"),
206 RoomTypeFilter::_Custom(s) => Some(&s.0),
207 }
208 }
209}
210
211impl<T> From<Option<T>> for RoomTypeFilter
212where
213 T: AsRef<str> + Into<Box<str>>,
214{
215 fn from(s: Option<T>) -> Self {
216 match s {
217 None => Self::Default,
218 Some(s) => match s.as_ref() {
219 "m.space" => Self::Space,
220 _ => Self::_Custom(PrivOwnedStr(s.into())),
221 },
222 }
223 }
224}
225
226impl From<Option<RoomType>> for RoomTypeFilter {
227 fn from(t: Option<RoomType>) -> Self {
228 match t {
229 None => Self::Default,
230 Some(s) => match s {
231 RoomType::Space => Self::Space,
232 _ => Self::from(Some(s.as_str())),
233 },
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use assert_matches2::assert_matches;
241 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
242
243 use super::{Filter, RoomNetwork, RoomTypeFilter};
244 use crate::room::RoomType;
245
246 #[test]
247 fn test_from_room_type() {
248 let test = RoomType::Space;
249 let other: RoomTypeFilter = RoomTypeFilter::from(Some(test));
250 assert_eq!(other, RoomTypeFilter::Space);
251 }
252
253 #[test]
254 fn serialize_matrix_network_only() {
255 let json = json!({});
256 assert_eq!(to_json_value(RoomNetwork::Matrix).unwrap(), json);
257 }
258
259 #[test]
260 fn deserialize_matrix_network_only() {
261 let json = json!({ "include_all_networks": false });
262 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
263 }
264
265 #[test]
266 fn serialize_default_network_is_empty() {
267 let json = json!({});
268 assert_eq!(to_json_value(RoomNetwork::default()).unwrap(), json);
269 }
270
271 #[test]
272 fn deserialize_empty_network_is_default() {
273 let json = json!({});
274 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::Matrix);
275 }
276
277 #[test]
278 fn serialize_include_all_networks() {
279 let json = json!({ "include_all_networks": true });
280 assert_eq!(to_json_value(RoomNetwork::All).unwrap(), json);
281 }
282
283 #[test]
284 fn deserialize_include_all_networks() {
285 let json = json!({ "include_all_networks": true });
286 assert_eq!(from_json_value::<RoomNetwork>(json).unwrap(), RoomNetwork::All);
287 }
288
289 #[test]
290 fn serialize_third_party_network() {
291 let json = json!({ "third_party_instance_id": "freenode" });
292 assert_eq!(to_json_value(RoomNetwork::ThirdParty("freenode".to_owned())).unwrap(), json);
293 }
294
295 #[test]
296 fn deserialize_third_party_network() {
297 let json = json!({ "third_party_instance_id": "freenode" });
298 assert_eq!(
299 from_json_value::<RoomNetwork>(json).unwrap(),
300 RoomNetwork::ThirdParty("freenode".into())
301 );
302 }
303
304 #[test]
305 fn deserialize_include_all_networks_and_third_party_exclusivity() {
306 let json = json!({ "include_all_networks": true, "third_party_instance_id": "freenode" });
307 assert_eq!(
308 from_json_value::<RoomNetwork>(json).unwrap_err().to_string().as_str(),
309 "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive."
310 );
311 }
312
313 #[test]
314 fn serialize_filter_empty() {
315 let filter = Filter::default();
316 let json = json!({});
317 assert_eq!(to_json_value(filter).unwrap(), json);
318 }
319
320 #[test]
321 fn deserialize_filter_empty() {
322 let json = json!({});
323 let filter = from_json_value::<Filter>(json).unwrap();
324 assert_eq!(filter.generic_search_term, None);
325 assert_eq!(filter.room_types.len(), 0);
326 }
327
328 #[test]
329 fn serialize_filter_room_types() {
330 let filter = Filter {
331 generic_search_term: None,
332 room_types: vec![
333 RoomTypeFilter::Default,
334 RoomTypeFilter::Space,
335 Some("custom_type").into(),
336 ],
337 };
338 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
339 assert_eq!(to_json_value(filter).unwrap(), json);
340 }
341
342 #[test]
343 fn deserialize_filter_room_types() {
344 let json = json!({ "room_types": [null, "m.space", "custom_type"] });
345 let filter = from_json_value::<Filter>(json).unwrap();
346 assert_eq!(filter.room_types.len(), 3);
347 assert_eq!(filter.room_types[0], RoomTypeFilter::Default);
348 assert_eq!(filter.room_types[1], RoomTypeFilter::Space);
349 assert_matches!(&filter.room_types[2], RoomTypeFilter::_Custom(_));
350 assert_eq!(filter.room_types[2].as_str(), Some("custom_type"));
351 }
352}