use std::str::FromStr;
use ruma_common::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
use serde::{
de::{self, Deserialize, Deserializer, Unexpected},
Serialize, Serializer,
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(clippy::exhaustive_structs)]
pub struct CallMemberStateKey {
key: CallMemberStateKeyEnum,
raw: Box<str>,
}
impl CallMemberStateKey {
pub fn new(user_id: OwnedUserId, device_id: Option<OwnedDeviceId>, underscore: bool) -> Self {
CallMemberStateKeyEnum::new(user_id, device_id, underscore).into()
}
pub fn user_id(&self) -> &UserId {
match &self.key {
CallMemberStateKeyEnum::UnderscoreUserDevice(u, _) => u,
CallMemberStateKeyEnum::UserDevice(u, _) => u,
CallMemberStateKeyEnum::User(u) => u,
}
}
pub fn device_id(&self) -> Option<&DeviceId> {
match &self.key {
CallMemberStateKeyEnum::UnderscoreUserDevice(_, d) => Some(d),
CallMemberStateKeyEnum::UserDevice(_, d) => Some(d),
CallMemberStateKeyEnum::User(_) => None,
}
}
}
impl AsRef<str> for CallMemberStateKey {
fn as_ref(&self) -> &str {
&self.raw
}
}
impl From<CallMemberStateKeyEnum> for CallMemberStateKey {
fn from(value: CallMemberStateKeyEnum) -> Self {
let raw = value.to_string().into();
Self { key: value, raw }
}
}
impl FromStr for CallMemberStateKey {
type Err = KeyParseError;
fn from_str(state_key: &str) -> Result<Self, Self::Err> {
Ok(Self { key: CallMemberStateKeyEnum::from_str(state_key)?, raw: state_key.into() })
}
}
impl<'de> Deserialize<'de> for CallMemberStateKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = ruma_common::serde::deserialize_cow_str(deserializer)?;
Self::from_str(&s).map_err(|err| de::Error::invalid_value(Unexpected::Str(&s), &err))
}
}
impl Serialize for CallMemberStateKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum CallMemberStateKeyEnum {
UnderscoreUserDevice(OwnedUserId, OwnedDeviceId),
UserDevice(OwnedUserId, OwnedDeviceId),
User(OwnedUserId),
}
impl CallMemberStateKeyEnum {
fn new(user_id: OwnedUserId, device_id: Option<OwnedDeviceId>, underscore: bool) -> Self {
match (device_id, underscore) {
(Some(device_id), true) => {
CallMemberStateKeyEnum::UnderscoreUserDevice(user_id, device_id)
}
(Some(device_id), false) => CallMemberStateKeyEnum::UserDevice(user_id, device_id),
(None, _) => CallMemberStateKeyEnum::User(user_id),
}
}
}
impl std::fmt::Display for CallMemberStateKeyEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
CallMemberStateKeyEnum::UnderscoreUserDevice(u, d) => write!(f, "_{u}_{d}"),
CallMemberStateKeyEnum::UserDevice(u, d) => write!(f, "{u}_{d}"),
CallMemberStateKeyEnum::User(u) => f.write_str(u.as_str()),
}
}
}
impl FromStr for CallMemberStateKeyEnum {
type Err = KeyParseError;
fn from_str(state_key: &str) -> Result<Self, Self::Err> {
let (state_key, underscore) = match state_key.strip_prefix('_') {
Some(s) => (s, true),
None => (state_key, false),
};
let Some(colon_idx) = state_key.find(':') else {
return Err(KeyParseError::InvalidUser {
user_id: state_key.to_owned(),
error: ruma_common::IdParseError::MissingColon,
});
};
let (user_id, device_id) = match state_key[colon_idx + 1..].find('_') {
None => {
return match UserId::parse(state_key) {
Ok(user_id) => {
if underscore {
Err(KeyParseError::LeadingUnderscoreNoDevice)
} else {
Ok(CallMemberStateKeyEnum::new(user_id, None, underscore))
}
}
Err(err) => Err(KeyParseError::InvalidUser {
error: err,
user_id: state_key.to_owned(),
}),
}
}
Some(suffix_idx) => {
(&state_key[..colon_idx + 1 + suffix_idx], &state_key[colon_idx + 2 + suffix_idx..])
}
};
match (UserId::parse(user_id), OwnedDeviceId::from(device_id)) {
(Ok(user_id), device_id) => {
if device_id.as_str().is_empty() {
return Err(KeyParseError::EmptyDevice);
};
Ok(CallMemberStateKeyEnum::new(user_id, Some(device_id), underscore))
}
(Err(err), _) => {
Err(KeyParseError::InvalidUser { user_id: user_id.to_owned(), error: err })
}
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum KeyParseError {
#[error("uses a malformatted UserId in the UserId defined section.")]
InvalidUser {
user_id: String,
error: ruma_common::IdParseError,
},
#[error("uses a leading underscore but no trailing device id. The part after the underscore is a valid user id.")]
LeadingUnderscoreNoDevice,
#[error("uses an empty device id. (UserId with trailing underscore)")]
EmptyDevice,
}
impl de::Expected for KeyParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "correct call member event key format. The provided string, {})", self)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::call::member::{member_state_key::CallMemberStateKeyEnum, CallMemberStateKey};
#[test]
fn convert_state_key_enum_to_state_key() {
let key = "_@user:domain.org_DEVICE";
let state_key_enum = CallMemberStateKeyEnum::from_str(key).unwrap();
let state_key: CallMemberStateKey = state_key_enum.into();
assert_eq!(state_key.as_ref(), key);
let state_key_direct = CallMemberStateKey::from_str(state_key.as_ref()).unwrap();
assert_eq!(state_key, state_key_direct);
}
}