@zap-js/server 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +18 -14
- package/package.json +5 -10
- package/src/bin/zap.rs +0 -154
- package/src/config.rs +0 -253
- package/src/connection_pool.rs +0 -404
- package/src/error.rs +0 -380
- package/src/handler.rs +0 -89
- package/src/ipc.js +0 -10
- package/src/ipc.rs +0 -499
- package/src/lib.rs +0 -433
- package/src/metrics.rs +0 -264
- package/src/proxy.rs +0 -436
- package/src/reliability.rs +0 -917
- package/src/request.rs +0 -60
- package/src/request_id.rs +0 -97
- package/src/response.rs +0 -182
- package/src/rpc.js +0 -14
- package/src/server.rs +0 -597
- package/src/static.rs +0 -572
- package/src/types.js +0 -21
- package/src/utils.rs +0 -18
- package/src/websocket.rs +0 -429
package/src/ipc.rs
DELETED
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
//! Unix Domain Socket IPC Protocol
|
|
2
|
-
//!
|
|
3
|
-
//! High-performance inter-process communication between TypeScript wrapper and Rust binary.
|
|
4
|
-
//! Protocol: Length-prefixed MessagePack messages (default) with JSON fallback.
|
|
5
|
-
//!
|
|
6
|
-
//! Frame format: [4-byte big-endian length][payload]
|
|
7
|
-
//! - MessagePack: First byte is 0x80-0xBF (map fixmap) or 0xDE-0xDF (map16/32)
|
|
8
|
-
//! - JSON: First byte is '{' (0x7B)
|
|
9
|
-
|
|
10
|
-
use crate::error::{ZapError, ZapResult};
|
|
11
|
-
use serde::{Deserialize, Serialize};
|
|
12
|
-
use std::collections::HashMap;
|
|
13
|
-
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
14
|
-
use tokio::net::UnixStream;
|
|
15
|
-
|
|
16
|
-
/// IPC encoding format
|
|
17
|
-
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
18
|
-
pub enum IpcEncoding {
|
|
19
|
-
/// MessagePack (default, ~40% faster)
|
|
20
|
-
#[default]
|
|
21
|
-
MessagePack,
|
|
22
|
-
/// JSON (for debugging)
|
|
23
|
-
Json,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/// Messages sent over the IPC channel
|
|
27
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
28
|
-
#[serde(tag = "type", rename_all = "snake_case")]
|
|
29
|
-
#[allow(clippy::large_enum_variant)] // IpcRequest is used directly, boxing adds overhead
|
|
30
|
-
pub enum IpcMessage {
|
|
31
|
-
/// TypeScript asks Rust to invoke a handler
|
|
32
|
-
InvokeHandler {
|
|
33
|
-
handler_id: String,
|
|
34
|
-
request: IpcRequest,
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
/// TypeScript responds with handler result
|
|
38
|
-
HandlerResponse {
|
|
39
|
-
handler_id: String,
|
|
40
|
-
status: u16,
|
|
41
|
-
headers: HashMap<String, String>,
|
|
42
|
-
body: String,
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
/// Health check ping from TypeScript
|
|
46
|
-
HealthCheck,
|
|
47
|
-
|
|
48
|
-
/// Health check response from Rust
|
|
49
|
-
HealthCheckResponse,
|
|
50
|
-
|
|
51
|
-
/// Structured error response with full context
|
|
52
|
-
Error {
|
|
53
|
-
/// Machine-readable error code (e.g., "HANDLER_ERROR")
|
|
54
|
-
code: String,
|
|
55
|
-
/// Human-readable error message
|
|
56
|
-
message: String,
|
|
57
|
-
/// HTTP status code
|
|
58
|
-
#[serde(default = "default_error_status")]
|
|
59
|
-
status: u16,
|
|
60
|
-
/// Unique error ID for log correlation
|
|
61
|
-
#[serde(default)]
|
|
62
|
-
digest: String,
|
|
63
|
-
/// Additional error details
|
|
64
|
-
#[serde(skip_serializing_if = "Option::is_none")]
|
|
65
|
-
details: Option<serde_json::Value>,
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
// Phase 8: Streaming support
|
|
69
|
-
/// Start a streaming response
|
|
70
|
-
StreamStart {
|
|
71
|
-
stream_id: String,
|
|
72
|
-
status: u16,
|
|
73
|
-
headers: HashMap<String, String>,
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
/// A chunk of streaming data
|
|
77
|
-
StreamChunk {
|
|
78
|
-
stream_id: String,
|
|
79
|
-
/// Base64-encoded binary data
|
|
80
|
-
data: String,
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/// End of streaming response
|
|
84
|
-
StreamEnd {
|
|
85
|
-
stream_id: String,
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Phase 8: WebSocket support
|
|
89
|
-
/// WebSocket connection opened
|
|
90
|
-
WsConnect {
|
|
91
|
-
connection_id: String,
|
|
92
|
-
handler_id: String,
|
|
93
|
-
path: String,
|
|
94
|
-
headers: HashMap<String, String>,
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/// WebSocket message from client
|
|
98
|
-
WsMessage {
|
|
99
|
-
connection_id: String,
|
|
100
|
-
handler_id: String,
|
|
101
|
-
/// Message data (text or base64-encoded binary)
|
|
102
|
-
data: String,
|
|
103
|
-
/// true if binary data
|
|
104
|
-
binary: bool,
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
/// WebSocket connection closed
|
|
108
|
-
WsClose {
|
|
109
|
-
connection_id: String,
|
|
110
|
-
handler_id: String,
|
|
111
|
-
code: Option<u16>,
|
|
112
|
-
reason: Option<String>,
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
/// WebSocket message to send to client (TypeScript -> Rust)
|
|
116
|
-
WsSend {
|
|
117
|
-
connection_id: String,
|
|
118
|
-
data: String,
|
|
119
|
-
binary: bool,
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
fn default_error_status() -> u16 {
|
|
124
|
-
500
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Serialize an IPC message to bytes
|
|
128
|
-
pub fn serialize_message(msg: &IpcMessage, encoding: IpcEncoding) -> ZapResult<Vec<u8>> {
|
|
129
|
-
match encoding {
|
|
130
|
-
IpcEncoding::MessagePack => {
|
|
131
|
-
// IMPORTANT: Use to_vec_named to preserve string field names
|
|
132
|
-
// This is required for #[serde(tag = "type")] to work correctly
|
|
133
|
-
// with @msgpack/msgpack on the TypeScript side
|
|
134
|
-
rmp_serde::to_vec_named(msg).map_err(|e| ZapError::ipc(format!("MessagePack serialize error: {}", e)))
|
|
135
|
-
}
|
|
136
|
-
IpcEncoding::Json => {
|
|
137
|
-
serde_json::to_vec(msg).map_err(|e| ZapError::ipc(format!("JSON serialize error: {}", e)))
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/// Deserialize an IPC message from bytes, auto-detecting encoding
|
|
143
|
-
pub fn deserialize_message(data: &[u8]) -> ZapResult<IpcMessage> {
|
|
144
|
-
if data.is_empty() {
|
|
145
|
-
return Err(ZapError::ipc("Empty message".to_string()));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Auto-detect encoding from first byte
|
|
149
|
-
let first_byte = data[0];
|
|
150
|
-
if first_byte == b'{' {
|
|
151
|
-
// JSON
|
|
152
|
-
serde_json::from_slice(data).map_err(|e| ZapError::ipc(format!("JSON deserialize error: {}", e)))
|
|
153
|
-
} else {
|
|
154
|
-
// MessagePack (maps start with 0x80-0xBF, 0xDE, or 0xDF)
|
|
155
|
-
rmp_serde::from_slice(data).map_err(|e| ZapError::ipc(format!("MessagePack deserialize error: {}", e)))
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/// Request data sent to TypeScript handler
|
|
160
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
161
|
-
pub struct IpcRequest {
|
|
162
|
-
/// Unique request ID for correlation across Rust/TypeScript boundary
|
|
163
|
-
pub request_id: String,
|
|
164
|
-
|
|
165
|
-
/// HTTP method (GET, POST, etc.)
|
|
166
|
-
pub method: String,
|
|
167
|
-
|
|
168
|
-
/// Full path with query string
|
|
169
|
-
pub path: String,
|
|
170
|
-
|
|
171
|
-
/// Path without query string
|
|
172
|
-
pub path_only: String,
|
|
173
|
-
|
|
174
|
-
/// Query parameters
|
|
175
|
-
pub query: HashMap<String, String>,
|
|
176
|
-
|
|
177
|
-
/// Route parameters (from :id in path)
|
|
178
|
-
pub params: HashMap<String, String>,
|
|
179
|
-
|
|
180
|
-
/// HTTP headers
|
|
181
|
-
pub headers: HashMap<String, String>,
|
|
182
|
-
|
|
183
|
-
/// Request body as UTF-8 string
|
|
184
|
-
pub body: String,
|
|
185
|
-
|
|
186
|
-
/// Cookies parsed from headers
|
|
187
|
-
pub cookies: HashMap<String, String>,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// IPC Server - receives requests from Rust, forwards to TypeScript
|
|
191
|
-
pub struct IpcServer {
|
|
192
|
-
socket_path: String,
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
impl IpcServer {
|
|
196
|
-
/// Create a new IPC server
|
|
197
|
-
pub fn new(socket_path: String) -> Self {
|
|
198
|
-
Self { socket_path }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/// Start listening on the Unix socket
|
|
202
|
-
pub async fn listen(&self) -> ZapResult<()> {
|
|
203
|
-
// Remove existing socket file if it exists
|
|
204
|
-
#[cfg(unix)]
|
|
205
|
-
{
|
|
206
|
-
let _ = std::fs::remove_file(&self.socket_path);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Create Unix socket listener
|
|
210
|
-
let listener = tokio::net::UnixListener::bind(&self.socket_path)
|
|
211
|
-
.map_err(|e| ZapError::ipc(format!("Failed to bind socket: {}", e)))?;
|
|
212
|
-
|
|
213
|
-
tracing::info!("🔌 IPC server listening on {}", self.socket_path);
|
|
214
|
-
|
|
215
|
-
// Accept connections in background
|
|
216
|
-
tokio::spawn(async move {
|
|
217
|
-
loop {
|
|
218
|
-
match listener.accept().await {
|
|
219
|
-
Ok((stream, _)) => {
|
|
220
|
-
tokio::spawn(async move {
|
|
221
|
-
if let Err(e) = handle_ipc_connection(stream).await {
|
|
222
|
-
tracing::error!("IPC connection error: {}", e);
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
Err(e) => {
|
|
227
|
-
tracing::error!("IPC accept error: {}", e);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
Ok(())
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/// IPC Client - connects to TypeScript's IPC server
|
|
238
|
-
pub struct IpcClient {
|
|
239
|
-
stream: UnixStream,
|
|
240
|
-
encoding: IpcEncoding,
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
impl IpcClient {
|
|
244
|
-
/// Connect to a remote IPC server with default MessagePack encoding
|
|
245
|
-
pub async fn connect(socket_path: &str) -> ZapResult<Self> {
|
|
246
|
-
Self::connect_with_encoding(socket_path, IpcEncoding::default()).await
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/// Connect to a remote IPC server with specified encoding
|
|
250
|
-
pub async fn connect_with_encoding(socket_path: &str, encoding: IpcEncoding) -> ZapResult<Self> {
|
|
251
|
-
let stream = UnixStream::connect(socket_path).await.map_err(|e| {
|
|
252
|
-
ZapError::ipc(format!("Failed to connect to IPC socket: {}", e))
|
|
253
|
-
})?;
|
|
254
|
-
|
|
255
|
-
Ok(Self { stream, encoding })
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/// Send a message over the IPC channel using length-prefixed framing
|
|
259
|
-
pub async fn send_message(&mut self, msg: IpcMessage) -> ZapResult<()> {
|
|
260
|
-
let payload = serialize_message(&msg, self.encoding)?;
|
|
261
|
-
let len = payload.len() as u32;
|
|
262
|
-
|
|
263
|
-
// ATOMIC: Combine length prefix and payload into single buffer to prevent frame corruption
|
|
264
|
-
let mut frame = Vec::with_capacity(4 + payload.len());
|
|
265
|
-
frame.extend_from_slice(&len.to_be_bytes());
|
|
266
|
-
frame.extend_from_slice(&payload);
|
|
267
|
-
|
|
268
|
-
// Single atomic write
|
|
269
|
-
self.stream
|
|
270
|
-
.write_all(&frame)
|
|
271
|
-
.await
|
|
272
|
-
.map_err(|e| ZapError::ipc(format!("Write frame error: {}", e)))?;
|
|
273
|
-
|
|
274
|
-
self.stream.flush().await.map_err(|e| {
|
|
275
|
-
ZapError::ipc(format!("Flush error: {}", e))
|
|
276
|
-
})?;
|
|
277
|
-
|
|
278
|
-
Ok(())
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/// Receive a message from the IPC channel using length-prefixed framing
|
|
282
|
-
pub async fn recv_message(&mut self) -> ZapResult<Option<IpcMessage>> {
|
|
283
|
-
// Read 4-byte length prefix
|
|
284
|
-
let mut len_buf = [0u8; 4];
|
|
285
|
-
match self.stream.read_exact(&mut len_buf).await {
|
|
286
|
-
Ok(_) => {}
|
|
287
|
-
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(None),
|
|
288
|
-
Err(e) => return Err(ZapError::ipc(format!("Read length error: {}", e))),
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
let len = u32::from_be_bytes(len_buf) as usize;
|
|
292
|
-
if len > 100 * 1024 * 1024 {
|
|
293
|
-
// 100MB limit
|
|
294
|
-
return Err(ZapError::ipc(format!("Message too large: {} bytes", len)));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Read payload
|
|
298
|
-
let mut buffer = vec![0u8; len];
|
|
299
|
-
self.stream
|
|
300
|
-
.read_exact(&mut buffer)
|
|
301
|
-
.await
|
|
302
|
-
.map_err(|e| ZapError::ipc(format!("Read payload error: {}", e)))?;
|
|
303
|
-
|
|
304
|
-
// Auto-detect encoding and deserialize
|
|
305
|
-
let msg = deserialize_message(&buffer)?;
|
|
306
|
-
|
|
307
|
-
Ok(Some(msg))
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/// Send a message and receive a response (request-response pattern)
|
|
311
|
-
pub async fn send_recv(&mut self, msg: IpcMessage) -> ZapResult<IpcMessage> {
|
|
312
|
-
self.send_message(msg).await?;
|
|
313
|
-
match self.recv_message().await? {
|
|
314
|
-
Some(response) => Ok(response),
|
|
315
|
-
None => Err(ZapError::ipc("Connection closed".to_string())),
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/// Get the encoding being used
|
|
320
|
-
pub fn encoding(&self) -> IpcEncoding {
|
|
321
|
-
self.encoding
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/// Handle an IPC client connection (for future use)
|
|
326
|
-
async fn handle_ipc_connection(mut _stream: UnixStream) -> ZapResult<()> {
|
|
327
|
-
// Currently, the Rust server only initiates connections to TypeScript
|
|
328
|
-
// This handler is here for future bidirectional communication
|
|
329
|
-
Ok(())
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
#[cfg(test)]
|
|
333
|
-
mod tests {
|
|
334
|
-
use super::*;
|
|
335
|
-
|
|
336
|
-
#[test]
|
|
337
|
-
fn test_ipc_message_json_serialization() {
|
|
338
|
-
let msg = IpcMessage::HealthCheck;
|
|
339
|
-
let json = serde_json::to_string(&msg).unwrap();
|
|
340
|
-
assert!(json.contains("health_check"));
|
|
341
|
-
|
|
342
|
-
let decoded: IpcMessage = serde_json::from_str(&json).unwrap();
|
|
343
|
-
matches!(decoded, IpcMessage::HealthCheck);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
#[test]
|
|
347
|
-
fn test_ipc_message_msgpack_serialization() {
|
|
348
|
-
let msg = IpcMessage::HealthCheck;
|
|
349
|
-
let msgpack = serialize_message(&msg, IpcEncoding::MessagePack).unwrap();
|
|
350
|
-
let decoded = deserialize_message(&msgpack).unwrap();
|
|
351
|
-
matches!(decoded, IpcMessage::HealthCheck);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
#[test]
|
|
355
|
-
fn test_ipc_request_json_serialization() {
|
|
356
|
-
let req = IpcRequest {
|
|
357
|
-
request_id: "test-request-123".to_string(),
|
|
358
|
-
method: "GET".to_string(),
|
|
359
|
-
path: "/api/users/123?sort=asc".to_string(),
|
|
360
|
-
path_only: "/api/users/123".to_string(),
|
|
361
|
-
query: {
|
|
362
|
-
let mut m = HashMap::new();
|
|
363
|
-
m.insert("sort".to_string(), "asc".to_string());
|
|
364
|
-
m
|
|
365
|
-
},
|
|
366
|
-
params: {
|
|
367
|
-
let mut m = HashMap::new();
|
|
368
|
-
m.insert("id".to_string(), "123".to_string());
|
|
369
|
-
m
|
|
370
|
-
},
|
|
371
|
-
headers: HashMap::new(),
|
|
372
|
-
body: String::new(),
|
|
373
|
-
cookies: HashMap::new(),
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
let json = serde_json::to_string(&req).unwrap();
|
|
377
|
-
let decoded: IpcRequest = serde_json::from_str(&json).unwrap();
|
|
378
|
-
|
|
379
|
-
assert_eq!(decoded.method, "GET");
|
|
380
|
-
assert_eq!(decoded.path, "/api/users/123?sort=asc");
|
|
381
|
-
assert_eq!(decoded.params.get("id").unwrap(), "123");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
#[test]
|
|
385
|
-
fn test_ipc_request_msgpack_serialization() {
|
|
386
|
-
let req = IpcRequest {
|
|
387
|
-
request_id: "test-request-123".to_string(),
|
|
388
|
-
method: "GET".to_string(),
|
|
389
|
-
path: "/api/users/123".to_string(),
|
|
390
|
-
path_only: "/api/users/123".to_string(),
|
|
391
|
-
query: HashMap::new(),
|
|
392
|
-
params: {
|
|
393
|
-
let mut m = HashMap::new();
|
|
394
|
-
m.insert("id".to_string(), "123".to_string());
|
|
395
|
-
m
|
|
396
|
-
},
|
|
397
|
-
headers: HashMap::new(),
|
|
398
|
-
body: String::new(),
|
|
399
|
-
cookies: HashMap::new(),
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
let msg = IpcMessage::InvokeHandler {
|
|
403
|
-
handler_id: "handler_0".to_string(),
|
|
404
|
-
request: req,
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
let msgpack = serialize_message(&msg, IpcEncoding::MessagePack).unwrap();
|
|
408
|
-
let json = serialize_message(&msg, IpcEncoding::Json).unwrap();
|
|
409
|
-
|
|
410
|
-
// MessagePack should be smaller
|
|
411
|
-
assert!(msgpack.len() < json.len(), "MessagePack ({}) should be smaller than JSON ({})", msgpack.len(), json.len());
|
|
412
|
-
|
|
413
|
-
// Both should deserialize correctly
|
|
414
|
-
let decoded_msgpack = deserialize_message(&msgpack).unwrap();
|
|
415
|
-
let decoded_json = deserialize_message(&json).unwrap();
|
|
416
|
-
|
|
417
|
-
if let (
|
|
418
|
-
IpcMessage::InvokeHandler { request: req1, .. },
|
|
419
|
-
IpcMessage::InvokeHandler { request: req2, .. },
|
|
420
|
-
) = (decoded_msgpack, decoded_json)
|
|
421
|
-
{
|
|
422
|
-
assert_eq!(req1.method, req2.method);
|
|
423
|
-
assert_eq!(req1.path, req2.path);
|
|
424
|
-
} else {
|
|
425
|
-
panic!("Unexpected message types");
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
#[test]
|
|
430
|
-
fn test_auto_detect_encoding() {
|
|
431
|
-
let msg = IpcMessage::HealthCheck;
|
|
432
|
-
|
|
433
|
-
// JSON starts with '{'
|
|
434
|
-
let json = serialize_message(&msg, IpcEncoding::Json).unwrap();
|
|
435
|
-
assert_eq!(json[0], b'{');
|
|
436
|
-
let decoded_json = deserialize_message(&json).unwrap();
|
|
437
|
-
matches!(decoded_json, IpcMessage::HealthCheck);
|
|
438
|
-
|
|
439
|
-
// MessagePack starts with 0x80-0xBF for fixmap
|
|
440
|
-
let msgpack = serialize_message(&msg, IpcEncoding::MessagePack).unwrap();
|
|
441
|
-
assert!(msgpack[0] >= 0x80 || msgpack[0] == 0xDE || msgpack[0] == 0xDF);
|
|
442
|
-
let decoded_msgpack = deserialize_message(&msgpack).unwrap();
|
|
443
|
-
matches!(decoded_msgpack, IpcMessage::HealthCheck);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
#[test]
|
|
447
|
-
fn test_stream_messages() {
|
|
448
|
-
let start = IpcMessage::StreamStart {
|
|
449
|
-
stream_id: "stream-123".to_string(),
|
|
450
|
-
status: 200,
|
|
451
|
-
headers: HashMap::new(),
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
let chunk = IpcMessage::StreamChunk {
|
|
455
|
-
stream_id: "stream-123".to_string(),
|
|
456
|
-
data: "SGVsbG8gV29ybGQ=".to_string(), // "Hello World" base64
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
let end = IpcMessage::StreamEnd {
|
|
460
|
-
stream_id: "stream-123".to_string(),
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// Test serialization round-trip
|
|
464
|
-
for msg in [start, chunk, end] {
|
|
465
|
-
let msgpack = serialize_message(&msg, IpcEncoding::MessagePack).unwrap();
|
|
466
|
-
let _decoded = deserialize_message(&msgpack).unwrap();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
#[test]
|
|
471
|
-
fn test_websocket_messages() {
|
|
472
|
-
let connect = IpcMessage::WsConnect {
|
|
473
|
-
connection_id: "ws-123".to_string(),
|
|
474
|
-
handler_id: "ws_handler_0".to_string(),
|
|
475
|
-
path: "/ws/chat".to_string(),
|
|
476
|
-
headers: HashMap::new(),
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
let message = IpcMessage::WsMessage {
|
|
480
|
-
connection_id: "ws-123".to_string(),
|
|
481
|
-
handler_id: "ws_handler_0".to_string(),
|
|
482
|
-
data: "Hello".to_string(),
|
|
483
|
-
binary: false,
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
let close = IpcMessage::WsClose {
|
|
487
|
-
connection_id: "ws-123".to_string(),
|
|
488
|
-
handler_id: "ws_handler_0".to_string(),
|
|
489
|
-
code: Some(1000),
|
|
490
|
-
reason: Some("Normal closure".to_string()),
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
// Test serialization round-trip
|
|
494
|
-
for msg in [connect, message, close] {
|
|
495
|
-
let msgpack = serialize_message(&msg, IpcEncoding::MessagePack).unwrap();
|
|
496
|
-
let _decoded = deserialize_message(&msgpack).unwrap();
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|