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/accept.go CHANGED
@@ -7,14 +7,20 @@ import (
7
7
  "github.com/libp2p/go-yamux/v5"
8
8
  )
9
9
 
10
- // AcceptMuxedListener accepts incoming connections from a net.Listener.
11
- //
12
- // Uses the default yamux muxer.
13
- // If yamux conf is nil, uses the defaults.
10
+ // AcceptMuxedListener accepts connections until ctx is canceled or the listener
11
+ // fails. Cancellation closes lis to interrupt Accept. A nil yamuxConf uses defaults.
14
12
  func AcceptMuxedListener(ctx context.Context, lis net.Listener, srv *Server, yamuxConf *yamux.Config) error {
13
+ // Interrupt a blocked Accept when its serving context ends.
14
+ stop := context.AfterFunc(ctx, func() { _ = lis.Close() })
15
+ defer stop()
16
+
17
+ // Transfer accepted connections to the server and release rejected ones.
15
18
  for {
16
19
  nc, err := lis.Accept()
17
20
  if err != nil {
21
+ if ctx.Err() != nil {
22
+ return ctx.Err()
23
+ }
18
24
  return err
19
25
  }
20
26
 
@@ -0,0 +1,25 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "net"
7
+ "testing"
8
+ )
9
+
10
+ func TestAcceptMuxedListenerCancellation(t *testing.T) {
11
+ // An idle listener must not retain a blocked Accept after cancellation.
12
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
13
+ if err != nil {
14
+ t.Fatal(err)
15
+ }
16
+ t.Cleanup(func() { _ = listener.Close() })
17
+ ctx, cancel := context.WithCancel(t.Context())
18
+ t.Cleanup(cancel)
19
+ done := make(chan error, 1)
20
+ go func() { done <- AcceptMuxedListener(ctx, listener, nil, nil) }()
21
+ cancel()
22
+ if err := <-done; !errors.Is(err, context.Canceled) {
23
+ t.Fatalf("listener cancellation returned %v", err)
24
+ }
25
+ }
package/srpc/build.rs CHANGED
@@ -3,12 +3,21 @@
3
3
  //! Enable the `build` feature and call [`configure`] from a downstream
4
4
  //! `build.rs` to install the Starpc service generator into `prost-build`.
5
5
 
6
+ /// Precedes each generated `close_send` whose error is ignored.
7
+ ///
8
+ /// A failed half-close means the call already ended; returning that error would
9
+ /// discard responses the stream already received, and `msg_recv` reports the
10
+ /// call's outcome after them.
11
+ const HALF_CLOSE_COMMENT: &str =
12
+ " // A failed half-close means the call ended; msg_recv reports its outcome.";
13
+
6
14
  /// Configures `prost-build` to generate Starpc service bindings.
7
15
  ///
8
16
  /// The returned config generates normal Prost message types and appends Starpc
9
17
  /// client/server traits, client implementations, stream wrappers, and handler
10
18
  /// glue for every protobuf service.
11
19
  pub fn configure() -> prost_build::Config {
20
+ // Use the packaged compiler and its standard protobuf imports when available.
12
21
  let mut config = prost_build::Config::new();
13
22
  if let Ok(protoc) = protoc_bin_vendored::protoc_bin_path() {
14
23
  config.protoc_executable(protoc);
@@ -16,39 +25,49 @@ pub fn configure() -> prost_build::Config {
16
25
  if let Ok(include) = protoc_bin_vendored::include_path() {
17
26
  config.protoc_arg(format!("--proto_path={}", include.display()));
18
27
  }
28
+
29
+ // Share runtime message types and generate services beside Prost messages.
19
30
  config.extern_path(".rpcstream", "::starpc::rpcstream");
20
- config.service_generator(Box::new(StarpcServiceGenerator::default()));
31
+ config.service_generator(Box::new(StarpcServiceGenerator));
21
32
  config
22
33
  }
23
34
 
24
- /// StarpcServiceGenerator emits Starpc client and server glue for Prost services.
35
+ /// Emits StarPC client and server bindings for Prost services.
25
36
  #[derive(Debug, Default)]
26
37
  pub struct StarpcServiceGenerator;
27
38
 
28
39
  impl prost_build::ServiceGenerator for StarpcServiceGenerator {
40
+ /// Appends one service's clients, streams, and dispatch implementation.
29
41
  fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
30
42
  let mut gen = Generator::new(service, buf);
31
43
  gen.generate();
32
44
  }
33
45
  }
34
46
 
47
+ /// Emits bindings for one service into its protobuf package's output module.
35
48
  struct Generator<'a> {
49
+ /// Service identity, message types, and streaming modes supplied by Prost.
36
50
  service: prost_build::Service,
51
+ /// Package output shared by every service in the descriptor graph.
37
52
  buf: &'a mut String,
38
53
  }
39
54
 
40
55
  impl<'a> Generator<'a> {
56
+ /// Borrows the package output for this service's generation pass.
41
57
  fn new(service: prost_build::Service, buf: &'a mut String) -> Self {
42
58
  Self { service, buf }
43
59
  }
44
60
 
61
+ /// Emits the complete client and server contract for this service.
45
62
  fn generate(&mut self) {
63
+ // Resolve stable identifiers before emitting any declarations.
46
64
  let service_id = self.service_id();
47
65
  let service_name = self.service.name.clone();
48
66
 
67
+ // Keep extension methods available without adding a shared import name.
49
68
  self.line("");
50
69
  self.line("#[allow(unused_imports)]");
51
- self.line("use starpc::StreamExt;");
70
+ self.line("use starpc::StreamExt as _;");
52
71
  self.line("");
53
72
  self.line(&format!("/// Service ID for {}.", service_name));
54
73
  self.line(&format!(
@@ -58,6 +77,7 @@ impl<'a> Generator<'a> {
58
77
  ));
59
78
  self.line("");
60
79
 
80
+ // Compose the client and server surfaces around the same wire identity.
61
81
  self.generate_stream_traits();
62
82
  self.generate_client_trait();
63
83
  self.generate_client_impl();
@@ -66,15 +86,17 @@ impl<'a> Generator<'a> {
66
86
  self.generate_handler();
67
87
  }
68
88
 
89
+ /// Declares the send, receive, and close operations for each streaming call.
69
90
  fn generate_stream_traits(&mut self) {
91
+ // Emit the client stream contract only for streaming methods.
70
92
  let service_name = self.service.name.clone();
71
93
  for method in self.service.methods.clone() {
72
94
  if !method.client_streaming && !method.server_streaming {
73
95
  continue;
74
96
  }
75
97
 
98
+ // Name the typed stream and retain its common transport contract.
76
99
  let stream_trait = stream_trait_name(&service_name, &method);
77
-
78
100
  self.line(&format!(
79
101
  "/// Stream trait for {}.{}.",
80
102
  service_name, method.proto_name
@@ -84,6 +106,7 @@ impl<'a> Generator<'a> {
84
106
  self.line(" /// Returns the context for this stream.");
85
107
  self.line(" fn context(&self) -> &starpc::Context;");
86
108
 
109
+ // Provide typed sends when the caller owns the request stream.
87
110
  if method.client_streaming {
88
111
  self.line(" /// Sends a message on the stream.");
89
112
  self.line(&format!(
@@ -92,6 +115,7 @@ impl<'a> Generator<'a> {
92
115
  ));
93
116
  }
94
117
 
118
+ // Provide typed receives when the server owns the response stream.
95
119
  if method.server_streaming {
96
120
  self.line(" /// Receives a message from the stream.");
97
121
  self.line(&format!(
@@ -100,6 +124,7 @@ impl<'a> Generator<'a> {
100
124
  ));
101
125
  }
102
126
 
127
+ // Preserve the final response after half-closing a request stream.
103
128
  if method.client_streaming && !method.server_streaming {
104
129
  self.line(" /// Closes the send side and receives the response.");
105
130
  self.line(&format!(
@@ -110,18 +135,20 @@ impl<'a> Generator<'a> {
110
135
  self.line(" /// Closes the stream.");
111
136
  self.line(" async fn close(&self) -> starpc::Result<()>;");
112
137
  }
113
-
114
138
  self.line("}");
115
139
  self.line("");
116
140
  }
117
141
  }
118
142
 
143
+ /// Declares the caller-facing methods for all four RPC shapes.
119
144
  fn generate_client_trait(&mut self) {
145
+ // Declare the caller contract before its typed methods.
120
146
  let service_name = self.service.name.clone();
121
147
  self.line(&format!("/// Client trait for {}.", service_name));
122
148
  self.line("#[starpc::async_trait]");
123
149
  self.line(&format!("pub trait {}Client: Send + Sync {{", service_name));
124
150
 
151
+ // Select each method signature from the descriptor streaming modes.
125
152
  for method in self.service.methods.clone() {
126
153
  self.line(&format!(" /// {}.", method.proto_name));
127
154
  self.line(&format!(
@@ -129,18 +156,25 @@ impl<'a> Generator<'a> {
129
156
  client_trait_method(&service_name, &method)
130
157
  ));
131
158
  }
132
-
133
159
  self.line("}");
134
160
  self.line("");
135
161
  }
136
162
 
163
+ /// Implements calls through a retained transport client.
137
164
  fn generate_client_impl(&mut self) {
165
+ // Retain even an empty service's client for its declared lifetime.
138
166
  let service_id = self.service_id();
139
167
  let service_name = self.service.name.clone();
168
+ let client_field = if self.service.methods.is_empty() {
169
+ "_client"
170
+ } else {
171
+ "client"
172
+ };
140
173
 
174
+ // Emit the client handle and its constructor.
141
175
  self.line(&format!("/// Client implementation for {}.", service_name));
142
176
  self.line(&format!("pub struct {}ClientImpl<C> {{", service_name));
143
- self.line(" client: C,");
177
+ self.line(&format!(" {client_field}: C,"));
144
178
  self.line("}");
145
179
  self.line("");
146
180
  self.line(&format!(
@@ -149,7 +183,11 @@ impl<'a> Generator<'a> {
149
183
  ));
150
184
  self.line(" /// Creates a new client.");
151
185
  self.line(" pub fn new(client: C) -> Self {");
152
- self.line(" Self { client }");
186
+ if self.service.methods.is_empty() {
187
+ self.line(" Self { _client: client }");
188
+ } else {
189
+ self.line(" Self { client }");
190
+ }
153
191
  self.line(" }");
154
192
  self.line("}");
155
193
  self.line("");
@@ -159,6 +197,7 @@ impl<'a> Generator<'a> {
159
197
  service_name, service_name
160
198
  ));
161
199
 
200
+ // Implement each call using its wire identity and typed stream adapter.
162
201
  for method in self.service.methods.clone() {
163
202
  let method_name = &method.name;
164
203
  if method.client_streaming && method.server_streaming {
@@ -189,7 +228,8 @@ impl<'a> Generator<'a> {
189
228
  " let stream = self.client.new_stream({:?}, {:?}, Some(&data)).await?;",
190
229
  service_id, method.proto_name
191
230
  ));
192
- self.line(" stream.close_send().await?;");
231
+ self.line(HALF_CLOSE_COMMENT);
232
+ self.line(" let _ = stream.close_send().await;");
193
233
  self.line(&format!(
194
234
  " Ok(Box::new({}Impl {{ stream }}))",
195
235
  stream_trait_name(&service_name, &method)
@@ -222,20 +262,21 @@ impl<'a> Generator<'a> {
222
262
  self.line(" }");
223
263
  }
224
264
  }
225
-
226
265
  self.line("}");
227
266
  self.line("");
228
267
  }
229
268
 
269
+ /// Adapts each transport stream to its generated typed client contract.
230
270
  fn generate_stream_impls(&mut self) {
271
+ // Adapt each streaming method through its retained transport stream.
231
272
  let service_name = self.service.name.clone();
232
273
  for method in self.service.methods.clone() {
233
274
  if !method.client_streaming && !method.server_streaming {
234
275
  continue;
235
276
  }
236
277
 
278
+ // Name the typed stream and retain its common transport contract.
237
279
  let stream_trait = stream_trait_name(&service_name, &method);
238
-
239
280
  self.line(&format!("struct {}Impl {{", stream_trait));
240
281
  self.line(" stream: Box<dyn starpc::Stream>,");
241
282
  self.line("}");
@@ -249,6 +290,7 @@ impl<'a> Generator<'a> {
249
290
  self.line(" self.stream.context()");
250
291
  self.line(" }");
251
292
 
293
+ // Provide typed sends when the caller owns the request stream.
252
294
  if method.client_streaming {
253
295
  self.line(&format!(
254
296
  " async fn send(&self, msg: &{}) -> starpc::Result<()> {{",
@@ -258,6 +300,7 @@ impl<'a> Generator<'a> {
258
300
  self.line(" }");
259
301
  }
260
302
 
303
+ // Provide typed receives when the server owns the response stream.
261
304
  if method.server_streaming {
262
305
  self.line(&format!(
263
306
  " async fn recv(&self) -> starpc::Result<{}> {{",
@@ -267,12 +310,14 @@ impl<'a> Generator<'a> {
267
310
  self.line(" }");
268
311
  }
269
312
 
313
+ // Preserve the final response after half-closing a request stream.
270
314
  if method.client_streaming && !method.server_streaming {
271
315
  self.line(&format!(
272
316
  " async fn close_and_recv(&self) -> starpc::Result<{}> {{",
273
317
  method.output_type
274
318
  ));
275
- self.line(" self.stream.close_send().await?;");
319
+ self.line(HALF_CLOSE_COMMENT);
320
+ self.line(" let _ = self.stream.close_send().await;");
276
321
  self.line(" self.stream.msg_recv().await");
277
322
  self.line(" }");
278
323
  } else {
@@ -280,19 +325,20 @@ impl<'a> Generator<'a> {
280
325
  self.line(" self.stream.close().await");
281
326
  self.line(" }");
282
327
  }
283
-
284
328
  self.line("}");
285
329
  self.line("");
286
330
  }
287
331
  }
288
332
 
333
+ /// Declares the implementation contract consumed by handler dispatch.
289
334
  fn generate_server_trait(&mut self) {
335
+ // Declare the implementation contract consumed by dispatch.
290
336
  let service_name = self.service.name.clone();
291
-
292
337
  self.line(&format!("/// Server trait for {}.", service_name));
293
338
  self.line("#[starpc::async_trait]");
294
339
  self.line(&format!("pub trait {}Server: Send + Sync {{", service_name));
295
340
 
341
+ // Select each method signature from the descriptor streaming modes.
296
342
  for method in self.service.methods.clone() {
297
343
  self.line(&format!(" /// {}.", method.proto_name));
298
344
  if method.client_streaming && method.server_streaming {
@@ -312,34 +358,43 @@ impl<'a> Generator<'a> {
312
358
  ));
313
359
  } else {
314
360
  self.line(&format!(
315
- " async fn {}(&self, request: {}) -> starpc::Result<{}>;",
361
+ " async fn {}(&self, context: &starpc::Context, request: {}) -> starpc::Result<{}>;",
316
362
  method.name, method.input_type, method.output_type
317
363
  ));
318
364
  }
319
365
  }
320
-
321
366
  self.line("}");
322
367
  self.line("");
323
368
  }
324
369
 
370
+ /// Routes wire method names through the exact server trait.
325
371
  fn generate_handler(&mut self) {
372
+ // Retain empty services without emitting unused field or parameter names.
326
373
  let service_id = self.service_id();
327
374
  let service_name = self.service.name.clone();
328
375
  let methods = self.service.methods.clone();
329
376
  let method_ids = method_ids_const(&service_name);
377
+ let (server_field, stream_parameter, method_parameter) = if methods.is_empty() {
378
+ ("_server", "_stream", "_method_id")
379
+ } else {
380
+ ("server", "stream", "method_id")
381
+ };
330
382
 
383
+ // Emit the supported wire method identities.
331
384
  self.line(&format!("const {}: &[&str] = &[", method_ids));
332
385
  for method in &methods {
333
386
  self.line(&format!(" {:?},", method.proto_name));
334
387
  }
335
388
  self.line("];");
336
389
  self.line("");
390
+
391
+ // Retain the server through both owned and shared construction.
337
392
  self.line(&format!("/// Handler for {}.", service_name));
338
393
  self.line(&format!(
339
394
  "pub struct {}Handler<S: {}Server> {{",
340
395
  service_name, service_name
341
396
  ));
342
- self.line(" server: std::sync::Arc<S>,");
397
+ self.line(&format!(" {server_field}: std::sync::Arc<S>,"));
343
398
  self.line("}");
344
399
  self.line("");
345
400
  self.line(&format!(
@@ -348,15 +403,23 @@ impl<'a> Generator<'a> {
348
403
  ));
349
404
  self.line(" /// Creates a new handler wrapping the server implementation.");
350
405
  self.line(" pub fn new(server: S) -> Self {");
351
- self.line(" Self { server: std::sync::Arc::new(server) }");
406
+ self.line(&format!(
407
+ " Self {{ {server_field}: std::sync::Arc::new(server) }}"
408
+ ));
352
409
  self.line(" }");
353
410
  self.line("");
354
411
  self.line(" /// Creates a new handler with a shared server.");
355
412
  self.line(" pub fn with_arc(server: std::sync::Arc<S>) -> Self {");
356
- self.line(" Self { server }");
413
+ if methods.is_empty() {
414
+ self.line(" Self { _server: server }");
415
+ } else {
416
+ self.line(" Self { server }");
417
+ }
357
418
  self.line(" }");
358
419
  self.line("}");
359
420
  self.line("");
421
+
422
+ // Dispatch through the named trait so Arc methods cannot shadow RPCs.
360
423
  self.line("#[starpc::async_trait]");
361
424
  self.line(&format!(
362
425
  "impl<S: {}Server + 'static> starpc::Invoker for {}Handler<S> {{",
@@ -365,17 +428,24 @@ impl<'a> Generator<'a> {
365
428
  self.line(" async fn invoke_method(");
366
429
  self.line(" &self,");
367
430
  self.line(" _service_id: &str,");
368
- self.line(" method_id: &str,");
369
- self.line(" stream: Box<dyn starpc::Stream>,");
431
+ self.line(&format!(" {method_parameter}: &str,"));
432
+ self.line(&format!(
433
+ " {stream_parameter}: Box<dyn starpc::Stream>,"
434
+ ));
370
435
  self.line(" ) -> (bool, starpc::Result<()>) {");
371
- self.line(" match method_id {");
436
+ if methods.is_empty() {
437
+ self.line(" (false, Err(starpc::Error::Unimplemented))");
438
+ } else {
439
+ self.line(" match method_id {");
440
+ }
372
441
 
442
+ // Receive and return the messages required by each RPC shape.
373
443
  for method in &methods {
374
444
  self.line(&format!(" {:?} => {{", method.proto_name));
375
445
  if method.client_streaming && method.server_streaming {
376
446
  self.line(&format!(
377
- " (true, self.server.{}(stream).await)",
378
- method.name
447
+ " (true, <S as {}Server>::{}(self.server.as_ref(), stream).await)",
448
+ service_name, method.name
379
449
  ));
380
450
  } else if method.server_streaming {
381
451
  self.line(&format!(
@@ -386,13 +456,13 @@ impl<'a> Generator<'a> {
386
456
  self.line(" Err(e) => return (true, Err(e)),");
387
457
  self.line(" };");
388
458
  self.line(&format!(
389
- " (true, self.server.{}(request, stream).await)",
390
- method.name
459
+ " (true, <S as {}Server>::{}(self.server.as_ref(), request, stream).await)",
460
+ service_name, method.name
391
461
  ));
392
462
  } else if method.client_streaming {
393
463
  self.line(&format!(
394
- " match self.server.{}(stream.as_ref()).await {{",
395
- method.name
464
+ " match <S as {}Server>::{}(self.server.as_ref(), stream.as_ref()).await {{",
465
+ service_name, method.name
396
466
  ));
397
467
  self.line(" Ok(response) => {");
398
468
  self.line(
@@ -413,8 +483,8 @@ impl<'a> Generator<'a> {
413
483
  self.line(" Err(e) => return (true, Err(e)),");
414
484
  self.line(" };");
415
485
  self.line(&format!(
416
- " match self.server.{}(request).await {{",
417
- method.name
486
+ " match <S as {}Server>::{}(self.server.as_ref(), stream.context(), request).await {{",
487
+ service_name, method.name
418
488
  ));
419
489
  self.line(" Ok(response) => {");
420
490
  self.line(
@@ -430,11 +500,16 @@ impl<'a> Generator<'a> {
430
500
  self.line(" }");
431
501
  }
432
502
 
433
- self.line(" _ => (false, Err(starpc::Error::Unimplemented)),");
434
- self.line(" }");
503
+ // Leave unknown wire methods available for another handler.
504
+ if !methods.is_empty() {
505
+ self.line(" _ => (false, Err(starpc::Error::Unimplemented)),");
506
+ self.line(" }");
507
+ }
435
508
  self.line(" }");
436
509
  self.line("}");
437
510
  self.line("");
511
+
512
+ // Advertise the service and method identities used for routing.
438
513
  self.line(&format!(
439
514
  "impl<S: {}Server + 'static> starpc::Handler for {}Handler<S> {{",
440
515
  service_name, service_name
@@ -450,6 +525,7 @@ impl<'a> Generator<'a> {
450
525
  self.line("");
451
526
  }
452
527
 
528
+ /// Returns the fully qualified protobuf service identity used on the wire.
453
529
  fn service_id(&self) -> String {
454
530
  if self.service.package.is_empty() {
455
531
  self.service.proto_name.clone()
@@ -458,12 +534,14 @@ impl<'a> Generator<'a> {
458
534
  }
459
535
  }
460
536
 
537
+ /// Appends one complete generated line to the package output.
461
538
  fn line(&mut self, line: &str) {
462
539
  self.buf.push_str(line);
463
540
  self.buf.push('\n');
464
541
  }
465
542
  }
466
543
 
544
+ /// Formats the caller signature selected by a method's streaming modes.
467
545
  fn client_trait_method(service_name: &str, method: &prost_build::Method) -> String {
468
546
  let stream_trait = stream_trait_name(service_name, method);
469
547
  if method.client_streaming && method.server_streaming {
@@ -489,6 +567,7 @@ fn client_trait_method(service_name: &str, method: &prost_build::Method) -> Stri
489
567
  }
490
568
  }
491
569
 
570
+ /// Names a stream within its service, avoiding collisions with other services.
492
571
  fn stream_trait_name(service_name: &str, method: &prost_build::Method) -> String {
493
572
  format!(
494
573
  "{}{}Stream",
@@ -497,14 +576,17 @@ fn stream_trait_name(service_name: &str, method: &prost_build::Method) -> String
497
576
  )
498
577
  }
499
578
 
579
+ /// Names the public wire-service identity constant.
500
580
  fn service_id_const(service_name: &str) -> String {
501
581
  format!("{}_SERVICE_ID", screaming_snake(service_name))
502
582
  }
503
583
 
584
+ /// Names the handler's supported-method list.
504
585
  fn method_ids_const(service_name: &str) -> String {
505
586
  format!("{}_METHOD_IDS", screaming_snake(service_name))
506
587
  }
507
588
 
589
+ /// Separates uppercase transitions using the existing generated naming contract.
508
590
  fn screaming_snake(name: &str) -> String {
509
591
  let mut out = String::new();
510
592
  for (idx, ch) in name.chars().enumerate() {
@@ -516,6 +598,7 @@ fn screaming_snake(name: &str) -> String {
516
598
  out
517
599
  }
518
600
 
601
+ /// Converts a Prost method identifier into the stream type's name segment.
519
602
  fn upper_camel_from_snake(name: &str) -> String {
520
603
  let mut out = String::new();
521
604
  let mut upper = true;
@@ -534,12 +617,15 @@ fn upper_camel_from_snake(name: &str) -> String {
534
617
  out
535
618
  }
536
619
 
620
+ /// Checks generated API shape and half-close failure handling.
537
621
  #[cfg(test)]
538
622
  mod tests {
539
623
  use std::fs;
540
624
 
625
+ /// Keeps every RPC shape and preserves responses after a failed half-close.
541
626
  #[test]
542
627
  fn configure_generates_starpc_service_glue() {
628
+ // Isolate generated sources from other generator runs.
543
629
  let root = std::env::temp_dir().join(format!(
544
630
  "starpc-codegen-test-{}-{}",
545
631
  std::process::id(),
@@ -550,10 +636,10 @@ mod tests {
550
636
  ));
551
637
  let proto_dir = root.join("proto");
552
638
  let out_dir = root.join("out");
553
- let _ = fs::remove_dir_all(&root);
554
639
  fs::create_dir_all(&proto_dir).unwrap();
555
640
  fs::create_dir_all(&out_dir).unwrap();
556
641
 
642
+ // Exercise all call shapes in one service declaration.
557
643
  let proto = proto_dir.join("test.proto");
558
644
  fs::write(
559
645
  &proto,
@@ -574,10 +660,12 @@ message TestMsg {
574
660
  )
575
661
  .unwrap();
576
662
 
663
+ // Generate the real bindings and check their public surfaces.
577
664
  let mut config = super::configure();
578
665
  config.out_dir(&out_dir);
579
666
  config.compile_protos(&[proto], &[proto_dir]).unwrap();
580
667
 
668
+ // Verify the externally consumed client, server, and stream names.
581
669
  let generated = fs::read_to_string(out_dir.join("fixture.rs")).unwrap();
582
670
  assert!(generated.contains("pub const TEST_SERVICE_SERVICE_ID"));
583
671
  assert!(generated.contains("pub trait TestServiceClient"));
@@ -588,6 +676,13 @@ message TestMsg {
588
676
  assert!(generated.contains("TestServiceClientStreamStream"));
589
677
  assert!(generated.contains("TestServiceBidiStream"));
590
678
 
591
- let _ = fs::remove_dir_all(&root);
679
+ // A failed half-close must not discard the stream; msg_recv reports
680
+ // the call's outcome.
681
+ assert!(!generated.contains("close_send().await?"));
682
+ assert!(generated.contains("let _ = stream.close_send().await;"));
683
+ assert!(generated.contains("let _ = self.stream.close_send().await;"));
684
+
685
+ // Release the disposable generated sources after checking their contract.
686
+ fs::remove_dir_all(&root).unwrap();
592
687
  }
593
688
  }