Skip to main content

wavecraft_bridge/
handler.rs

1//! IPC request handler.
2
3use crate::error::BridgeError;
4use crate::host::ParameterHost;
5use serde::Serialize;
6use wavecraft_protocol::{
7    GetAllParametersResult, GetAudioStatusResult, GetMeterFrameResult, GetOscilloscopeFrameResult,
8    GetParameterParams, GetParameterResult, IpcRequest, IpcResponse, METHOD_GET_ALL_PARAMETERS,
9    METHOD_GET_AUDIO_STATUS, METHOD_GET_METER_FRAME, METHOD_GET_OSCILLOSCOPE_FRAME,
10    METHOD_GET_PARAMETER, METHOD_REQUEST_RESIZE, METHOD_SET_PARAMETER, RequestId,
11    RequestResizeParams, RequestResizeResult, SetParameterParams, SetParameterResult,
12};
13
14/// IPC message handler that dispatches requests to a ParameterHost
15pub struct IpcHandler<H: ParameterHost> {
16    host: H,
17}
18
19impl<H: ParameterHost> IpcHandler<H> {
20    /// Create a new IPC handler with the given parameter host
21    pub fn new(host: H) -> Self {
22        Self { host }
23    }
24
25    /// Handle an incoming IPC request and produce a response
26    ///
27    /// This is the main entry point for processing messages from the UI.
28    /// It dispatches to appropriate handlers based on the method name.
29    pub fn handle_request(&self, request: IpcRequest) -> IpcResponse {
30        let result = match request.method.as_str() {
31            METHOD_GET_PARAMETER => self.handle_get_parameter(&request),
32            METHOD_SET_PARAMETER => self.handle_set_parameter(&request),
33            METHOD_GET_ALL_PARAMETERS => self.handle_get_all_parameters(&request),
34            METHOD_GET_METER_FRAME => self.handle_get_meter_frame(&request),
35            METHOD_GET_OSCILLOSCOPE_FRAME => self.handle_get_oscilloscope_frame(&request),
36            METHOD_GET_AUDIO_STATUS => self.handle_get_audio_status(&request),
37            METHOD_REQUEST_RESIZE => self.handle_request_resize(&request),
38            "ping" => self.handle_ping(&request),
39            _ => Err(BridgeError::UnknownMethod(request.method.clone())),
40        };
41
42        match result {
43            Ok(response) => response,
44            Err(err) => IpcResponse::error(request.id, err.to_ipc_error()),
45        }
46    }
47
48    /// Handle a raw JSON string request
49    ///
50    /// Convenience method that parses JSON and dispatches to handle_request.
51    pub fn handle_json(&self, json: &str) -> String {
52        // Parse request
53        let request: IpcRequest = match serde_json::from_str(json) {
54            Ok(req) => req,
55            Err(_e) => {
56                // Can't extract ID from malformed request, use null
57                let response = IpcResponse::error(
58                    RequestId::Number(0),
59                    wavecraft_protocol::IpcError::parse_error(),
60                );
61                // IpcResponse serialization is infallible: all fields are simple types
62                // (RequestId, Option<Value>, Option<IpcError>) that serde_json always handles
63                return serde_json::to_string(&response)
64                    .expect("IpcResponse serialization is infallible");
65            }
66        };
67
68        // Handle request
69        let response = self.handle_request(request);
70
71        // Serialize response - infallible for well-typed IpcResponse
72        serde_json::to_string(&response).expect("IpcResponse serialization is infallible")
73    }
74
75    // ------------------------------------------------------------------------
76    // Method Handlers
77    // ------------------------------------------------------------------------
78
79    fn handle_get_parameter(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
80        // Parse params
81        let params: GetParameterParams = match &request.params {
82            Some(value) => serde_json::from_value(value.clone())?,
83            None => {
84                return Err(BridgeError::InvalidParams {
85                    method: METHOD_GET_PARAMETER.to_string(),
86                    reason: "Missing params".to_string(),
87                });
88            }
89        };
90
91        // Get parameter from host
92        let param_info = self
93            .host
94            .get_parameter(&params.id)
95            .ok_or_else(|| BridgeError::ParameterNotFound(params.id.clone()))?;
96
97        // Build result
98        let result = GetParameterResult {
99            id: param_info.id,
100            value: param_info.value,
101        };
102
103        Ok(IpcResponse::success(request.id.clone(), result))
104    }
105
106    fn handle_set_parameter(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
107        // Parse params
108        let params: SetParameterParams = match &request.params {
109            Some(value) => serde_json::from_value(value.clone())?,
110            None => {
111                return Err(BridgeError::InvalidParams {
112                    method: METHOD_SET_PARAMETER.to_string(),
113                    reason: "Missing params".to_string(),
114                });
115            }
116        };
117
118        // Set parameter
119        self.host.set_parameter(&params.id, params.value)?;
120
121        // Build result (empty success)
122        Ok(IpcResponse::success(
123            request.id.clone(),
124            SetParameterResult {},
125        ))
126    }
127
128    fn handle_get_all_parameters(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
129        let parameters = self.host.get_all_parameters();
130
131        let result = GetAllParametersResult { parameters };
132
133        Ok(IpcResponse::success(request.id.clone(), result))
134    }
135
136    fn handle_get_meter_frame(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
137        // Get meter frame from host
138        let frame = self.host.get_meter_frame();
139
140        let result = GetMeterFrameResult { frame };
141
142        Ok(IpcResponse::success(request.id.clone(), result))
143    }
144
145    fn handle_get_oscilloscope_frame(
146        &self,
147        request: &IpcRequest,
148    ) -> Result<IpcResponse, BridgeError> {
149        let frame = self.host.get_oscilloscope_frame();
150
151        let result = GetOscilloscopeFrameResult { frame };
152
153        Ok(IpcResponse::success(request.id.clone(), result))
154    }
155
156    fn handle_request_resize(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
157        // Parse params
158        let params: RequestResizeParams = match &request.params {
159            Some(value) => serde_json::from_value(value.clone())?,
160            None => {
161                return Err(BridgeError::InvalidParams {
162                    method: METHOD_REQUEST_RESIZE.to_string(),
163                    reason: "Missing params".to_string(),
164                });
165            }
166        };
167
168        // Request resize from host
169        let accepted = self.host.request_resize(params.width, params.height);
170
171        let result = RequestResizeResult { accepted };
172
173        Ok(IpcResponse::success(request.id.clone(), result))
174    }
175
176    fn handle_get_audio_status(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
177        let result = GetAudioStatusResult {
178            status: self.host.get_audio_status(),
179        };
180
181        Ok(IpcResponse::success(request.id.clone(), result))
182    }
183
184    fn handle_ping(&self, request: &IpcRequest) -> Result<IpcResponse, BridgeError> {
185        // Simple ping/pong for testing connectivity
186        #[derive(Serialize)]
187        struct PingResult {
188            pong: bool,
189        }
190
191        Ok(IpcResponse::success(
192            request.id.clone(),
193            PingResult { pong: true },
194        ))
195    }
196}
197
198// ============================================================================
199// Tests
200// ============================================================================
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use wavecraft_protocol::{
206        AudioRuntimePhase, AudioRuntimeStatus, MeterFrame, OscilloscopeFrame, ParameterInfo,
207        ParameterType, RequestId,
208    };
209
210    // Mock ParameterHost for testing
211    struct MockHost {
212        params: Vec<ParameterInfo>,
213    }
214
215    impl MockHost {
216        fn new() -> Self {
217            Self {
218                params: vec![
219                    ParameterInfo {
220                        id: "gain".to_string(),
221                        name: "Gain".to_string(),
222                        param_type: ParameterType::Float,
223                        value: 0.5,
224                        default: 0.7,
225                        min: 0.0,
226                        max: 1.0,
227                        unit: Some("dB".to_string()),
228                        group: None,
229                        variants: None,
230                    },
231                    ParameterInfo {
232                        id: "bypass".to_string(),
233                        name: "Bypass".to_string(),
234                        param_type: ParameterType::Bool,
235                        value: 0.0,
236                        default: 0.0,
237                        min: 0.0,
238                        max: 1.0,
239                        unit: None,
240                        group: None,
241                        variants: None,
242                    },
243                ],
244            }
245        }
246    }
247
248    impl ParameterHost for MockHost {
249        fn get_parameter(&self, id: &str) -> Option<ParameterInfo> {
250            self.params.iter().find(|p| p.id == id).cloned()
251        }
252
253        fn set_parameter(&self, id: &str, value: f32) -> Result<(), BridgeError> {
254            let Some(param) = self.params.iter().find(|p| p.id == id) else {
255                return Err(BridgeError::ParameterNotFound(id.to_string()));
256            };
257
258            if !(param.min..=param.max).contains(&value) {
259                return Err(BridgeError::ParameterOutOfRange {
260                    id: id.to_string(),
261                    value,
262                });
263            }
264
265            // In real implementation, would update atomic value
266            Ok(())
267        }
268
269        fn get_all_parameters(&self) -> Vec<ParameterInfo> {
270            self.params.clone()
271        }
272
273        fn get_meter_frame(&self) -> Option<MeterFrame> {
274            // Mock returns None
275            None
276        }
277
278        fn get_oscilloscope_frame(&self) -> Option<OscilloscopeFrame> {
279            None
280        }
281
282        fn request_resize(&self, _width: u32, _height: u32) -> bool {
283            // Mock always accepts resize requests
284            true
285        }
286
287        fn get_audio_status(&self) -> Option<AudioRuntimeStatus> {
288            Some(AudioRuntimeStatus {
289                phase: AudioRuntimePhase::RunningFullDuplex,
290                diagnostic: None,
291                sample_rate: Some(44100.0),
292                buffer_size: Some(512),
293                updated_at_ms: 123,
294            })
295        }
296    }
297
298    #[test]
299    fn test_get_parameter_success() {
300        let handler = IpcHandler::new(MockHost::new());
301
302        let request = IpcRequest::new(
303            RequestId::Number(1),
304            METHOD_GET_PARAMETER,
305            Some(serde_json::json!({"id": "gain"})),
306        );
307
308        let response = handler.handle_request(request);
309
310        assert!(response.result.is_some());
311        assert!(response.error.is_none());
312
313        let result: GetParameterResult = serde_json::from_value(response.result.unwrap()).unwrap();
314        assert_eq!(result.id, "gain");
315        assert_eq!(result.value, 0.5);
316    }
317
318    #[test]
319    fn test_get_parameter_not_found() {
320        let handler = IpcHandler::new(MockHost::new());
321
322        let request = IpcRequest::new(
323            RequestId::Number(2),
324            METHOD_GET_PARAMETER,
325            Some(serde_json::json!({"id": "unknown"})),
326        );
327
328        let response = handler.handle_request(request);
329
330        assert!(response.error.is_some());
331        assert!(response.result.is_none());
332
333        let error = response.error.unwrap();
334        assert_eq!(error.code, wavecraft_protocol::ERROR_PARAM_NOT_FOUND);
335    }
336
337    #[test]
338    fn test_set_parameter_success() {
339        let handler = IpcHandler::new(MockHost::new());
340
341        let request = IpcRequest::new(
342            RequestId::Number(3),
343            METHOD_SET_PARAMETER,
344            Some(serde_json::json!({"id": "gain", "value": 0.8})),
345        );
346
347        let response = handler.handle_request(request);
348
349        assert!(response.result.is_some());
350        assert!(response.error.is_none());
351    }
352
353    #[test]
354    fn test_set_parameter_out_of_range() {
355        let handler = IpcHandler::new(MockHost::new());
356
357        let request = IpcRequest::new(
358            RequestId::Number(4),
359            METHOD_SET_PARAMETER,
360            Some(serde_json::json!({"id": "gain", "value": 1.5})),
361        );
362
363        let response = handler.handle_request(request);
364
365        assert!(response.error.is_some());
366        assert!(response.result.is_none());
367
368        let error = response.error.unwrap();
369        assert_eq!(error.code, wavecraft_protocol::ERROR_PARAM_OUT_OF_RANGE);
370    }
371
372    #[test]
373    fn test_unknown_method() {
374        let handler = IpcHandler::new(MockHost::new());
375
376        let request = IpcRequest::new(RequestId::Number(5), "unknownMethod", None);
377
378        let response = handler.handle_request(request);
379
380        assert!(response.error.is_some());
381        let error = response.error.unwrap();
382        assert_eq!(error.code, wavecraft_protocol::ERROR_METHOD_NOT_FOUND);
383    }
384
385    #[test]
386    fn test_ping() {
387        let handler = IpcHandler::new(MockHost::new());
388
389        let request = IpcRequest::new(RequestId::String("ping-1".to_string()), "ping", None);
390
391        let response = handler.handle_request(request);
392
393        assert!(response.result.is_some());
394        assert!(response.error.is_none());
395    }
396
397    #[test]
398    fn test_get_all_parameters() {
399        let handler = IpcHandler::new(MockHost::new());
400
401        let request = IpcRequest::new(RequestId::Number(6), METHOD_GET_ALL_PARAMETERS, None);
402
403        let response = handler.handle_request(request);
404
405        assert!(response.result.is_some());
406
407        let result: GetAllParametersResult =
408            serde_json::from_value(response.result.unwrap()).unwrap();
409        assert_eq!(result.parameters.len(), 2);
410    }
411
412    #[test]
413    fn test_handle_json() {
414        let handler = IpcHandler::new(MockHost::new());
415
416        let json = r#"{"jsonrpc":"2.0","id":1,"method":"getParameter","params":{"id":"gain"}}"#;
417        let response_json = handler.handle_json(json);
418
419        assert!(response_json.contains("\"result\""));
420        assert!(!response_json.contains("\"error\""));
421    }
422
423    #[test]
424    fn test_handle_json_parse_error() {
425        let handler = IpcHandler::new(MockHost::new());
426
427        let json = r#"{"invalid json"#;
428        let response_json = handler.handle_json(json);
429
430        assert!(response_json.contains("\"error\""));
431    }
432
433    #[test]
434    fn test_get_audio_status() {
435        let handler = IpcHandler::new(MockHost::new());
436
437        let request = IpcRequest::new(RequestId::Number(7), METHOD_GET_AUDIO_STATUS, None);
438
439        let response = handler.handle_request(request);
440        assert!(response.result.is_some());
441
442        let result: GetAudioStatusResult =
443            serde_json::from_value(response.result.expect("audio status response should exist"))
444                .expect("audio status result should deserialize");
445        let status = result.status.expect("status should be present");
446        assert_eq!(status.phase, AudioRuntimePhase::RunningFullDuplex);
447    }
448
449    #[test]
450    fn test_get_oscilloscope_frame_none() {
451        let handler = IpcHandler::new(MockHost::new());
452
453        let request = IpcRequest::new(RequestId::Number(8), METHOD_GET_OSCILLOSCOPE_FRAME, None);
454
455        let response = handler.handle_request(request);
456        assert!(response.result.is_some());
457
458        let result: GetOscilloscopeFrameResult =
459            serde_json::from_value(response.result.expect("oscilloscope response should exist"))
460                .expect("oscilloscope result should deserialize");
461        assert!(result.frame.is_none());
462    }
463}