1use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct IpcRequest {
24 pub jsonrpc: String,
26 pub id: RequestId,
28 pub method: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub params: Option<serde_json::Value>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct IpcResponse {
38 pub jsonrpc: String,
40 pub id: RequestId,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub result: Option<serde_json::Value>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub error: Option<IpcError>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct IpcNotification {
53 pub jsonrpc: String,
55 pub method: String,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub params: Option<serde_json::Value>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64#[serde(untagged)]
65pub enum RequestId {
66 String(String),
67 Number(i64),
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct IpcError {
73 pub code: i32,
75 pub message: String,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub data: Option<serde_json::Value>,
80}
81
82pub const ERROR_PARSE: i32 = -32700;
88pub const ERROR_INVALID_REQUEST: i32 = -32600;
90pub const ERROR_METHOD_NOT_FOUND: i32 = -32601;
92pub const ERROR_INVALID_PARAMS: i32 = -32602;
94pub const ERROR_INTERNAL: i32 = -32603;
96
97pub const ERROR_PARAM_NOT_FOUND: i32 = -32000;
100pub const ERROR_PARAM_OUT_OF_RANGE: i32 = -32001;
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct GetParameterParams {
114 pub id: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct GetParameterResult {
121 pub id: String,
123 pub value: f32,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct SetParameterParams {
134 pub id: String,
136 pub value: f32,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct SetParameterResult {}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct GetAllParametersResult {
151 pub parameters: Vec<ParameterInfo>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ParameterInfo {
158 pub id: String,
160 pub name: String,
162 #[serde(rename = "type")]
164 pub param_type: ParameterType,
165 pub value: f32,
167 pub default: f32,
169 pub min: f32,
171 pub max: f32,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub unit: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub group: Option<String>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "lowercase")]
184pub enum ParameterType {
185 Float,
186 Bool,
187 Enum,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ParameterChangedNotification {
197 pub id: String,
199 pub value: f32,
201}
202
203pub const METHOD_GET_PARAMETER: &str = "getParameter";
209pub const METHOD_SET_PARAMETER: &str = "setParameter";
211pub const METHOD_GET_ALL_PARAMETERS: &str = "getAllParameters";
213pub const METHOD_GET_METER_FRAME: &str = "getMeterFrame";
215pub const METHOD_GET_AUDIO_STATUS: &str = "getAudioStatus";
217pub const METHOD_REQUEST_RESIZE: &str = "requestResize";
219pub const METHOD_REGISTER_AUDIO: &str = "registerAudio";
221pub const NOTIFICATION_PARAMETER_CHANGED: &str = "parameterChanged";
223pub const NOTIFICATION_METER_UPDATE: &str = "meterUpdate";
225pub const NOTIFICATION_AUDIO_STATUS_CHANGED: &str = "audioStatusChanged";
227
228impl IpcRequest {
233 pub fn new(
235 id: RequestId,
236 method: impl Into<String>,
237 params: Option<serde_json::Value>,
238 ) -> Self {
239 Self {
240 jsonrpc: "2.0".to_string(),
241 id,
242 method: method.into(),
243 params,
244 }
245 }
246}
247
248impl IpcResponse {
249 pub fn success(id: RequestId, result: impl Serialize) -> Self {
251 Self {
252 jsonrpc: "2.0".to_string(),
253 id,
254 result: Some(serde_json::to_value(result).unwrap()),
255 error: None,
256 }
257 }
258
259 pub fn error(id: RequestId, error: IpcError) -> Self {
261 Self {
262 jsonrpc: "2.0".to_string(),
263 id,
264 result: None,
265 error: Some(error),
266 }
267 }
268}
269
270impl IpcNotification {
271 pub fn new(method: impl Into<String>, params: impl Serialize) -> Self {
273 Self {
274 jsonrpc: "2.0".to_string(),
275 method: method.into(),
276 params: Some(serde_json::to_value(params).unwrap()),
277 }
278 }
279}
280
281impl IpcError {
282 pub fn new(code: i32, message: impl Into<String>) -> Self {
284 Self {
285 code,
286 message: message.into(),
287 data: None,
288 }
289 }
290
291 pub fn with_data(code: i32, message: impl Into<String>, data: impl Serialize) -> Self {
293 Self {
294 code,
295 message: message.into(),
296 data: Some(serde_json::to_value(data).unwrap()),
297 }
298 }
299
300 pub fn parse_error() -> Self {
302 Self::new(ERROR_PARSE, "Parse error")
303 }
304
305 pub fn invalid_request(reason: impl Into<String>) -> Self {
307 Self::new(
308 ERROR_INVALID_REQUEST,
309 format!("Invalid request: {}", reason.into()),
310 )
311 }
312
313 pub fn method_not_found(method: impl AsRef<str>) -> Self {
315 Self::new(
316 ERROR_METHOD_NOT_FOUND,
317 format!("Method not found: {}", method.as_ref()),
318 )
319 }
320
321 pub fn invalid_params(reason: impl Into<String>) -> Self {
323 Self::new(
324 ERROR_INVALID_PARAMS,
325 format!("Invalid params: {}", reason.into()),
326 )
327 }
328
329 pub fn internal_error(reason: impl Into<String>) -> Self {
331 Self::new(ERROR_INTERNAL, format!("Internal error: {}", reason.into()))
332 }
333
334 pub fn param_not_found(id: impl AsRef<str>) -> Self {
336 Self::new(
337 ERROR_PARAM_NOT_FOUND,
338 format!("Parameter not found: {}", id.as_ref()),
339 )
340 }
341
342 pub fn param_out_of_range(id: impl AsRef<str>, value: f32) -> Self {
344 Self::new(
345 ERROR_PARAM_OUT_OF_RANGE,
346 format!("Parameter '{}' value {} out of range", id.as_ref(), value),
347 )
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_request_serialization() {
357 let req = IpcRequest::new(
358 RequestId::Number(1),
359 METHOD_GET_PARAMETER,
360 Some(serde_json::json!({"id": "gain"})),
361 );
362
363 let json = serde_json::to_string(&req).unwrap();
364 assert!(json.contains("\"jsonrpc\":\"2.0\""));
365 assert!(json.contains("\"method\":\"getParameter\""));
366 }
367
368 #[test]
369 fn test_response_serialization() {
370 let resp = IpcResponse::success(
371 RequestId::Number(1),
372 GetParameterResult {
373 id: "gain".to_string(),
374 value: 0.5,
375 },
376 );
377
378 let json = serde_json::to_string(&resp).unwrap();
379 assert!(json.contains("\"jsonrpc\":\"2.0\""));
380 assert!(json.contains("\"result\""));
381 assert!(!json.contains("\"error\""));
382 }
383
384 #[test]
385 fn test_error_response() {
386 let resp = IpcResponse::error(
387 RequestId::String("test".to_string()),
388 IpcError::method_not_found("unknownMethod"),
389 );
390
391 let json = serde_json::to_string(&resp).unwrap();
392 assert!(json.contains("\"error\""));
393 assert!(!json.contains("\"result\""));
394 }
395
396 #[test]
397 fn test_notification_serialization() {
398 let notif = IpcNotification::new(
399 NOTIFICATION_PARAMETER_CHANGED,
400 ParameterChangedNotification {
401 id: "gain".to_string(),
402 value: 0.8,
403 },
404 );
405
406 let json = serde_json::to_string(¬if).unwrap();
407 println!("Notification JSON: {}", json);
408 assert!(json.contains("\"jsonrpc\":\"2.0\""));
409 assert!(json.contains("\"method\":\"parameterChanged\""));
410 }
413
414 #[test]
415 fn test_register_audio_serialization() {
416 let req = IpcRequest::new(
417 RequestId::String("audio-1".to_string()),
418 METHOD_REGISTER_AUDIO,
419 Some(serde_json::json!({
420 "client_id": "dev-audio",
421 "sample_rate": 44100.0,
422 "buffer_size": 512
423 })),
424 );
425
426 let json = serde_json::to_string(&req).unwrap();
427 assert!(json.contains("\"method\":\"registerAudio\""));
428 assert!(json.contains("\"sample_rate\":44100"));
429 }
430
431 #[test]
432 fn test_meter_update_notification() {
433 let notif = IpcNotification::new(
434 NOTIFICATION_METER_UPDATE,
435 MeterUpdateNotification {
436 timestamp_us: 1000,
437 left_peak: 0.5,
438 left_rms: 0.3,
439 right_peak: 0.6,
440 right_rms: 0.4,
441 },
442 );
443
444 let json = serde_json::to_string(¬if).unwrap();
445 assert!(json.contains("\"method\":\"meterUpdate\""));
446 assert!(json.contains("\"left_peak\":0.5"));
447 }
448
449 #[test]
450 fn test_audio_status_serialization() {
451 let result = GetAudioStatusResult {
452 status: Some(AudioRuntimeStatus {
453 phase: AudioRuntimePhase::RunningFullDuplex,
454 diagnostic: None,
455 sample_rate: Some(44100.0),
456 buffer_size: Some(512),
457 updated_at_ms: 123,
458 }),
459 };
460
461 let json = serde_json::to_string(&result).expect("status result should serialize");
462 assert!(json.contains("\"phase\":\"runningFullDuplex\""));
463 assert!(json.contains("\"sample_rate\":44100"));
464 }
465}
466
467#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
475pub struct MeterFrame {
476 pub peak_l: f32,
478 pub peak_r: f32,
480 pub rms_l: f32,
482 pub rms_r: f32,
484 pub timestamp: u64,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct GetMeterFrameResult {
491 pub frame: Option<MeterFrame>,
493}
494
495#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub enum AudioRuntimePhase {
503 Disabled,
504 Initializing,
505 RunningFullDuplex,
506 RunningInputOnly,
507 Degraded,
508 Failed,
509}
510
511#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub enum AudioDiagnosticCode {
515 LoaderUnavailable,
516 VtableMissing,
517 ProcessorCreateFailed,
518 NoInputDevice,
519 InputPermissionDenied,
520 NoOutputDevice,
521 StreamStartFailed,
522 Unknown,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct AudioDiagnostic {
528 pub code: AudioDiagnosticCode,
530 pub message: String,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub hint: Option<String>,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct AudioRuntimeStatus {
540 pub phase: AudioRuntimePhase,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub diagnostic: Option<AudioDiagnostic>,
545 #[serde(skip_serializing_if = "Option::is_none")]
547 pub sample_rate: Option<f32>,
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub buffer_size: Option<u32>,
551 pub updated_at_ms: u64,
553}
554
555#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct GetAudioStatusResult {
558 pub status: Option<AudioRuntimeStatus>,
560}
561
562#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct RequestResizeParams {
569 pub width: u32,
571 pub height: u32,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct RequestResizeResult {
578 pub accepted: bool,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
588pub struct RegisterAudioParams {
589 pub client_id: String,
591 pub sample_rate: f32,
593 pub buffer_size: u32,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct RegisterAudioResult {
600 pub status: String,
602}
603
604#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct MeterUpdateNotification {
611 pub timestamp_us: u64,
613 pub left_peak: f32,
615 pub left_rms: f32,
617 pub right_peak: f32,
619 pub right_rms: f32,
621}