1use 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
15pub struct IpcHandler<H: ParameterHost> {
17 host: H,
18}
19
20impl<H: ParameterHost> IpcHandler<H> {
21 pub fn new(host: H) -> Self {
23 Self { host }
24 }
25
26 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 pub fn handle_json(&self, json: &str) -> String {
53 let request: IpcRequest = match serde_json::from_str(json) {
55 Ok(req) => req,
56 Err(_e) => {
57 let response = IpcResponse::error(
59 RequestId::Number(0),
60 wavecraft_protocol::IpcError::parse_error(),
61 );
62 return serde_json::to_string(&response)
65 .expect("IpcResponse serialization is infallible");
66 }
67 };
68
69 let response = self.handle_request(request);
71
72 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 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 let param_info = self
103 .host
104 .get_parameter(¶ms.id)
105 .ok_or_else(|| BridgeError::ParameterNotFound(params.id.clone()))?;
106
107 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 self.host.set_parameter(¶ms.id, params.value)?;
122
123 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 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 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 #[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#[cfg(test)]
197mod tests {
198 use super::*;
199 use wavecraft_protocol::{
200 AudioRuntimePhase, AudioRuntimeStatus, MeterFrame, OscilloscopeFrame, ParameterInfo,
201 ParameterType, RequestId,
202 };
203
204 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 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 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 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}