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
140pub const ERR_WALLET_NOT_CONNECTED: &str = "wallet not connected";
199pub const ERR_WALLET_NOT_ENABLED: &str = "wallet not enabled";
200pub const ERR_DATA_PERMISSION: &str = "data permission not granted";
201pub const ERR_MEDIA_PERMISSION: &str = "media permission not granted";
202pub const ERR_CAMERA_PERMISSION: &str = "camera permission not granted";
203pub const ERR_AUDIO_PERMISSION: &str = "audio permission not granted";
204pub const ERR_CHAIN_PERMISSION: &str = "chain permission not granted";
205pub const ERR_STATEMENTS_PERMISSION: &str = "statements permission not granted";
206pub const ERR_PENDING_SIGN: &str = "another signing request is pending";
207pub const ERR_PENDING_SUBMIT: &str = "another submit request is pending";
208pub const ERR_PENDING_CONNECT: &str = "another connect request is pending";
209pub const ERR_ADDRESS_REQUIRED: &str = "address is required";
210pub const ERR_PEER_ADDRESS_EMPTY: &str = "peer address cannot be empty";
211pub const ERR_FILENAME_REQUIRED: &str = "filename is required";
212pub const ERR_INVALID_ELEMENT_ID: &str = "invalid elementId";
213pub const ERR_SCREEN_PERMISSION: &str = "screen capture permission not granted";
214pub const ERR_SCREEN_DENIED_BY_OS: &str = "screen capture denied by operating system";
215pub const ERR_GROUP_TOO_LARGE: &str = "group exceeds maximum peer limit";
216pub const ERR_NO_PENDING_CALL_FOR_PEER: &str = "no pending incoming call from that peer";
217pub const ERR_GROUP_ID_REQUIRED: &str = "groupId is required for group calls";
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 fn json(v: &impl serde::Serialize) -> String {
224 serde_json::to_string(v).unwrap()
225 }
226
227 #[test]
228 fn get_user_media_result_both_tracks_json_shape() {
229 let r = GetUserMediaResult {
230 audio_track_id: Some(1),
231 video_track_id: Some(2),
232 };
233 assert_eq!(json(&r), r#"{"audioTrackId":1,"videoTrackId":2}"#);
234 }
235
236 #[test]
237 fn get_user_media_result_audio_only_omits_video() {
238 let r = GetUserMediaResult {
239 audio_track_id: Some(1),
240 video_track_id: None,
241 };
242 assert_eq!(json(&r), r#"{"audioTrackId":1}"#);
243 }
244
245 #[test]
246 fn get_user_media_result_video_only_omits_audio() {
247 let r = GetUserMediaResult {
248 audio_track_id: None,
249 video_track_id: Some(3),
250 };
251 assert_eq!(json(&r), r#"{"videoTrackId":3}"#);
252 }
253
254 #[test]
255 fn get_display_media_result_json_shape() {
256 let r = GetDisplayMediaResult {
257 screen_track_id: 42,
258 };
259 assert_eq!(json(&r), r#"{"screenTrackId":42}"#);
260 }
261
262 #[test]
263 fn mesh_status_result_json_shape() {
264 let result = MeshStatusResult {
265 health: "healthy".into(),
266 transport: "statement-store".into(),
267 pending_publishes: 1,
268 pending_queries: 2,
269 last_error: None,
270 };
271 assert_eq!(
272 json(&result),
273 r#"{"health":"healthy","transport":"statement-store","pendingPublishes":1,"pendingQueries":2}"#
274 );
275 }
276
277 #[test]
278 fn mesh_object_policy_json_shape() {
279 let policy = MeshObjectPolicy {
280 expires_at_ms: Some(1710000000000),
281 suppress_previews: Some(true),
282 };
283 assert_eq!(
284 json(&policy),
285 r#"{"expiresAtMs":1710000000000,"suppressPreviews":true}"#
286 );
287 }
288
289 #[test]
290 fn mesh_object_result_positive_json_shape() {
291 let result = MeshObjectResult::found("AQID".into(), Some(1710000000000));
292 assert_eq!(
293 json(&result),
294 r#"{"dataBase64":"AQID","expiresAtMs":1710000000000}"#
295 );
296 }
297
298 #[test]
299 fn mesh_object_result_negative_json_shape() {
300 let result =
301 MeshObjectResult::unavailable(MeshObjectReadReason::Expired, Some(1710000000000));
302 assert_eq!(
303 json(&result),
304 r#"{"dataBase64":null,"reason":"expired","expiresAtMs":1710000000000}"#
305 );
306 }
307
308 #[test]
309 fn mesh_control_envelope_json_shape() {
310 let envelope = MeshControlEnvelope {
311 mode: MeshControlMode::Encrypted,
312 data_base64: "AQID".into(),
313 };
314 assert_eq!(
315 json(&envelope),
316 r#"{"mode":"encrypted","dataBase64":"AQID"}"#
317 );
318 }
319
320 #[test]
321 fn mesh_private_object_ref_json_shape() {
322 let reference = MeshPrivateObjectRef {
323 handle: "mesh-private-handle-1".into(),
324 capability: "mesh-private-capability-1".into(),
325 expires_at_ms: Some(1710000000000),
326 };
327 assert_eq!(
328 json(&reference),
329 r#"{"handle":"mesh-private-handle-1","capability":"mesh-private-capability-1","expiresAtMs":1710000000000}"#
330 );
331 }
332
333 #[test]
334 fn error_constants_are_non_empty() {
335 let constants = [
336 ERR_WALLET_NOT_CONNECTED,
337 ERR_WALLET_NOT_ENABLED,
338 ERR_DATA_PERMISSION,
339 ERR_MEDIA_PERMISSION,
340 ERR_CAMERA_PERMISSION,
341 ERR_AUDIO_PERMISSION,
342 ERR_CHAIN_PERMISSION,
343 ERR_STATEMENTS_PERMISSION,
344 ERR_PENDING_SIGN,
345 ERR_PENDING_SUBMIT,
346 ERR_PENDING_CONNECT,
347 ERR_ADDRESS_REQUIRED,
348 ERR_PEER_ADDRESS_EMPTY,
349 ERR_FILENAME_REQUIRED,
350 ERR_INVALID_ELEMENT_ID,
351 ERR_SCREEN_PERMISSION,
352 ERR_SCREEN_DENIED_BY_OS,
353 ERR_GROUP_TOO_LARGE,
354 ERR_NO_PENDING_CALL_FOR_PEER,
355 ERR_GROUP_ID_REQUIRED,
356 ];
357 for c in &constants {
358 assert!(!c.is_empty());
359 }
360 }
361}