#[path = "ipc/envelope.rs"]
mod envelope;
#[path = "ipc/errors.rs"]
mod errors;
#[path = "ipc/methods.rs"]
mod methods;
pub use envelope::{IpcNotification, IpcRequest, IpcResponse, RequestId};
pub use errors::{
ERROR_INTERNAL, ERROR_INVALID_PARAMS, ERROR_INVALID_REQUEST, ERROR_METHOD_NOT_FOUND,
ERROR_PARAM_NOT_FOUND, ERROR_PARAM_OUT_OF_RANGE, ERROR_PARSE, IpcError,
};
pub use methods::{
AudioDiagnostic, AudioDiagnosticCode, AudioRuntimePhase, AudioRuntimeStatus,
GetAllParametersResult, GetAudioStatusResult, GetMeterFrameResult, GetOscilloscopeFrameResult,
GetParameterParams, GetParameterResult, METHOD_GET_ALL_PARAMETERS, METHOD_GET_AUDIO_STATUS,
METHOD_GET_METER_FRAME, METHOD_GET_OSCILLOSCOPE_FRAME, METHOD_GET_PARAMETER,
METHOD_REGISTER_AUDIO, METHOD_REQUEST_RESIZE, METHOD_SET_PARAMETER, MeterFrame,
MeterUpdateNotification, NOTIFICATION_AUDIO_STATUS_CHANGED, NOTIFICATION_METER_UPDATE,
NOTIFICATION_PARAMETER_CHANGED, OscilloscopeChannelView, OscilloscopeFrame,
OscilloscopeTriggerMode, ParameterChangedNotification, ParameterInfo, ParameterType,
ProcessorInfo, RegisterAudioParams, RegisterAudioResult, RequestResizeParams,
RequestResizeResult, SetParameterParams, SetParameterResult,
};
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
use serde::ser::Error as _;
struct FailingSerialize;
impl Serialize for FailingSerialize {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Err(S::Error::custom("intentional serialization failure"))
}
}
#[test]
fn test_request_serialization() {
let req = IpcRequest::new(
RequestId::Number(1),
METHOD_GET_PARAMETER,
Some(serde_json::json!({"id": "gain"})),
);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"method\":\"getParameter\""));
}
#[test]
fn test_response_serialization() {
let resp = IpcResponse::success(
RequestId::Number(1),
GetParameterResult {
id: "gain".to_string(),
value: 0.5,
},
);
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"result\""));
assert!(!json.contains("\"error\""));
}
#[test]
fn test_error_response() {
let resp = IpcResponse::error(
RequestId::String("test".to_string()),
IpcError::method_not_found("unknownMethod"),
);
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("\"error\""));
assert!(!json.contains("\"result\""));
}
#[test]
fn test_notification_serialization() {
let notif = IpcNotification::new(
NOTIFICATION_PARAMETER_CHANGED,
ParameterChangedNotification {
id: "gain".to_string(),
value: 0.8,
},
);
let json = serde_json::to_string(¬if).unwrap();
println!("Notification JSON: {}", json);
assert!(json.contains("\"jsonrpc\":\"2.0\""));
assert!(json.contains("\"method\":\"parameterChanged\""));
}
#[test]
fn test_register_audio_serialization() {
let req = IpcRequest::new(
RequestId::String("audio-1".to_string()),
METHOD_REGISTER_AUDIO,
Some(serde_json::json!({
"client_id": "dev-audio",
"sample_rate": 44100.0,
"buffer_size": 512
})),
);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"method\":\"registerAudio\""));
assert!(json.contains("\"sample_rate\":44100"));
}
#[test]
fn test_meter_update_notification() {
let notif = IpcNotification::new(
NOTIFICATION_METER_UPDATE,
MeterUpdateNotification {
timestamp_us: 1000,
left_peak: 0.5,
left_rms: 0.3,
right_peak: 0.6,
right_rms: 0.4,
},
);
let json = serde_json::to_string(¬if).unwrap();
assert!(json.contains("\"method\":\"meterUpdate\""));
assert!(json.contains("\"left_peak\":0.5"));
}
#[test]
fn test_audio_status_serialization() {
let result = GetAudioStatusResult {
status: Some(AudioRuntimeStatus {
phase: AudioRuntimePhase::RunningFullDuplex,
diagnostic: None,
sample_rate: Some(44100.0),
buffer_size: Some(512),
updated_at_ms: 123,
}),
};
let json = serde_json::to_string(&result).expect("status result should serialize");
assert!(json.contains("\"phase\":\"runningFullDuplex\""));
assert!(json.contains("\"sample_rate\":44100"));
}
#[test]
fn test_oscilloscope_frame_serialization() {
let result = GetOscilloscopeFrameResult {
frame: Some(OscilloscopeFrame {
points_l: vec![0.0; 1024],
points_r: vec![0.0; 1024],
sample_rate: 44100.0,
timestamp: 7,
no_signal: true,
trigger_mode: OscilloscopeTriggerMode::RisingZeroCrossing,
}),
};
let json = serde_json::to_string(&result).expect("oscilloscope result should serialize");
assert!(json.contains("\"sample_rate\":44100"));
assert!(json.contains("\"trigger_mode\":\"risingZeroCrossing\""));
}
#[test]
fn parameter_info_with_variants_serializes_correctly() {
let info = ParameterInfo {
id: "osc_waveform".to_string(),
name: "Waveform".to_string(),
param_type: ParameterType::Enum,
value: 0.0,
default: 0.0,
min: 0.0,
max: 3.0,
unit: None,
group: None,
variants: Some(vec![
"Sine".to_string(),
"Square".to_string(),
"Saw".to_string(),
"Triangle".to_string(),
]),
};
let json = serde_json::to_string(&info).expect("parameter info should serialize");
assert!(json.contains("\"variants\""));
let deserialized: ParameterInfo =
serde_json::from_str(&json).expect("parameter info should deserialize");
assert_eq!(
deserialized.variants.expect("variants should exist").len(),
4
);
}
#[test]
fn parameter_info_without_variants_omits_field() {
let info = ParameterInfo {
id: "gain".to_string(),
name: "Gain".to_string(),
param_type: ParameterType::Float,
value: 0.5,
default: 0.5,
min: 0.0,
max: 1.0,
unit: Some("dB".to_string()),
group: None,
variants: None,
};
let json = serde_json::to_string(&info).expect("parameter info should serialize");
assert!(!json.contains("\"variants\""));
}
#[test]
fn try_success_returns_error_on_serialize_failure() {
let result = IpcResponse::try_success(RequestId::Number(1), FailingSerialize);
assert!(result.is_err());
}
#[test]
fn success_does_not_panic_on_serialize_failure() {
let response = IpcResponse::success(RequestId::Number(1), FailingSerialize);
assert!(response.result.is_none());
assert!(response.error.is_some());
}
#[test]
fn try_notification_returns_error_on_serialize_failure() {
let result = IpcNotification::try_new(NOTIFICATION_PARAMETER_CHANGED, FailingSerialize);
assert!(result.is_err());
}
#[test]
fn notification_new_does_not_panic_on_serialize_failure() {
let notification = IpcNotification::new(NOTIFICATION_PARAMETER_CHANGED, FailingSerialize);
assert!(notification.params.is_none());
}
#[test]
fn try_with_data_returns_error_on_serialize_failure() {
let result = IpcError::try_with_data(ERROR_INTERNAL, "test", FailingSerialize);
assert!(result.is_err());
}
#[test]
fn with_data_does_not_panic_on_serialize_failure() {
let error = IpcError::with_data(ERROR_INTERNAL, "test", FailingSerialize);
assert!(error.data.is_none());
assert_eq!(error.message, "test");
}
}