//! Common types for room directory endpoints. use js_int::UInt; use serde::{Deserialize, Serialize}; #[cfg(feature = "unstable-msc3827")] mod filter_room_type_serde; mod room_network_serde; #[cfg(feature = "unstable-msc3827")] use crate::room::RoomType; use crate::{ serde::{Incoming, StringEnum}, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr, }; /// A chunk of a room list response, describing one room. /// /// To create an instance of this type, first create a `PublicRoomsChunkInit` and convert it via /// `PublicRoomsChunk::from` / `.into()`. #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct PublicRoomsChunk { /// The canonical alias of the room, if any. #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr( feature = "compat", serde(default, deserialize_with = "crate::serde::empty_string_as_none") )] pub canonical_alias: Option, /// The name of the room, if any. #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, /// The number of members joined to the room. pub num_joined_members: UInt, /// The ID of the room. pub room_id: OwnedRoomId, /// The topic of the room, if any. #[serde(skip_serializing_if = "Option::is_none")] pub topic: Option, /// Whether the room may be viewed by guest users without joining. pub world_readable: bool, /// Whether guest users may join the room and participate in it. /// /// If they can, they will be subject to ordinary power level rules like any other user. pub guest_can_join: bool, /// The URL for the room's avatar, if one is set. /// /// If you activate the `compat` feature, this field being an empty string in JSON will result /// in `None` here during deserialization. #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr( feature = "compat", serde(default, deserialize_with = "crate::serde::empty_string_as_none") )] pub avatar_url: Option, /// The join rule of the room. #[serde(default, skip_serializing_if = "crate::serde::is_default")] pub join_rule: PublicRoomJoinRule, /// The type of room from `m.room.create`, if any. /// /// This field uses the unstable prefix from [MSC3827]. /// /// [MSC3827]: https://github.com/matrix-org/matrix-spec-proposals/pull/3827 #[cfg(feature = "unstable-msc3827")] #[serde( rename = "org.matrix.msc3827.room_type", alias = "room_type", skip_serializing_if = "Option::is_none" )] pub room_type: Option, } /// Initial set of mandatory fields of `PublicRoomsChunk`. /// /// This struct will not be updated even if additional fields are added to `PublicRoomsChunk` in a /// new (non-breaking) release of the Matrix specification. #[derive(Debug)] #[allow(clippy::exhaustive_structs)] pub struct PublicRoomsChunkInit { /// The number of members joined to the room. pub num_joined_members: UInt, /// The ID of the room. pub room_id: OwnedRoomId, /// Whether the room may be viewed by guest users without joining. pub world_readable: bool, /// Whether guest users may join the room and participate in it. /// /// If they can, they will be subject to ordinary power level rules like any other user. pub guest_can_join: bool, } impl From for PublicRoomsChunk { fn from(init: PublicRoomsChunkInit) -> Self { let PublicRoomsChunkInit { num_joined_members, room_id, world_readable, guest_can_join } = init; Self { canonical_alias: None, name: None, num_joined_members, room_id, topic: None, world_readable, guest_can_join, avatar_url: None, join_rule: PublicRoomJoinRule::default(), #[cfg(feature = "unstable-msc3827")] room_type: None, } } } /// A filter for public rooms lists #[derive(Clone, Debug, Default, Incoming, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[incoming_derive(Default)] pub struct Filter<'a> { /// A string to search for in the room metadata, e.g. name, topic, canonical alias etc. #[serde(skip_serializing_if = "Option::is_none")] pub generic_search_term: Option<&'a str>, /// The room types to include in the results. /// /// Includes all room types if it is empty. /// /// This field uses the unstable prefix from [MSC3827]. /// /// [MSC3827]: https://github.com/matrix-org/matrix-spec-proposals/pull/3827 #[cfg(feature = "unstable-msc3827")] #[serde( rename = "org.matrix.msc3827.room_types", alias = "room_types", default, skip_serializing_if = "Vec::is_empty" )] pub room_types: Vec, } impl Filter<'_> { /// Creates an empty `Filter`. pub fn new() -> Self { Default::default() } /// Returns `true` if the filter is empty. pub fn is_empty(&self) -> bool { self.generic_search_term.is_none() } } /// Information about which networks/protocols from application services on the /// homeserver from which to request rooms. #[derive(Clone, Debug, PartialEq, Eq, Incoming)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[incoming_derive(Clone, PartialEq, Eq, !Deserialize)] pub enum RoomNetwork<'a> { /// Return rooms from the Matrix network. Matrix, /// Return rooms from all the networks/protocols the homeserver knows about. All, /// Return rooms from a specific third party network/protocol. ThirdParty(&'a str), } impl<'a> Default for RoomNetwork<'a> { fn default() -> Self { RoomNetwork::Matrix } } /// The rule used for users wishing to join a public room. #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))] #[derive(Clone, Debug, PartialEq, Eq, StringEnum)] #[ruma_enum(rename_all = "snake_case")] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub enum PublicRoomJoinRule { /// Users can request an invite to the room. Knock, /// Anyone can join the room without any prior action. Public, #[doc(hidden)] _Custom(PrivOwnedStr), } impl Default for PublicRoomJoinRule { fn default() -> Self { Self::Public } } /// An enum of possible room types to filter. /// /// This type can hold an arbitrary string. To build this with a custom value, convert it from an /// `Option` with `::from()` / `.into()`. [`RoomTypeFilter::Default`] can be constructed /// from `None`. /// /// To check for values that are not available as a documented variant here, use its string /// representation, obtained through [`.as_str()`](Self::as_str()). #[cfg(feature = "unstable-msc3827")] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum RoomTypeFilter { /// The default room type, defined without a `room_type`. Default, /// A space. Space, /// A custom room type. #[doc(hidden)] _Custom(PrivOwnedStr), } #[cfg(feature = "unstable-msc3827")] impl RoomTypeFilter { /// Get the string representation of this `RoomTypeFilter`. /// /// [`RoomTypeFilter::Default`] returns `None`. pub fn as_str(&self) -> Option<&str> { match self { RoomTypeFilter::Default => None, RoomTypeFilter::Space => Some("m.space"), RoomTypeFilter::_Custom(s) => Some(&s.0), } } } #[cfg(feature = "unstable-msc3827")] impl From> for RoomTypeFilter where T: AsRef + Into>, { fn from(s: Option) -> Self { match s { None => Self::Default, Some(s) => match s.as_ref() { "m.space" => Self::Space, _ => Self::_Custom(PrivOwnedStr(s.into())), }, } } } #[cfg(test)] mod tests { #[cfg(feature = "unstable-msc3827")] use assert_matches::assert_matches; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; #[cfg(feature = "unstable-msc3827")] use super::RoomTypeFilter; use super::{Filter, IncomingFilter, IncomingRoomNetwork, RoomNetwork}; #[test] fn serialize_matrix_network_only() { let json = json!({}); assert_eq!(to_json_value(RoomNetwork::Matrix).unwrap(), json); } #[test] fn deserialize_matrix_network_only() { let json = json!({ "include_all_networks": false }); assert_eq!( from_json_value::(json).unwrap(), IncomingRoomNetwork::Matrix ); } #[test] fn serialize_default_network_is_empty() { let json = json!({}); assert_eq!(to_json_value(RoomNetwork::default()).unwrap(), json); } #[test] fn deserialize_empty_network_is_default() { let json = json!({}); assert_eq!( from_json_value::(json).unwrap(), IncomingRoomNetwork::Matrix ); } #[test] fn serialize_include_all_networks() { let json = json!({ "include_all_networks": true }); assert_eq!(to_json_value(RoomNetwork::All).unwrap(), json); } #[test] fn deserialize_include_all_networks() { let json = json!({ "include_all_networks": true }); assert_eq!(from_json_value::(json).unwrap(), IncomingRoomNetwork::All); } #[test] fn serialize_third_party_network() { let json = json!({ "third_party_instance_id": "freenode" }); assert_eq!(to_json_value(RoomNetwork::ThirdParty("freenode")).unwrap(), json); } #[test] fn deserialize_third_party_network() { let json = json!({ "third_party_instance_id": "freenode" }); assert_eq!( from_json_value::(json).unwrap(), IncomingRoomNetwork::ThirdParty("freenode".into()) ); } #[test] fn deserialize_include_all_networks_and_third_party_exclusivity() { let json = json!({ "include_all_networks": true, "third_party_instance_id": "freenode" }); assert_eq!( from_json_value::(json).unwrap_err().to_string().as_str(), "`include_all_networks = true` and `third_party_instance_id` are mutually exclusive." ); } #[test] fn serialize_filter_empty() { let filter = Filter::default(); let json = json!({}); assert_eq!(to_json_value(filter).unwrap(), json); } #[test] fn deserialize_filter_empty() { let json = json!({}); let filter = from_json_value::(json).unwrap(); assert_eq!(filter.generic_search_term, None); #[cfg(feature = "unstable-msc3827")] assert_eq!(filter.room_types.len(), 0); } #[cfg(feature = "unstable-msc3827")] #[test] fn serialize_filter_room_types() { let filter = Filter { generic_search_term: None, room_types: vec![ RoomTypeFilter::Default, RoomTypeFilter::Space, Some("custom_type").into(), ], }; let json = json!({ "org.matrix.msc3827.room_types": [null, "m.space", "custom_type"] }); assert_eq!(to_json_value(filter).unwrap(), json); } #[cfg(feature = "unstable-msc3827")] #[test] fn deserialize_filter_room_types_unstable() { let json = json!({ "org.matrix.msc3827.room_types": [null, "m.space", "custom_type"] }); let filter = from_json_value::(json).unwrap(); assert_eq!(filter.room_types.len(), 3); assert_eq!(filter.room_types[0], RoomTypeFilter::Default); assert_eq!(filter.room_types[1], RoomTypeFilter::Space); assert_matches!(filter.room_types[2], RoomTypeFilter::_Custom(_)); assert_eq!(filter.room_types[2].as_str(), Some("custom_type")); } #[cfg(feature = "unstable-msc3827")] #[test] fn deserialize_filter_room_types_stable() { let json = json!({ "room_types": [null, "m.space", "custom_type"] }); let filter = from_json_value::(json).unwrap(); assert_eq!(filter.room_types.len(), 3); assert_eq!(filter.room_types[0], RoomTypeFilter::Default); assert_eq!(filter.room_types[1], RoomTypeFilter::Space); assert_matches!(filter.room_types[2], RoomTypeFilter::_Custom(_)); assert_eq!(filter.room_types[2].as_str(), Some("custom_type")); } }