1use std::borrow::Cow;
23
24use serde::{Deserialize, Serialize};
25use serde_json::Value;
26
27pub type JsonPayload = Value;
33
34#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
43#[serde(untagged)]
44pub enum UserId {
45 Integer(i64),
46 String(String),
47}
48
49impl From<i64> for UserId {
50 fn from(value: i64) -> Self {
51 Self::Integer(value)
52 }
53}
54
55impl From<&str> for UserId {
56 fn from(value: &str) -> Self {
57 Self::String(value.to_owned())
58 }
59}
60
61impl From<String> for UserId {
62 fn from(value: String) -> Self {
63 Self::String(value)
64 }
65}
66
67impl UserId {
68 #[must_use]
71 pub fn path_segment(&self) -> Cow<'_, str> {
72 match self {
73 Self::Integer(value) => Cow::Owned(value.to_string()),
74 Self::String(value) => Cow::Borrowed(value),
75 }
76 }
77
78 #[must_use]
85 pub fn normalized_for_runtime(self) -> Self {
86 match self {
87 Self::String(value) => value
88 .parse::<i64>()
89 .map_or(Self::String(value), Self::Integer),
90 Self::Integer(value) => Self::Integer(value),
91 }
92 }
93
94 #[must_use]
99 pub fn runtime_normalized(&self) -> Self {
100 self.clone().normalized_for_runtime()
101 }
102}
103
104#[allow(
111 clippy::struct_excessive_bools,
112 reason = "feature flags mirror the compatibility startup surface with explicit optional room capabilities"
113)]
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct AvailableFeatures {
117 pub rtc: bool,
119 pub transcription: bool,
120 pub audio_recording: bool,
121 pub video_recording: bool,
122}
123
124#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct RecordingState {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub recording: Option<bool>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub audio: Option<bool>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub transcription: Option<bool>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub video: Option<bool>,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
149pub enum StopCode {
150 #[serde(rename = "user_request")]
151 UserRequest,
152 #[serde(rename = "channel_closed")]
153 ChannelClosed,
154 #[serde(rename = "recording_timeout")]
155 RecordingTimeout,
156 #[serde(rename = "recording_failed")]
157 RecordingFailed,
158 #[serde(rename = "disk_space_exhausted")]
159 DiskSpaceExhausted,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167pub struct RecordingStateUpdate {
168 pub state: RecordingState,
169 #[serde(rename = "stopCode", skip_serializing_if = "Option::is_none")]
171 pub stop_code: Option<StopCode>,
172}
173
174#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct UserPermissions {
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub transcription: Option<bool>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub audio_recording: Option<bool>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub video_recording: Option<bool>,
187}
188
189#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct UserInfo {
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub is_talking: Option<bool>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub is_featured: Option<bool>,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub is_camera_on: Option<bool>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub is_screen_sharing_on: Option<bool>,
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub is_self_muted: Option<bool>,
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub is_deaf: Option<bool>,
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub is_raising_hand: Option<bool>,
213}
214
215impl UserInfo {
216 #[must_use]
222 pub fn snapshot_complete(self) -> Self {
223 Self {
224 is_talking: Some(self.is_talking.unwrap_or(false)),
225 is_featured: Some(self.is_featured.unwrap_or(false)),
226 is_camera_on: Some(self.is_camera_on.unwrap_or(false)),
227 is_screen_sharing_on: Some(self.is_screen_sharing_on.unwrap_or(false)),
228 is_self_muted: Some(self.is_self_muted.unwrap_or(false)),
229 is_deaf: Some(self.is_deaf.unwrap_or(false)),
230 is_raising_hand: Some(self.is_raising_hand.unwrap_or(false)),
231 }
232 }
233
234 pub fn apply_partial_update(&mut self, update: &Self) {
239 if let Some(is_talking) = update.is_talking {
240 self.is_talking = Some(is_talking);
241 }
242 if let Some(is_featured) = update.is_featured {
243 self.is_featured = Some(is_featured);
244 }
245 if let Some(is_camera_on) = update.is_camera_on {
246 self.is_camera_on = Some(is_camera_on);
247 }
248 if let Some(is_screen_sharing_on) = update.is_screen_sharing_on {
249 self.is_screen_sharing_on = Some(is_screen_sharing_on);
250 }
251 if let Some(is_self_muted) = update.is_self_muted {
252 self.is_self_muted = Some(is_self_muted);
253 }
254 if let Some(is_deaf) = update.is_deaf {
255 self.is_deaf = Some(is_deaf);
256 }
257 if let Some(is_raising_hand) = update.is_raising_hand {
258 self.is_raising_hand = Some(is_raising_hand);
259 }
260 }
261
262 #[must_use]
264 pub fn with_featured(mut self, is_featured: Option<bool>) -> Self {
265 self.is_featured = is_featured;
266 self
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
276pub struct PeerSnapshot {
277 #[serde(rename = "sessionId")]
278 pub user_id: UserId,
279 #[serde(default)]
280 pub info: UserInfo,
281}
282
283#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
289pub struct DownloadStates {
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub audio: Option<bool>,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub camera: Option<bool>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub screen: Option<bool>,
296 #[serde(rename = "cameraLayout", skip_serializing_if = "Option::is_none")]
297 pub camera_layout: Option<VideoLayoutIntent>,
298 #[serde(rename = "screenLayout", skip_serializing_if = "Option::is_none")]
299 pub screen_layout: Option<VideoLayoutIntent>,
300}
301
302impl DownloadStates {
303 pub fn apply_partial_update(&mut self, update: &Self) {
304 *self = Self {
305 audio: update.audio.or(self.audio),
306 camera: update.camera.or(self.camera),
307 screen: update.screen.or(self.screen),
308 camera_layout: update.camera_layout.or(self.camera_layout),
309 screen_layout: update.screen_layout.or(self.screen_layout),
310 };
311 }
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(rename_all = "snake_case")]
321pub enum VideoLayoutIntent {
322 Featured,
324 Pinned,
326 VisibleThumbnail,
328 Hidden,
330 Overflow,
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
343pub enum StreamType {
344 #[serde(rename = "audio")]
345 Audio,
346 #[serde(rename = "camera")]
347 Camera,
348 #[serde(rename = "screen")]
349 Screen,
350}
351
352#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
358pub struct RecordingOptions {
359 #[serde(skip_serializing_if = "Option::is_none")]
360 pub audio: Option<bool>,
361 #[serde(skip_serializing_if = "Option::is_none")]
362 pub transcription: Option<bool>,
363 #[serde(skip_serializing_if = "Option::is_none")]
364 pub video: Option<bool>,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373#[repr(u16)]
374pub enum WebSocketCloseCode {
375 Clean = 1000,
377 Leaving = 1001,
379 ProtocolError = 1002,
381 Error = 1011,
383
384 AuthFailed = 4106,
386 AuthTimeout = 4107,
388 Kicked = 4108,
390 RoomFull = 4109,
392}
393
394impl WebSocketCloseCode {
395 #[must_use]
401 pub const fn from_u16(value: u16) -> Option<Self> {
402 match value {
403 1000 => Some(Self::Clean),
404 1001 => Some(Self::Leaving),
405 1002 => Some(Self::ProtocolError),
406 1011 => Some(Self::Error),
407 4106 => Some(Self::AuthFailed),
408 4107 => Some(Self::AuthTimeout),
409 4108 => Some(Self::Kicked),
410 4109 => Some(Self::RoomFull),
411 _ => None,
412 }
413 }
414}
415
416impl From<WebSocketCloseCode> for u16 {
417 fn from(value: WebSocketCloseCode) -> Self {
418 match value {
419 WebSocketCloseCode::Clean => 1000,
420 WebSocketCloseCode::Leaving => 1001,
421 WebSocketCloseCode::ProtocolError => 1002,
422 WebSocketCloseCode::Error => 1011,
423 WebSocketCloseCode::AuthFailed => 4106,
424 WebSocketCloseCode::AuthTimeout => 4107,
425 WebSocketCloseCode::Kicked => 4108,
426 WebSocketCloseCode::RoomFull => 4109,
427 }
428 }
429}
430
431#[cfg(test)]
432#[path = "TESTS/lib.rs"]
433mod tests;