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