1use std::str::FromStr;
2
3use ruma_common::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
4use serde::{
5 de::{self, Deserialize, Deserializer, Unexpected},
6 Serialize, Serializer,
7};
8#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
11#[allow(clippy::exhaustive_structs)]
12pub struct CallMemberStateKey {
13 key: CallMemberStateKeyEnum,
14 raw: Box<str>,
15}
16
17impl CallMemberStateKey {
18 pub fn new(user_id: OwnedUserId, device_id: Option<OwnedDeviceId>, underscore: bool) -> Self {
28 CallMemberStateKeyEnum::new(user_id, device_id, underscore).into()
29 }
30
31 pub fn user_id(&self) -> &UserId {
35 match &self.key {
36 CallMemberStateKeyEnum::UnderscoreUserDevice(u, _) => u,
37 CallMemberStateKeyEnum::UserDevice(u, _) => u,
38 CallMemberStateKeyEnum::User(u) => u,
39 }
40 }
41
42 pub fn device_id(&self) -> Option<&DeviceId> {
46 match &self.key {
47 CallMemberStateKeyEnum::UnderscoreUserDevice(_, d) => Some(d),
48 CallMemberStateKeyEnum::UserDevice(_, d) => Some(d),
49 CallMemberStateKeyEnum::User(_) => None,
50 }
51 }
52}
53
54impl AsRef<str> for CallMemberStateKey {
55 fn as_ref(&self) -> &str {
56 &self.raw
57 }
58}
59
60impl From<CallMemberStateKeyEnum> for CallMemberStateKey {
61 fn from(value: CallMemberStateKeyEnum) -> Self {
62 let raw = value.to_string().into();
63 Self { key: value, raw }
64 }
65}
66
67impl FromStr for CallMemberStateKey {
68 type Err = KeyParseError;
69
70 fn from_str(state_key: &str) -> Result<Self, Self::Err> {
71 Ok(Self { key: CallMemberStateKeyEnum::from_str(state_key)?, raw: state_key.into() })
74 }
75}
76
77impl<'de> Deserialize<'de> for CallMemberStateKey {
78 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79 where
80 D: Deserializer<'de>,
81 {
82 let s = ruma_common::serde::deserialize_cow_str(deserializer)?;
83 Self::from_str(&s).map_err(|err| de::Error::invalid_value(Unexpected::Str(&s), &err))
84 }
85}
86
87impl Serialize for CallMemberStateKey {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 serializer.serialize_str(self.as_ref())
93 }
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
98enum CallMemberStateKeyEnum {
99 UnderscoreUserDevice(OwnedUserId, OwnedDeviceId),
100 UserDevice(OwnedUserId, OwnedDeviceId),
101 User(OwnedUserId),
102}
103
104impl CallMemberStateKeyEnum {
105 fn new(user_id: OwnedUserId, device_id: Option<OwnedDeviceId>, underscore: bool) -> Self {
106 match (device_id, underscore) {
107 (Some(device_id), true) => {
108 CallMemberStateKeyEnum::UnderscoreUserDevice(user_id, device_id)
109 }
110 (Some(device_id), false) => CallMemberStateKeyEnum::UserDevice(user_id, device_id),
111 (None, _) => CallMemberStateKeyEnum::User(user_id),
112 }
113 }
114}
115
116impl std::fmt::Display for CallMemberStateKeyEnum {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match &self {
119 CallMemberStateKeyEnum::UnderscoreUserDevice(u, d) => write!(f, "_{u}_{d}"),
120 CallMemberStateKeyEnum::UserDevice(u, d) => write!(f, "{u}_{d}"),
121 CallMemberStateKeyEnum::User(u) => f.write_str(u.as_str()),
122 }
123 }
124}
125
126impl FromStr for CallMemberStateKeyEnum {
127 type Err = KeyParseError;
128
129 fn from_str(state_key: &str) -> Result<Self, Self::Err> {
130 let (state_key, underscore) = match state_key.strip_prefix('_') {
133 Some(s) => (s, true),
134 None => (state_key, false),
135 };
136
137 let Some(colon_idx) = state_key.find(':') else {
139 return Err(KeyParseError::InvalidUser {
140 user_id: state_key.to_owned(),
141 error: ruma_common::IdParseError::MissingColon,
142 });
143 };
144
145 let (user_id, device_id) = match state_key[colon_idx + 1..].find('_') {
146 None => {
147 return match UserId::parse(state_key) {
148 Ok(user_id) => {
149 if underscore {
150 Err(KeyParseError::LeadingUnderscoreNoDevice)
151 } else {
152 Ok(CallMemberStateKeyEnum::new(user_id, None, underscore))
153 }
154 }
155 Err(err) => Err(KeyParseError::InvalidUser {
156 error: err,
157 user_id: state_key.to_owned(),
158 }),
159 }
160 }
161 Some(suffix_idx) => {
162 (&state_key[..colon_idx + 1 + suffix_idx], &state_key[colon_idx + 2 + suffix_idx..])
163 }
164 };
165
166 match (UserId::parse(user_id), OwnedDeviceId::from(device_id)) {
167 (Ok(user_id), device_id) => {
168 if device_id.as_str().is_empty() {
169 return Err(KeyParseError::EmptyDevice);
170 };
171 Ok(CallMemberStateKeyEnum::new(user_id, Some(device_id), underscore))
172 }
173 (Err(err), _) => {
174 Err(KeyParseError::InvalidUser { user_id: user_id.to_owned(), error: err })
175 }
176 }
177 }
178}
179
180#[derive(Debug, thiserror::Error)]
182#[non_exhaustive]
183pub enum KeyParseError {
184 #[error("uses a malformatted UserId in the UserId defined section.")]
186 InvalidUser {
187 user_id: String,
189 error: ruma_common::IdParseError,
191 },
192 #[error("uses a leading underscore but no trailing device id. The part after the underscore is a valid user id.")]
195 LeadingUnderscoreNoDevice,
196 #[error("uses an empty device id. (UserId with trailing underscore)")]
198 EmptyDevice,
199}
200
201impl de::Expected for KeyParseError {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "correct call member event key format. The provided string, {})", self)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use std::str::FromStr;
210
211 use crate::call::member::{member_state_key::CallMemberStateKeyEnum, CallMemberStateKey};
212
213 #[test]
214 fn convert_state_key_enum_to_state_key() {
215 let key = "_@user:domain.org_DEVICE";
216 let state_key_enum = CallMemberStateKeyEnum::from_str(key).unwrap();
217 let state_key: CallMemberStateKey = state_key_enum.into();
219 assert_eq!(state_key.as_ref(), key);
221 let state_key_direct = CallMemberStateKey::from_str(state_key.as_ref()).unwrap();
223 assert_eq!(state_key, state_key_direct);
224 }
225}