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