1use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub struct GetUserMediaResult {
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub audio_track_id: Option<u64>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub video_track_id: Option<u64>,
38}
39
40#[derive(Debug, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct GetDisplayMediaResult {
44 pub screen_track_id: u64,
45}
46
47#[derive(Debug, Serialize, serde::Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct MeshStatusResult {
51 pub health: String,
52 pub transport: String,
53 pub pending_publishes: u64,
54 pub pending_queries: u64,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub last_error: Option<String>,
57}
58
59#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(rename_all = "camelCase")]
62pub struct MeshObjectPolicy {
63 #[serde(skip_serializing_if = "Option::is_none")]
67 pub expires_at_ms: Option<u64>,
68 #[serde(skip_serializing_if = "Option::is_none")]
71 pub suppress_previews: Option<bool>,
72}
73
74#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "snake_case")]
76pub enum MeshObjectReadReason {
77 Expired,
78 NotFound,
79 PolicyDenied,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
84#[serde(rename_all = "camelCase")]
85pub struct MeshObjectResult {
86 pub data_base64: Option<String>,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub reason: Option<MeshObjectReadReason>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub expires_at_ms: Option<u64>,
91}
92
93impl MeshObjectResult {
94 pub fn found(data_base64: String, expires_at_ms: Option<u64>) -> Self {
95 Self {
96 data_base64: Some(data_base64),
97 reason: None,
98 expires_at_ms,
99 }
100 }
101
102 pub fn unavailable(reason: MeshObjectReadReason, expires_at_ms: Option<u64>) -> Self {
103 Self {
104 data_base64: None,
105 reason: Some(reason),
106 expires_at_ms,
107 }
108 }
109
110 pub fn compat_data(self) -> Option<String> {
111 self.data_base64
112 }
113}
114
115#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
116#[serde(rename_all = "lowercase")]
117pub enum MeshControlMode {
118 Visible,
119 Encrypted,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
124#[serde(rename_all = "camelCase")]
125pub struct MeshControlEnvelope {
126 pub mode: MeshControlMode,
127 pub data_base64: String,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(rename_all = "camelCase")]
133pub struct MeshPrivateObjectRef {
134 pub handle: String,
135 pub capability: String,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub expires_at_ms: Option<u64>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct CrdtJoinResult {
205 pub room_id: String,
206 pub transport: String,
207}
208
209pub const ERR_WALLET_NOT_CONNECTED: &str = "wallet not connected";
213pub const ERR_WALLET_NOT_ENABLED: &str = "wallet not enabled";
214pub const ERR_DATA_PERMISSION: &str = "data permission not granted";
215pub const ERR_MEDIA_PERMISSION: &str = "media permission not granted";
216pub const ERR_CAMERA_PERMISSION: &str = "camera permission not granted";
217pub const ERR_AUDIO_PERMISSION: &str = "audio permission not granted";
218pub const ERR_CHAIN_PERMISSION: &str = "chain permission not granted";
219pub const ERR_STATEMENTS_PERMISSION: &str = "statements permission not granted";
220pub const ERR_PENDING_SIGN: &str = "another signing request is pending";
221pub const ERR_PENDING_SUBMIT: &str = "another submit request is pending";
222pub const ERR_PENDING_CONNECT: &str = "another connect request is pending";
223pub const ERR_ADDRESS_REQUIRED: &str = "address is required";
224pub const ERR_PEER_ADDRESS_EMPTY: &str = "peer address cannot be empty";
225pub const ERR_FILENAME_REQUIRED: &str = "filename is required";
226pub const ERR_INVALID_ELEMENT_ID: &str = "invalid elementId";
227pub const ERR_SCREEN_PERMISSION: &str = "screen capture permission not granted";
228pub const ERR_SCREEN_DENIED_BY_OS: &str = "screen capture denied by operating system";
229pub const ERR_GROUP_TOO_LARGE: &str = "group exceeds maximum peer limit";
230pub const ERR_NO_PENDING_CALL_FOR_PEER: &str = "no pending incoming call from that peer";
231pub const ERR_GROUP_ID_REQUIRED: &str = "groupId is required for group calls";
232pub const ERR_CRDT_ROOM_NOT_FOUND: &str = "crdt room not found";
233pub const ERR_CRDT_ROOM_ID_REQUIRED: &str = "roomId is required";
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 fn json(v: &impl serde::Serialize) -> String {
240 serde_json::to_string(v).unwrap()
241 }
242
243 #[test]
244 fn get_user_media_result_both_tracks_json_shape() {
245 let r = GetUserMediaResult {
246 audio_track_id: Some(1),
247 video_track_id: Some(2),
248 };
249 assert_eq!(json(&r), r#"{"audioTrackId":1,"videoTrackId":2}"#);
250 }
251
252 #[test]
253 fn get_user_media_result_audio_only_omits_video() {
254 let r = GetUserMediaResult {
255 audio_track_id: Some(1),
256 video_track_id: None,
257 };
258 assert_eq!(json(&r), r#"{"audioTrackId":1}"#);
259 }
260
261 #[test]
262 fn get_user_media_result_video_only_omits_audio() {
263 let r = GetUserMediaResult {
264 audio_track_id: None,
265 video_track_id: Some(3),
266 };
267 assert_eq!(json(&r), r#"{"videoTrackId":3}"#);
268 }
269
270 #[test]
271 fn get_display_media_result_json_shape() {
272 let r = GetDisplayMediaResult {
273 screen_track_id: 42,
274 };
275 assert_eq!(json(&r), r#"{"screenTrackId":42}"#);
276 }
277
278 #[test]
279 fn mesh_status_result_json_shape() {
280 let result = MeshStatusResult {
281 health: "healthy".into(),
282 transport: "statement-store".into(),
283 pending_publishes: 1,
284 pending_queries: 2,
285 last_error: None,
286 };
287 assert_eq!(
288 json(&result),
289 r#"{"health":"healthy","transport":"statement-store","pendingPublishes":1,"pendingQueries":2}"#
290 );
291 }
292
293 #[test]
294 fn mesh_object_policy_json_shape() {
295 let policy = MeshObjectPolicy {
296 expires_at_ms: Some(1710000000000),
297 suppress_previews: Some(true),
298 };
299 assert_eq!(
300 json(&policy),
301 r#"{"expiresAtMs":1710000000000,"suppressPreviews":true}"#
302 );
303 }
304
305 #[test]
306 fn mesh_object_result_positive_json_shape() {
307 let result = MeshObjectResult::found("AQID".into(), Some(1710000000000));
308 assert_eq!(
309 json(&result),
310 r#"{"dataBase64":"AQID","expiresAtMs":1710000000000}"#
311 );
312 }
313
314 #[test]
315 fn mesh_object_result_negative_json_shape() {
316 let result =
317 MeshObjectResult::unavailable(MeshObjectReadReason::Expired, Some(1710000000000));
318 assert_eq!(
319 json(&result),
320 r#"{"dataBase64":null,"reason":"expired","expiresAtMs":1710000000000}"#
321 );
322 }
323
324 #[test]
325 fn mesh_control_envelope_json_shape() {
326 let envelope = MeshControlEnvelope {
327 mode: MeshControlMode::Encrypted,
328 data_base64: "AQID".into(),
329 };
330 assert_eq!(
331 json(&envelope),
332 r#"{"mode":"encrypted","dataBase64":"AQID"}"#
333 );
334 }
335
336 #[test]
337 fn mesh_private_object_ref_json_shape() {
338 let reference = MeshPrivateObjectRef {
339 handle: "mesh-private-handle-1".into(),
340 capability: "mesh-private-capability-1".into(),
341 expires_at_ms: Some(1710000000000),
342 };
343 assert_eq!(
344 json(&reference),
345 r#"{"handle":"mesh-private-handle-1","capability":"mesh-private-capability-1","expiresAtMs":1710000000000}"#
346 );
347 }
348
349 #[test]
350 fn crdt_join_result_json_shape() {
351 let r = CrdtJoinResult {
352 room_id: "doc-abc".into(),
353 transport: "relay".into(),
354 };
355 assert_eq!(json(&r), r#"{"roomId":"doc-abc","transport":"relay"}"#);
356 }
357
358 #[test]
359 fn error_constants_are_non_empty() {
360 let constants = [
361 ERR_WALLET_NOT_CONNECTED,
362 ERR_WALLET_NOT_ENABLED,
363 ERR_DATA_PERMISSION,
364 ERR_MEDIA_PERMISSION,
365 ERR_CAMERA_PERMISSION,
366 ERR_AUDIO_PERMISSION,
367 ERR_CHAIN_PERMISSION,
368 ERR_STATEMENTS_PERMISSION,
369 ERR_PENDING_SIGN,
370 ERR_PENDING_SUBMIT,
371 ERR_PENDING_CONNECT,
372 ERR_ADDRESS_REQUIRED,
373 ERR_PEER_ADDRESS_EMPTY,
374 ERR_FILENAME_REQUIRED,
375 ERR_INVALID_ELEMENT_ID,
376 ERR_SCREEN_PERMISSION,
377 ERR_SCREEN_DENIED_BY_OS,
378 ERR_GROUP_TOO_LARGE,
379 ERR_NO_PENDING_CALL_FOR_PEER,
380 ERR_GROUP_ID_REQUIRED,
381 ERR_CRDT_ROOM_NOT_FOUND,
382 ERR_CRDT_ROOM_ID_REQUIRED,
383 ];
384 for c in &constants {
385 assert!(!c.is_empty());
386 }
387 }
388}