starpc 0.52.1 → 0.52.2

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.
Files changed (91) hide show
  1. package/README.md +37 -8
  2. package/dist/srpc/client-rpc.d.ts +1 -0
  3. package/dist/srpc/client-rpc.js +9 -0
  4. package/dist/srpc/client-rpc.test.d.ts +1 -0
  5. package/dist/srpc/client-rpc.test.js +50 -0
  6. package/dist/srpc/common-rpc.d.ts +5 -0
  7. package/dist/srpc/common-rpc.js +37 -8
  8. package/dist/srpc/errors.d.ts +2 -0
  9. package/dist/srpc/errors.js +14 -0
  10. package/dist/srpc/index.d.ts +1 -1
  11. package/dist/srpc/index.js +1 -1
  12. package/echo/Cargo.toml +8 -0
  13. package/echo/echo_e2e_test.cpp +542 -985
  14. package/echo/echo_srpc.pb.cpp +3 -5
  15. package/echo/echo_srpc.pb.go +6 -9
  16. package/echo/echo_srpc.pb.hpp +23 -3
  17. package/echo/echo_srpc.pb.rs +61 -26
  18. package/echo/echo_srpc.py +4 -1
  19. package/echo/half_close_test.rs +178 -0
  20. package/echo/integration_server.rs +14 -21
  21. package/echo/main.rs +28 -48
  22. package/go.mod +1 -1
  23. package/go.sum +2 -18
  24. package/integration/cross-language/cpp-client.cpp +446 -402
  25. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  26. package/integration/cross-language/cpp-pipe_test.go +114 -0
  27. package/integration/cross-language/cpp-server.cpp +222 -186
  28. package/mock/mock_srpc.pb.cpp +1 -1
  29. package/mock/mock_srpc.pb.hpp +1 -1
  30. package/mock/mock_srpc.pb.rs +10 -9
  31. package/package.json +1 -1
  32. package/srpc/CMakeLists.txt +3 -58
  33. package/srpc/README-cpp.md +69 -0
  34. package/srpc/accept.go +10 -4
  35. package/srpc/accept_test.go +25 -0
  36. package/srpc/build.rs +129 -34
  37. package/srpc/client-half-close_test.go +152 -0
  38. package/srpc/client-invoker.go +32 -42
  39. package/srpc/client-invoker_test.go +53 -0
  40. package/srpc/client-rpc.cpp +65 -80
  41. package/srpc/client-rpc.go +32 -33
  42. package/srpc/client-rpc.hpp +52 -38
  43. package/srpc/client-rpc.test.ts +67 -0
  44. package/srpc/client-rpc.ts +10 -0
  45. package/srpc/client-rpc_test.go +76 -0
  46. package/srpc/client.cpp +99 -87
  47. package/srpc/client.hpp +46 -35
  48. package/srpc/client.rs +78 -38
  49. package/srpc/client_half_close_test.cc +331 -0
  50. package/srpc/client_invoker.rs +75 -0
  51. package/srpc/common-rpc.cpp +186 -146
  52. package/srpc/common-rpc.go +5 -4
  53. package/srpc/common-rpc.hpp +119 -89
  54. package/srpc/common-rpc.ts +43 -8
  55. package/srpc/common-rpc_test.go +23 -0
  56. package/srpc/error.rs +19 -2
  57. package/srpc/errors.hpp +80 -50
  58. package/srpc/errors.ts +16 -0
  59. package/srpc/handler.hpp +9 -7
  60. package/srpc/handler.rs +7 -45
  61. package/srpc/index.ts +2 -0
  62. package/srpc/invoker.hpp +82 -55
  63. package/srpc/invoker.rs +14 -52
  64. package/srpc/lib.rs +2 -0
  65. package/srpc/message.hpp +17 -12
  66. package/srpc/msg-stream.go +1 -0
  67. package/srpc/msg-stream.hpp +108 -61
  68. package/srpc/mux.cpp +84 -84
  69. package/srpc/mux.hpp +48 -38
  70. package/srpc/mux.rs +4 -4
  71. package/srpc/net.go +12 -3
  72. package/srpc/packet.cpp +60 -58
  73. package/srpc/packet.hpp +35 -20
  74. package/srpc/packet.rs +8 -27
  75. package/srpc/rpc.rs +332 -115
  76. package/srpc/rpc_lifecycle_test.cc +333 -0
  77. package/srpc/server-cancel_test.go +39 -0
  78. package/srpc/server-rpc.cpp +83 -73
  79. package/srpc/server-rpc.go +21 -19
  80. package/srpc/server-rpc.hpp +56 -37
  81. package/srpc/server.rs +76 -131
  82. package/srpc/starpc.hpp +4 -2
  83. package/srpc/stream.go +4 -0
  84. package/srpc/stream.hpp +95 -34
  85. package/srpc/stream.rs +7 -2
  86. package/srpc/testing.rs +11 -66
  87. package/srpc/transport.rs +21 -71
  88. package/srpc/websocket.rs +106 -57
  89. package/srpc/writer.hpp +46 -32
  90. package/srpc/yamux.rs +82 -26
  91. package/srpc/tests/integration_test.rs +0 -495
package/srpc/yamux.rs CHANGED
@@ -1,13 +1,15 @@
1
1
  //! Yamux transport adapters for starpc.
2
2
 
3
3
  use async_trait::async_trait;
4
- use futures::future::poll_fn;
4
+ use futures::{future::poll_fn, stream::FuturesUnordered, StreamExt};
5
5
  use std::io;
6
6
  use std::pin::Pin;
7
7
  use std::sync::Arc;
8
8
  use tokio::io::{AsyncRead, AsyncWrite};
9
- use tokio::sync::{mpsc, oneshot};
9
+ use tokio::sync::{mpsc, oneshot, Mutex};
10
+ use tokio::task::JoinHandle;
10
11
  use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
12
+ use tokio_util::sync::CancellationToken;
11
13
 
12
14
  use crate::client::{OpenStream, PacketReceiver};
13
15
  use crate::error::{Error, Result};
@@ -19,14 +21,30 @@ use crate::transport::{create_packet_channel, DEFAULT_CHANNEL_BUFFER};
19
21
  type OpenResult = std::result::Result<::yamux::Stream, ::yamux::ConnectionError>;
20
22
  type OpenRequest = oneshot::Sender<OpenResult>;
21
23
 
22
- /// YamuxStreamOpener opens Starpc packet streams over one yamux connection.
24
+ /// Opens StarPC packet streams over one owned yamux connection.
25
+ /// Clones share the connection. Dropping the last opener cancels its driver;
26
+ /// explicit close cancels and joins the driver before returning.
23
27
  #[derive(Clone)]
24
28
  pub struct YamuxStreamOpener {
29
+ driver: Arc<ClientDriver>,
30
+ }
31
+
32
+ struct ClientDriver {
25
33
  requests: mpsc::Sender<OpenRequest>,
34
+ task: Mutex<Option<JoinHandle<()>>>,
35
+ closed: CancellationToken,
36
+ }
37
+
38
+ impl Drop for ClientDriver {
39
+ fn drop(&mut self) {
40
+ if let Some(task) = self.task.get_mut().take() {
41
+ task.abort();
42
+ }
43
+ }
26
44
  }
27
45
 
28
46
  impl YamuxStreamOpener {
29
- /// client creates a client-mode yamux opener over a Tokio transport.
47
+ /// Creates a client-mode yamux opener over a Tokio transport.
30
48
  pub fn client<T>(transport: T) -> Self
31
49
  where
32
50
  T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
@@ -34,17 +52,41 @@ impl YamuxStreamOpener {
34
52
  Self::client_with_config(transport, ::yamux::Config::default())
35
53
  }
36
54
 
37
- /// client_with_config creates a client-mode yamux opener with a custom config.
55
+ /// Creates a client-mode yamux opener with a custom configuration.
38
56
  pub fn client_with_config<T>(transport: T, config: ::yamux::Config) -> Self
39
57
  where
40
58
  T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
41
59
  {
42
60
  let (requests, request_rx) = mpsc::channel(DEFAULT_CHANNEL_BUFFER);
43
- spawn_client_driver(transport.compat(), config, request_rx);
44
- Self { requests }
61
+ let closed = CancellationToken::new();
62
+ let task = spawn_client_driver(transport.compat(), config, request_rx, closed.clone());
63
+ Self {
64
+ driver: Arc::new(ClientDriver {
65
+ requests,
66
+ task: Mutex::new(Some(task)),
67
+ closed,
68
+ }),
69
+ }
45
70
  }
46
71
 
47
- /// client_websocket creates a client-mode yamux opener over a WebSocket.
72
+ /// Cancels the shared connection and joins its driver.
73
+ /// Concurrent callers wait for the same completed teardown.
74
+ pub async fn close(&self) {
75
+ let mut task = self.driver.task.lock().await;
76
+ if let Some(task) = task.as_mut() {
77
+ task.abort();
78
+ let _ = task.await;
79
+ }
80
+ task.take();
81
+ }
82
+
83
+ /// Waits for the connection driver to end, including peer closure and protocol failure.
84
+ /// Observers do not prevent a concurrent explicit close from joining the driver.
85
+ pub async fn closed(&self) {
86
+ self.driver.closed.cancelled().await;
87
+ }
88
+
89
+ /// Creates a client-mode yamux opener over a WebSocket.
48
90
  #[cfg(feature = "websocket")]
49
91
  pub fn client_websocket<S>(socket: tokio_tungstenite::WebSocketStream<S>) -> Self
50
92
  where
@@ -53,7 +95,7 @@ impl YamuxStreamOpener {
53
95
  Self::client(crate::websocket::websocket_byte_stream(socket))
54
96
  }
55
97
 
56
- /// client_websocket_with_config creates a client-mode yamux WebSocket opener with a custom config.
98
+ /// Creates a client-mode yamux WebSocket opener with a custom configuration.
57
99
  #[cfg(feature = "websocket")]
58
100
  pub fn client_websocket_with_config<S>(
59
101
  socket: tokio_tungstenite::WebSocketStream<S>,
@@ -70,7 +112,8 @@ impl YamuxStreamOpener {
70
112
  impl OpenStream for YamuxStreamOpener {
71
113
  async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
72
114
  let (tx, rx) = oneshot::channel();
73
- self.requests
115
+ self.driver
116
+ .requests
74
117
  .send(tx)
75
118
  .await
76
119
  .map_err(|_| Error::StreamClosed)?;
@@ -85,7 +128,7 @@ impl OpenStream for YamuxStreamOpener {
85
128
  }
86
129
  }
87
130
 
88
- /// handle_server_connection accepts yamux streams and serves each as Starpc.
131
+ /// Serves yamux substreams within this connection's owned lifetime.
89
132
  pub async fn handle_server_connection<I, T>(
90
133
  server: &Server<I>,
91
134
  transport: T,
@@ -98,18 +141,23 @@ where
98
141
  let mut connection =
99
142
  ::yamux::Connection::new(transport.compat(), config, ::yamux::Mode::Server);
100
143
 
144
+ // Keep accepted calls within the connection's lifetime. Losing the connection
145
+ // or dropping this future drops every call and its cancellation guard.
146
+ let mut calls = FuturesUnordered::new();
101
147
  loop {
102
- match poll_fn(|cx| connection.poll_next_inbound(cx)).await {
103
- Some(Ok(stream)) => {
104
- let server = server.clone_for_spawn();
105
- tokio::spawn(async move {
106
- if let Err(err) = server.handle_stream(stream.compat()).await {
107
- server.report_error(err);
108
- }
109
- });
148
+ tokio::select! {
149
+ incoming = poll_fn(|cx| connection.poll_next_inbound(cx)) => {
150
+ match incoming {
151
+ Some(Ok(stream)) => calls.push(server.handle_stream(stream.compat())),
152
+ Some(Err(err)) => return Err(connection_error(err)),
153
+ None => return Ok(()),
154
+ }
155
+ }
156
+ Some(result) = calls.next(), if !calls.is_empty() => {
157
+ if let Err(err) = result {
158
+ server.report_error(err);
159
+ }
110
160
  }
111
- Some(Err(err)) => return Err(connection_error(err)),
112
- None => return Ok(()),
113
161
  }
114
162
  }
115
163
  }
@@ -126,21 +174,29 @@ fn spawn_client_driver<T>(
126
174
  transport: T,
127
175
  config: ::yamux::Config,
128
176
  mut requests: mpsc::Receiver<OpenRequest>,
129
- ) -> tokio::task::JoinHandle<()>
177
+ closed: CancellationToken,
178
+ ) -> JoinHandle<()>
130
179
  where
131
180
  T: futures::io::AsyncRead + futures::io::AsyncWrite + Send + Unpin + 'static,
132
181
  {
182
+ // Construct the guard before spawning so cancellation before the first poll also notifies.
183
+ let ended = closed.drop_guard();
133
184
  tokio::spawn(async move {
185
+ let _ended = ended;
134
186
  let mut connection = ::yamux::Connection::new(transport, config, ::yamux::Mode::Client);
135
187
  let mut pending = None;
136
- let mut requests_closed = false;
137
188
 
138
189
  loop {
139
190
  let event = poll_fn(|cx| {
140
- if pending.is_none() && !requests_closed {
191
+ if pending.is_none() {
141
192
  match Pin::new(&mut requests).poll_recv(cx) {
142
193
  std::task::Poll::Ready(Some(request)) => pending = Some(request),
143
- std::task::Poll::Ready(None) => requests_closed = true,
194
+ std::task::Poll::Ready(None) => {
195
+ return std::task::Poll::Ready(DriverEvent::Closed {
196
+ pending: None,
197
+ err: None,
198
+ });
199
+ }
144
200
  std::task::Poll::Pending => {}
145
201
  }
146
202
  }
@@ -193,6 +249,6 @@ where
193
249
  fn connection_error(err: ::yamux::ConnectionError) -> Error {
194
250
  match err {
195
251
  ::yamux::ConnectionError::Io(err) => Error::Io(err),
196
- other => Error::Io(io::Error::new(io::ErrorKind::Other, other.to_string())),
252
+ other => Error::Io(io::Error::other(other.to_string())),
197
253
  }
198
254
  }
@@ -1,495 +0,0 @@
1
- //! Integration tests for starpc, mirroring the Go/JS test patterns.
2
- //!
3
- //! These tests verify:
4
- //! 1. Unary RPC
5
- //! 2. Server streaming (5 messages)
6
- //! 3. Client streaming
7
- //! 4. Bidirectional streaming
8
- //! 5. Error handling
9
- //! 6. Wire format compatibility
10
-
11
- use async_trait::async_trait;
12
- use prost::Message;
13
- use std::sync::Arc;
14
- use std::time::Duration;
15
-
16
- use starpc::error::{Error, Result};
17
- use starpc::handler::Handler;
18
- use starpc::invoker::Invoker;
19
- use starpc::mux::Mux;
20
- use starpc::server::Server;
21
- use starpc::stream::{Stream, StreamExt};
22
- use starpc::testing::{create_test_pair, SingleInMemoryOpener};
23
- use starpc::Client;
24
-
25
- // Simple EchoMsg for testing
26
- #[derive(Clone, PartialEq, Message)]
27
- struct EchoMsg {
28
- #[prost(string, tag = "1")]
29
- body: String,
30
- }
31
-
32
- const BODY_TXT: &str = "hello world via starpc e2e test";
33
-
34
- /// Echo server implementation matching Go's echo/server.go
35
- struct EchoServer;
36
-
37
- #[async_trait]
38
- impl Invoker for EchoServer {
39
- async fn invoke_method(
40
- &self,
41
- _service_id: &str,
42
- method_id: &str,
43
- stream: Box<dyn Stream>,
44
- ) -> (bool, Result<()>) {
45
- match method_id {
46
- "Echo" => (true, self.echo(stream).await),
47
- "EchoServerStream" => (true, self.echo_server_stream(stream).await),
48
- "EchoClientStream" => (true, self.echo_client_stream(stream).await),
49
- "EchoBidiStream" => (true, self.echo_bidi_stream(stream).await),
50
- _ => (false, Err(Error::Unimplemented)),
51
- }
52
- }
53
- }
54
-
55
- impl Handler for EchoServer {
56
- fn service_id(&self) -> &'static str {
57
- "echo.Echoer"
58
- }
59
-
60
- fn method_ids(&self) -> &'static [&'static str] {
61
- &[
62
- "Echo",
63
- "EchoServerStream",
64
- "EchoClientStream",
65
- "EchoBidiStream",
66
- ]
67
- }
68
- }
69
-
70
- impl EchoServer {
71
- /// Unary echo - returns the same message
72
- async fn echo(&self, stream: Box<dyn Stream>) -> Result<()> {
73
- let msg: EchoMsg = stream.msg_recv().await?;
74
- stream.msg_send(&msg).await?;
75
- Ok(())
76
- }
77
-
78
- /// Server streaming - sends 5 copies of the message
79
- async fn echo_server_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
80
- let msg: EchoMsg = stream.msg_recv().await?;
81
-
82
- // Send 5 responses with delays
83
- for _ in 0..5 {
84
- if stream.context().is_cancelled() {
85
- return Err(Error::Cancelled);
86
- }
87
- stream.msg_send(&msg).await?;
88
- tokio::time::sleep(Duration::from_millis(10)).await;
89
- }
90
- Ok(())
91
- }
92
-
93
- /// Client streaming - returns first message received
94
- async fn echo_client_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
95
- let msg: EchoMsg = stream.msg_recv().await?;
96
- stream.msg_send(&msg).await?;
97
- Ok(())
98
- }
99
-
100
- /// Bidirectional streaming - server sends first, then echoes all messages
101
- async fn echo_bidi_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
102
- // Server sends initial message
103
- let initial = EchoMsg {
104
- body: "hello from server".to_string(),
105
- };
106
- stream.msg_send(&initial).await?;
107
-
108
- // Echo all received messages
109
- loop {
110
- match stream.msg_recv::<EchoMsg>().await {
111
- Ok(msg) => {
112
- if msg.body.is_empty() {
113
- return Err(Error::Remote("got message with empty body".to_string()));
114
- }
115
- stream.msg_send(&msg).await?;
116
- }
117
- Err(Error::StreamClosed) => break,
118
- Err(e) => return Err(e),
119
- }
120
- }
121
- Ok(())
122
- }
123
- }
124
-
125
- /// Test infrastructure: creates connected client and server
126
- async fn setup_e2e() -> (starpc::SrpcClient<SingleInMemoryOpener>, tokio::task::JoinHandle<()>) {
127
- let (opener, server_stream) = create_test_pair();
128
-
129
- // Set up the server
130
- let mux = Arc::new(Mux::new());
131
- mux.register(Arc::new(EchoServer)).unwrap();
132
- let server = Server::with_arc(mux);
133
-
134
- // Spawn server handler
135
- let server_handle = tokio::spawn(async move {
136
- let _ = server.handle_stream(server_stream).await;
137
- });
138
-
139
- // Create client
140
- let client = starpc::SrpcClient::new(opener);
141
-
142
- (client, server_handle)
143
- }
144
-
145
- // ============================================================================
146
- // Tests matching Go's server_test.go
147
- // ============================================================================
148
-
149
- #[tokio::test]
150
- async fn test_e2e_unary() {
151
- let (client, server_handle) = setup_e2e().await;
152
-
153
- // Make unary call
154
- let request = EchoMsg {
155
- body: BODY_TXT.to_string(),
156
- };
157
- let response: EchoMsg = client
158
- .exec_call("echo.Echoer", "Echo", &request)
159
- .await
160
- .expect("exec_call failed");
161
-
162
- assert_eq!(response.body, BODY_TXT);
163
-
164
- server_handle.abort();
165
- }
166
-
167
- #[tokio::test]
168
- async fn test_e2e_server_stream() {
169
- let (client, server_handle) = setup_e2e().await;
170
-
171
- // Send request and open stream
172
- let request = EchoMsg {
173
- body: BODY_TXT.to_string(),
174
- };
175
- let data = request.encode_to_vec();
176
- let stream = client
177
- .new_stream("echo.Echoer", "EchoServerStream", Some(&data))
178
- .await
179
- .expect("new_stream failed");
180
-
181
- // Close send side
182
- stream.close_send().await.expect("close_send failed");
183
-
184
- // Expect to receive 5 messages
185
- let expected_rx = 5;
186
- let mut received = 0;
187
-
188
- loop {
189
- match stream.msg_recv::<EchoMsg>().await {
190
- Ok(msg) => {
191
- assert_eq!(msg.body, BODY_TXT);
192
- received += 1;
193
- }
194
- Err(Error::StreamClosed) => break,
195
- Err(e) => panic!("unexpected error: {}", e),
196
- }
197
- }
198
-
199
- assert_eq!(
200
- received, expected_rx,
201
- "expected {} messages, got {}",
202
- expected_rx, received
203
- );
204
-
205
- server_handle.abort();
206
- }
207
-
208
- #[tokio::test]
209
- async fn test_e2e_client_stream() {
210
- let (client, server_handle) = setup_e2e().await;
211
-
212
- // Open stream without initial message
213
- let stream = client
214
- .new_stream("echo.Echoer", "EchoClientStream", None)
215
- .await
216
- .expect("new_stream failed");
217
-
218
- // Send a message
219
- let request = EchoMsg {
220
- body: BODY_TXT.to_string(),
221
- };
222
- stream.msg_send(&request).await.expect("msg_send failed");
223
-
224
- // Close send side
225
- stream.close_send().await.expect("close_send failed");
226
-
227
- // Receive response
228
- let response: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
229
- assert_eq!(response.body, BODY_TXT);
230
-
231
- stream.close().await.ok();
232
- server_handle.abort();
233
- }
234
-
235
- #[tokio::test]
236
- async fn test_e2e_bidi_stream() {
237
- let (client, server_handle) = setup_e2e().await;
238
-
239
- // Open bidirectional stream
240
- let stream = client
241
- .new_stream("echo.Echoer", "EchoBidiStream", None)
242
- .await
243
- .expect("new_stream failed");
244
-
245
- // Receive server's initial message
246
- let initial: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
247
- assert_eq!(initial.body, "hello from server");
248
-
249
- // Send a message from client
250
- let client_msg = EchoMsg {
251
- body: "hello from client".to_string(),
252
- };
253
- stream.msg_send(&client_msg).await.expect("msg_send failed");
254
-
255
- // Receive echoed message
256
- let echoed: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
257
- assert_eq!(echoed.body, "hello from client");
258
-
259
- // Close the stream
260
- stream.close().await.expect("close failed");
261
- server_handle.abort();
262
- }
263
-
264
- #[tokio::test]
265
- async fn test_e2e_multiple_bidi_messages() {
266
- let (client, server_handle) = setup_e2e().await;
267
-
268
- let stream = client
269
- .new_stream("echo.Echoer", "EchoBidiStream", None)
270
- .await
271
- .expect("new_stream failed");
272
-
273
- // Receive server's initial message
274
- let _: EchoMsg = stream.msg_recv().await.expect("initial recv failed");
275
-
276
- // Send and receive multiple messages
277
- for i in 0..10 {
278
- let msg = EchoMsg {
279
- body: format!("message {}", i),
280
- };
281
- stream.msg_send(&msg).await.expect("msg_send failed");
282
-
283
- let echoed: EchoMsg = stream.msg_recv().await.expect("msg_recv failed");
284
- assert_eq!(echoed.body, format!("message {}", i));
285
- }
286
-
287
- stream.close().await.expect("close failed");
288
- server_handle.abort();
289
- }
290
-
291
- #[tokio::test]
292
- async fn test_e2e_unary_empty_message() {
293
- let (client, server_handle) = setup_e2e().await;
294
-
295
- // Send empty message
296
- let request = EchoMsg {
297
- body: String::new(),
298
- };
299
- let response: EchoMsg = client
300
- .exec_call("echo.Echoer", "Echo", &request)
301
- .await
302
- .expect("exec_call failed");
303
-
304
- assert_eq!(response.body, "");
305
-
306
- server_handle.abort();
307
- }
308
-
309
- #[tokio::test]
310
- async fn test_e2e_unimplemented_method() {
311
- let (client, server_handle) = setup_e2e().await;
312
-
313
- let request = EchoMsg {
314
- body: "test".to_string(),
315
- };
316
- let result: Result<EchoMsg> = client
317
- .exec_call("echo.Echoer", "NonExistentMethod", &request)
318
- .await;
319
-
320
- assert!(result.is_err());
321
-
322
- server_handle.abort();
323
- }
324
-
325
- #[tokio::test]
326
- async fn test_codec_wire_format() {
327
- use starpc::codec::PacketCodec;
328
- use starpc::proto::{packet::Body, CallData, CallStart, Packet};
329
- use tokio_util::codec::{Decoder, Encoder};
330
-
331
- let mut codec = PacketCodec::new();
332
- let mut buf = bytes::BytesMut::new();
333
-
334
- // Test CallStart encoding
335
- let call_start = Packet {
336
- body: Some(Body::CallStart(CallStart {
337
- rpc_service: "test.Service".into(),
338
- rpc_method: "TestMethod".into(),
339
- data: vec![1, 2, 3, 4],
340
- data_is_zero: false,
341
- })),
342
- };
343
-
344
- codec
345
- .encode(call_start.clone(), &mut buf)
346
- .expect("encode failed");
347
-
348
- // Verify length prefix (little-endian u32)
349
- let len = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
350
- assert_eq!(len, buf.len() - 4);
351
-
352
- // Decode and verify
353
- let decoded = codec
354
- .decode(&mut buf)
355
- .expect("decode failed")
356
- .expect("no packet");
357
- assert_eq!(decoded, call_start);
358
-
359
- // Test CallData encoding
360
- buf.clear();
361
- let call_data = Packet {
362
- body: Some(Body::CallData(CallData {
363
- data: vec![5, 6, 7, 8],
364
- data_is_zero: false,
365
- complete: true,
366
- error: String::new(),
367
- })),
368
- };
369
-
370
- codec
371
- .encode(call_data.clone(), &mut buf)
372
- .expect("encode failed");
373
- let decoded = codec
374
- .decode(&mut buf)
375
- .expect("decode failed")
376
- .expect("no packet");
377
- assert_eq!(decoded, call_data);
378
-
379
- // Test empty data with data_is_zero flag
380
- buf.clear();
381
- let empty_data = Packet {
382
- body: Some(Body::CallData(CallData {
383
- data: vec![],
384
- data_is_zero: true,
385
- complete: false,
386
- error: String::new(),
387
- })),
388
- };
389
-
390
- codec
391
- .encode(empty_data.clone(), &mut buf)
392
- .expect("encode failed");
393
- let decoded = codec
394
- .decode(&mut buf)
395
- .expect("decode failed")
396
- .expect("no packet");
397
- assert_eq!(decoded, empty_data);
398
- }
399
-
400
- #[tokio::test]
401
- async fn test_packet_validation() {
402
- use starpc::packet::Validate;
403
- use starpc::proto::{packet::Body, CallData, CallStart, Packet};
404
-
405
- // Valid CallStart
406
- let valid_start = Packet {
407
- body: Some(Body::CallStart(CallStart {
408
- rpc_service: "svc".into(),
409
- rpc_method: "method".into(),
410
- data: vec![],
411
- data_is_zero: false,
412
- })),
413
- };
414
- assert!(valid_start.validate().is_ok());
415
-
416
- // Invalid CallStart - empty method
417
- let invalid_start = Packet {
418
- body: Some(Body::CallStart(CallStart {
419
- rpc_service: "svc".into(),
420
- rpc_method: String::new(),
421
- data: vec![],
422
- data_is_zero: false,
423
- })),
424
- };
425
- assert!(invalid_start.validate().is_err());
426
-
427
- // Valid CallData with data
428
- let valid_data = Packet {
429
- body: Some(Body::CallData(CallData {
430
- data: vec![1, 2, 3],
431
- data_is_zero: false,
432
- complete: false,
433
- error: String::new(),
434
- })),
435
- };
436
- assert!(valid_data.validate().is_ok());
437
-
438
- // Invalid CallData - empty everything
439
- let invalid_data = Packet {
440
- body: Some(Body::CallData(CallData {
441
- data: vec![],
442
- data_is_zero: false,
443
- complete: false,
444
- error: String::new(),
445
- })),
446
- };
447
- assert!(invalid_data.validate().is_err());
448
-
449
- // Empty packet
450
- let empty_packet = Packet { body: None };
451
- assert!(empty_packet.validate().is_err());
452
- }
453
-
454
- #[tokio::test]
455
- async fn test_mux_registration_and_lookup() {
456
- let mux = Mux::new();
457
-
458
- // Register handler
459
- mux.register(Arc::new(EchoServer)).unwrap();
460
-
461
- // Check service exists
462
- assert!(mux.has_service("echo.Echoer"));
463
- assert!(!mux.has_service("nonexistent"));
464
-
465
- // Check methods exist
466
- assert!(mux.has_service_method("echo.Echoer", "Echo"));
467
- assert!(mux.has_service_method("echo.Echoer", "EchoServerStream"));
468
- assert!(!mux.has_service_method("echo.Echoer", "NonExistent"));
469
-
470
- // Empty strings should return false
471
- assert!(!mux.has_service(""));
472
- assert!(!mux.has_service_method("", "Echo"));
473
- assert!(!mux.has_service_method("echo.Echoer", ""));
474
- }
475
-
476
- #[tokio::test]
477
- async fn test_error_types() {
478
- use starpc::error::codes;
479
-
480
- // Test error predicates
481
- assert!(Error::Aborted.is_abort());
482
- assert!(Error::Cancelled.is_abort());
483
- assert!(!Error::StreamClosed.is_abort());
484
-
485
- assert!(Error::StreamClosed.is_closed());
486
- assert!(Error::Cancelled.is_closed());
487
-
488
- assert!(Error::StreamIdle.is_timeout());
489
-
490
- assert!(Error::Unimplemented.is_unimplemented());
491
-
492
- // Test error codes
493
- assert_eq!(codes::ERR_RPC_ABORT, "ERR_RPC_ABORT");
494
- assert_eq!(codes::ERR_STREAM_IDLE, "ERR_STREAM_IDLE");
495
- }