use std::borrow::Cow;
use std::collections::{BTreeMap, btree_map};
use maplit::btreemap;
use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, from_value as from_json_value, to_value as to_json_value};
use crate::{MatrixVersion, PrivOwnedStr, RoomVersionId, serde::StringEnum};
#[derive(ToSchema, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Capabilities {
#[serde(rename = "m.change_password", default)]
pub change_password: ChangePasswordCapability,
#[serde(
rename = "m.room_versions",
default,
skip_serializing_if = "RoomVersionsCapability::is_default"
)]
pub room_versions: RoomVersionsCapability,
#[serde(
rename = "m.set_display_name",
default,
skip_serializing_if = "SetDisplayNameCapability::is_default"
)]
pub set_display_name: SetDisplayNameCapability,
#[serde(
rename = "m.set_avatar_url",
default,
skip_serializing_if = "SetAvatarUrlCapability::is_default"
)]
pub set_avatar_url: SetAvatarUrlCapability,
#[serde(
rename = "m.3pid_changes",
default,
skip_serializing_if = "ThirdPartyIdChangesCapability::is_default"
)]
pub thirdparty_id_changes: ThirdPartyIdChangesCapability,
#[serde(flatten)]
#[salvo(schema(skip))]
#[salvo(schema(value_type = Object, additional_properties = true))]
pub custom_capabilities: BTreeMap<String, JsonValue>,
}
impl Capabilities {
pub fn new() -> Self {
Default::default()
}
pub fn get(&self, capability: &str) -> Option<Cow<'_, JsonValue>> {
fn serialize<T: Serialize>(cap: &T) -> JsonValue {
to_json_value(cap).expect("capability serialization to succeed")
}
match capability {
"m.change_password" => Some(Cow::Owned(serialize(&self.change_password))),
"m.room_versions" => Some(Cow::Owned(serialize(&self.room_versions))),
"m.set_display_name" => Some(Cow::Owned(serialize(&self.set_display_name))),
"m.set_avatar_url" => Some(Cow::Owned(serialize(&self.set_avatar_url))),
"m.3pid_changes" => Some(Cow::Owned(serialize(&self.thirdparty_id_changes))),
_ => self.custom_capabilities.get(capability).map(Cow::Borrowed),
}
}
pub fn set(&mut self, capability: &str, value: JsonValue) -> serde_json::Result<()> {
match capability {
"m.change_password" => self.change_password = from_json_value(value)?,
"m.room_versions" => self.room_versions = from_json_value(value)?,
"m.set_display_name" => self.set_display_name = from_json_value(value)?,
"m.set_avatar_url" => self.set_avatar_url = from_json_value(value)?,
"m.3pid_changes" => self.thirdparty_id_changes = from_json_value(value)?,
_ => {
self.custom_capabilities.insert(capability.to_owned(), value);
}
}
Ok(())
}
pub fn iter(&self) -> CapabilitiesIter<'_> {
CapabilitiesIter::new(self)
}
}
impl<'a> IntoIterator for &'a Capabilities {
type Item = CapabilityRef<'a>;
type IntoIter = CapabilitiesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct ChangePasswordCapability {
pub enabled: bool,
}
impl ChangePasswordCapability {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
pub fn is_default(&self) -> bool {
self.enabled
}
}
impl Default for ChangePasswordCapability {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct RoomVersionsCapability {
pub default: RoomVersionId,
pub available: BTreeMap<RoomVersionId, RoomVersionStability>,
}
impl RoomVersionsCapability {
pub fn new(default: RoomVersionId, available: BTreeMap<RoomVersionId, RoomVersionStability>) -> Self {
Self { default, available }
}
pub fn is_default(&self) -> bool {
self.default == RoomVersionId::V1
&& self.available.len() == 1
&& self
.available
.get(&RoomVersionId::V1)
.map(|stability| *stability == RoomVersionStability::Stable)
.unwrap_or(false)
}
}
impl Default for RoomVersionsCapability {
fn default() -> Self {
Self {
default: RoomVersionId::V1,
available: btreemap! { RoomVersionId::V1 => RoomVersionStability::Stable },
}
}
}
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(ToSchema, Clone, PartialEq, Eq, StringEnum)]
#[palpo_enum(rename_all = "lowercase")]
#[non_exhaustive]
pub enum RoomVersionStability {
Stable,
Unstable,
#[doc(hidden)]
#[salvo(schema(skip))]
_Custom(PrivOwnedStr),
}
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct SetDisplayNameCapability {
pub enabled: bool,
}
impl SetDisplayNameCapability {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
pub fn is_default(&self) -> bool {
self.enabled
}
}
impl Default for SetDisplayNameCapability {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct SetAvatarUrlCapability {
pub enabled: bool,
}
impl SetAvatarUrlCapability {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
pub fn is_default(&self) -> bool {
self.enabled
}
}
impl Default for SetAvatarUrlCapability {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(ToSchema, Clone, Debug, Serialize, Deserialize)]
pub struct ThirdPartyIdChangesCapability {
pub enabled: bool,
}
impl ThirdPartyIdChangesCapability {
pub fn new(enabled: bool) -> Self {
Self { enabled }
}
pub fn is_default(&self) -> bool {
self.enabled
}
}
impl Default for ThirdPartyIdChangesCapability {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug)]
pub struct CapabilityRef<'a> {
name: &'a str,
value: Option<&'a JsonValue>,
caps: &'a Capabilities,
}
impl<'a> CapabilityRef<'a> {
pub fn name(&self) -> &'a str {
self.name
}
pub fn value(&self) -> Cow<'a, JsonValue> {
match self.value {
Some(val) => Cow::Borrowed(val),
None => self.caps.get(self.name).unwrap(),
}
}
}
#[derive(Debug)]
pub struct CapabilitiesIter<'a> {
caps: &'a Capabilities,
pos: usize,
custom_caps_iterator: btree_map::Iter<'a, String, JsonValue>,
}
impl<'a> CapabilitiesIter<'a> {
pub(super) fn new(caps: &'a Capabilities) -> Self {
Self {
caps,
pos: 0,
custom_caps_iterator: caps.custom_capabilities.iter(),
}
}
}
impl<'a> Iterator for CapabilitiesIter<'a> {
type Item = CapabilityRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
match self.pos {
0 => {
self.pos += 1;
Some(CapabilityRef {
name: "m.change_password",
value: None,
caps: self.caps,
})
}
1 => {
self.pos += 1;
Some(CapabilityRef {
name: "m.room_versions",
value: None,
caps: self.caps,
})
}
2 => {
self.pos += 1;
Some(CapabilityRef {
name: "m.set_display_name",
value: None,
caps: self.caps,
})
}
3 => {
self.pos += 1;
Some(CapabilityRef {
name: "m.set_avatar_url",
value: None,
caps: self.caps,
})
}
4 => {
self.pos += 1;
Some(CapabilityRef {
name: "m.3pid_changes",
value: None,
caps: self.caps,
})
}
_ => self.custom_caps_iterator.next().map(|(name, value)| CapabilityRef {
name,
value: Some(value),
caps: self.caps,
}),
}
}
}
#[derive(ToSchema, Serialize, Debug)]
pub struct ClientWellKnownResBody {
#[serde(rename = "m.homeserver")]
pub homeserver: HomeServerInfo,
#[serde(default, rename = "m.identity_server", skip_serializing_if = "Option::is_none")]
pub identity_server: Option<IdentityServerInfo>,
#[serde(
default,
rename = "org.matrix.msc3488.tile_server",
alias = "m.tile_server",
skip_serializing_if = "Option::is_none"
)]
pub tile_server: Option<TileServerInfo>,
#[serde(
default,
rename = "org.matrix.msc2965.authentication",
alias = "m.authentication",
skip_serializing_if = "Option::is_none"
)]
pub authentication: Option<AuthenticationServerInfo>,
#[serde(
default,
rename = "org.matrix.msc3575.proxy",
skip_serializing_if = "Option::is_none"
)]
pub sliding_sync_proxy: Option<SlidingSyncProxyInfo>,
}
impl ClientWellKnownResBody {
pub fn new(homeserver: HomeServerInfo) -> Self {
Self {
homeserver,
identity_server: None,
tile_server: None,
authentication: None,
sliding_sync_proxy: None,
}
}
}
#[derive(ToSchema, Clone, Debug, Deserialize, Hash, Serialize)]
pub struct HomeServerInfo {
pub base_url: String,
}
impl HomeServerInfo {
pub fn new(base_url: String) -> Self {
Self { base_url }
}
}
#[derive(ToSchema, Clone, Debug, Deserialize, Hash, Serialize)]
pub struct IdentityServerInfo {
pub base_url: String,
}
impl IdentityServerInfo {
pub fn new(base_url: String) -> Self {
Self { base_url }
}
}
#[derive(ToSchema, Clone, Debug, Deserialize, Hash, Serialize)]
pub struct TileServerInfo {
pub map_style_url: String,
}
impl TileServerInfo {
pub fn new(map_style_url: String) -> Self {
Self { map_style_url }
}
}
#[derive(ToSchema, Clone, Debug, Deserialize, Hash, Serialize)]
pub struct AuthenticationServerInfo {
pub issuer: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
}
impl AuthenticationServerInfo {
pub fn new(issuer: String, account: Option<String>) -> Self {
Self { issuer, account }
}
}
#[derive(ToSchema, Clone, Debug, Deserialize, Hash, Serialize)]
pub struct SlidingSyncProxyInfo {
pub url: String,
}
impl SlidingSyncProxyInfo {
pub fn new(url: String) -> Self {
Self { url }
}
}
#[derive(ToSchema, Serialize, Debug)]
pub struct VersionsResBody {
pub versions: Vec<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unstable_features: BTreeMap<String, bool>,
}
impl VersionsResBody {
pub fn new(versions: Vec<String>) -> Self {
Self {
versions,
unstable_features: BTreeMap::new(),
}
}
pub fn known_versions(&self) -> impl Iterator<Item = MatrixVersion> + DoubleEndedIterator {
self.versions
.iter()
.flat_map(|s| s.parse::<MatrixVersion>())
.map(|v| (v.into_parts(), v))
.collect::<BTreeMap<_, _>>()
.into_values()
}
}
#[derive(ToSchema, Serialize, Debug)]
pub struct CapabilitiesResBody {
pub capabilities: Capabilities,
}
impl CapabilitiesResBody {
pub fn new(capabilities: Capabilities) -> Self {
Self { capabilities }
}
}