1use std::str::FromStr;
2
3use ruma_common::{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, member_id: Option<String>, underscore: bool) -> Self {
30 CallMemberStateKeyEnum::new(user_id, member_id, underscore).into()
31 }
32
33 pub fn user_id(&self) -> &UserId {
40 match &self.key {
41 CallMemberStateKeyEnum::UnderscoreMemberId(u, _) => u,
42 CallMemberStateKeyEnum::MemberId(u, _) => u,
43 CallMemberStateKeyEnum::User(u) => u,
44 }
45 }
46}
47
48impl AsRef<str> for CallMemberStateKey {
49 fn as_ref(&self) -> &str {
50 &self.raw
51 }
52}
53
54impl From<CallMemberStateKeyEnum> for CallMemberStateKey {
55 fn from(value: CallMemberStateKeyEnum) -> Self {
56 let raw = value.to_string().into();
57 Self { key: value, raw }
58 }
59}
60
61impl FromStr for CallMemberStateKey {
62 type Err = KeyParseError;
63
64 fn from_str(state_key: &str) -> Result<Self, Self::Err> {
65 Ok(Self { key: CallMemberStateKeyEnum::from_str(state_key)?, raw: state_key.into() })
68 }
69}
70
71impl<'de> Deserialize<'de> for CallMemberStateKey {
72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 let s = ruma_common::serde::deserialize_cow_str(deserializer)?;
77 Self::from_str(&s).map_err(|err| de::Error::invalid_value(Unexpected::Str(&s), &err))
78 }
79}
80
81impl Serialize for CallMemberStateKey {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: Serializer,
85 {
86 serializer.serialize_str(self.as_ref())
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
92enum CallMemberStateKeyEnum {
93 UnderscoreMemberId(OwnedUserId, String),
94 MemberId(OwnedUserId, String),
95 User(OwnedUserId),
96}
97
98impl CallMemberStateKeyEnum {
99 fn new(user_id: OwnedUserId, unique_member_id: Option<String>, underscore: bool) -> Self {
100 match (unique_member_id, underscore) {
101 (Some(member_id), true) => {
102 CallMemberStateKeyEnum::UnderscoreMemberId(user_id, member_id)
103 }
104 (Some(member_id), false) => CallMemberStateKeyEnum::MemberId(user_id, member_id),
105 (None, _) => CallMemberStateKeyEnum::User(user_id),
106 }
107 }
108}
109
110impl std::fmt::Display for CallMemberStateKeyEnum {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match &self {
113 CallMemberStateKeyEnum::UnderscoreMemberId(u, d) => write!(f, "_{u}_{d}"),
114 CallMemberStateKeyEnum::MemberId(u, d) => write!(f, "{u}_{d}"),
115 CallMemberStateKeyEnum::User(u) => f.write_str(u.as_str()),
116 }
117 }
118}
119
120impl FromStr for CallMemberStateKeyEnum {
121 type Err = KeyParseError;
122
123 fn from_str(state_key: &str) -> Result<Self, Self::Err> {
124 let (state_key, has_underscore) = match state_key.strip_prefix('_') {
127 Some(s) => (s, true),
128 None => (state_key, false),
129 };
130
131 let Some(colon_idx) = state_key.find(':') else {
133 return Err(KeyParseError::InvalidUser {
134 user_id: state_key.to_owned(),
135 error: ruma_common::IdParseError::MissingColon,
136 });
137 };
138
139 let (user_id, member_id) = match state_key[colon_idx + 1..].find('_') {
140 None => {
141 return match UserId::parse(state_key) {
142 Ok(user_id) => {
143 if has_underscore {
144 Err(KeyParseError::LeadingUnderscoreNoMemberId)
145 } else {
146 Ok(CallMemberStateKeyEnum::new(user_id, None, has_underscore))
147 }
148 }
149 Err(err) => Err(KeyParseError::InvalidUser {
150 error: err,
151 user_id: state_key.to_owned(),
152 }),
153 };
154 }
155 Some(suffix_idx) => {
156 (&state_key[..colon_idx + 1 + suffix_idx], &state_key[colon_idx + 2 + suffix_idx..])
157 }
158 };
159
160 match UserId::parse(user_id) {
161 Ok(user_id) => {
162 if member_id.is_empty() {
163 return Err(KeyParseError::EmptyMemberId);
164 }
165 Ok(CallMemberStateKeyEnum::new(user_id, Some(member_id.to_owned()), has_underscore))
166 }
167 Err(err) => Err(KeyParseError::InvalidUser { user_id: user_id.to_owned(), error: err }),
168 }
169 }
170}
171
172#[derive(Debug, thiserror::Error)]
174#[non_exhaustive]
175pub enum KeyParseError {
176 #[error("uses a malformatted UserId in the UserId defined section.")]
178 InvalidUser {
179 user_id: String,
181 error: ruma_common::IdParseError,
183 },
184 #[error(
187 "uses a leading underscore but no trailing device id. The part after the underscore is a valid user id."
188 )]
189 LeadingUnderscoreNoMemberId,
190 #[error("uses an empty memberId. (UserId with trailing underscore)")]
192 EmptyMemberId,
193}
194
195impl de::Expected for KeyParseError {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 write!(f, "correct call member event key format. The provided string, {self})")
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use std::str::FromStr;
204
205 use ruma_common::user_id;
206
207 use crate::call::member::{member_state_key::CallMemberStateKeyEnum, CallMemberStateKey};
208
209 #[test]
210 fn convert_state_key_enum_to_state_key() {
211 let key = "_@user:domain.org_ABC";
212 let state_key_enum = CallMemberStateKeyEnum::from_str(key).unwrap();
213 let state_key: CallMemberStateKey = state_key_enum.into();
215 assert_eq!(state_key.as_ref(), key);
217 let state_key_direct = CallMemberStateKey::new(
219 user_id!("@user:domain.org").to_owned(),
220 Some("ABC".to_owned()),
221 true,
222 );
223 assert_eq!(state_key, state_key_direct);
224 }
225
226 #[test]
227 fn convert_no_underscore_state_key_without_member_id() {
228 let key = "@user:domain.org";
229 let state_key_enum = CallMemberStateKeyEnum::from_str(key).unwrap();
230 let state_key: CallMemberStateKey = state_key_enum.into();
232 assert_eq!(state_key.as_ref(), key);
234 let state_key_direct =
236 CallMemberStateKey::new(user_id!("@user:domain.org").to_owned(), None, false);
237 assert_eq!(state_key, state_key_direct);
238 }
239
240 #[test]
241 fn convert_no_underscore_state_key_with_member_id() {
242 let key = "@user:domain.org_ABC_m.callTestId";
243 let state_key_enum = CallMemberStateKeyEnum::from_str(key).unwrap();
244 let state_key: CallMemberStateKey = state_key_enum.into();
246 assert_eq!(state_key.as_ref(), key);
248 let state_key_direct = CallMemberStateKey::new(
250 user_id!("@user:domain.org").to_owned(),
251 Some("ABC_m.callTestId".to_owned()),
252 false,
253 );
254
255 assert_eq!(state_key, state_key_direct);
256 }
257}