bbx_net/websocket/
protocol.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Deserialize)]
7#[serde(tag = "type")]
8pub enum ClientMessage {
9 #[serde(rename = "join")]
11 Join {
12 room_code: String,
13 client_name: Option<String>,
14 },
15
16 #[serde(rename = "param")]
18 Parameter {
19 param: String,
20 value: f32,
21 #[serde(default)]
23 at: Option<u64>,
24 },
25
26 #[serde(rename = "trigger")]
28 Trigger {
29 name: String,
30 #[serde(default)]
31 at: Option<u64>,
32 },
33
34 #[serde(rename = "sync")]
36 Sync,
37
38 #[serde(rename = "ping")]
40 Ping { client_time: u64 },
41
42 #[serde(rename = "leave")]
44 Leave,
45}
46
47#[derive(Debug, Clone, Serialize)]
49#[serde(tag = "type")]
50pub enum ServerMessage {
51 #[serde(rename = "welcome")]
53 Welcome { node_id: String, server_time: u64 },
54
55 #[serde(rename = "state")]
57 State { params: Vec<ParamState> },
58
59 #[serde(rename = "update")]
61 Update { param: String, value: f32 },
62
63 #[serde(rename = "pong")]
65 Pong { client_time: u64, server_time: u64 },
66
67 #[serde(rename = "error")]
69 Error { code: String, message: String },
70
71 #[serde(rename = "closed")]
73 RoomClosed,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ParamState {
79 pub name: String,
80 pub value: f32,
81 #[serde(default)]
82 pub min: f32,
83 #[serde(default = "default_max")]
84 pub max: f32,
85}
86
87fn default_max() -> f32 {
88 1.0
89}
90
91impl ParamState {
92 pub fn new(name: impl Into<String>, value: f32) -> Self {
93 Self {
94 name: name.into(),
95 value,
96 min: 0.0,
97 max: 1.0,
98 }
99 }
100
101 pub fn with_range(mut self, min: f32, max: f32) -> Self {
102 self.min = min;
103 self.max = max;
104 self
105 }
106}
107
108impl ServerMessage {
109 pub fn error(code: impl Into<String>, message: impl Into<String>) -> Self {
111 Self::Error {
112 code: code.into(),
113 message: message.into(),
114 }
115 }
116
117 pub fn invalid_room_code() -> Self {
119 Self::error("INVALID_ROOM", "Invalid room code")
120 }
121
122 pub fn room_full() -> Self {
124 Self::error("ROOM_FULL", "Room is at capacity")
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_client_message_join() {
134 let json = r#"{"type": "join", "room_code": "123456"}"#;
135 let msg: ClientMessage = serde_json::from_str(json).unwrap();
136
137 match msg {
138 ClientMessage::Join { room_code, .. } => {
139 assert_eq!(room_code, "123456");
140 }
141 _ => panic!("Expected Join message"),
142 }
143 }
144
145 #[test]
146 fn test_client_message_param() {
147 let json = r#"{"type": "param", "param": "gain", "value": 0.5}"#;
148 let msg: ClientMessage = serde_json::from_str(json).unwrap();
149
150 match msg {
151 ClientMessage::Parameter { param, value, at } => {
152 assert_eq!(param, "gain");
153 assert!((value - 0.5).abs() < f32::EPSILON);
154 assert!(at.is_none());
155 }
156 _ => panic!("Expected Parameter message"),
157 }
158 }
159
160 #[test]
161 fn test_client_message_param_with_time() {
162 let json = r#"{"type": "param", "param": "freq", "value": 440.0, "at": 1000000}"#;
163 let msg: ClientMessage = serde_json::from_str(json).unwrap();
164
165 match msg {
166 ClientMessage::Parameter { at, .. } => {
167 assert_eq!(at, Some(1000000));
168 }
169 _ => panic!("Expected Parameter message"),
170 }
171 }
172
173 #[test]
174 fn test_client_message_trigger() {
175 let json = r#"{"type": "trigger", "name": "note_on"}"#;
176 let msg: ClientMessage = serde_json::from_str(json).unwrap();
177
178 match msg {
179 ClientMessage::Trigger { name, .. } => {
180 assert_eq!(name, "note_on");
181 }
182 _ => panic!("Expected Trigger message"),
183 }
184 }
185
186 #[test]
187 fn test_client_message_ping() {
188 let json = r#"{"type": "ping", "client_time": 12345}"#;
189 let msg: ClientMessage = serde_json::from_str(json).unwrap();
190
191 match msg {
192 ClientMessage::Ping { client_time } => {
193 assert_eq!(client_time, 12345);
194 }
195 _ => panic!("Expected Ping message"),
196 }
197 }
198
199 #[test]
200 fn test_server_message_welcome() {
201 let msg = ServerMessage::Welcome {
202 node_id: "abc-123".to_string(),
203 server_time: 1000,
204 };
205 let json = serde_json::to_string(&msg).unwrap();
206
207 assert!(json.contains("\"type\":\"welcome\""));
208 assert!(json.contains("\"node_id\":\"abc-123\""));
209 }
210
211 #[test]
212 fn test_server_message_state() {
213 let msg = ServerMessage::State {
214 params: vec![
215 ParamState::new("gain", 0.5),
216 ParamState::new("freq", 440.0).with_range(20.0, 20000.0),
217 ],
218 };
219 let json = serde_json::to_string(&msg).unwrap();
220
221 assert!(json.contains("\"type\":\"state\""));
222 assert!(json.contains("\"name\":\"gain\""));
223 }
224
225 #[test]
226 fn test_server_message_error() {
227 let msg = ServerMessage::error("TEST_ERROR", "Test error message");
228 let json = serde_json::to_string(&msg).unwrap();
229
230 assert!(json.contains("\"type\":\"error\""));
231 assert!(json.contains("\"code\":\"TEST_ERROR\""));
232 }
233
234 #[test]
235 fn test_param_state_defaults() {
236 let state = ParamState::new("test", 0.5);
237 assert!((state.min - 0.0).abs() < f32::EPSILON);
238 assert!((state.max - 1.0).abs() < f32::EPSILON);
239 }
240}