starpc 0.52.0 → 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.
- package/README.md +37 -8
- package/dist/echo/client-test.d.ts +1 -1
- package/dist/echo/client-test.js +110 -2
- package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
- package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
- package/dist/integration/cross-language/ts-client.js +50 -37
- package/dist/integration/cross-language/ts-server.js +1 -36
- package/dist/rpcstream/rpcstream.d.ts +5 -1
- package/dist/rpcstream/rpcstream.js +75 -28
- package/dist/rpcstream/rpcstream.test.d.ts +1 -0
- package/dist/rpcstream/rpcstream.test.js +92 -0
- package/dist/srpc/client-rpc.d.ts +1 -0
- package/dist/srpc/client-rpc.js +9 -0
- package/dist/srpc/client-rpc.test.d.ts +1 -0
- package/dist/srpc/client-rpc.test.js +50 -0
- package/dist/srpc/client.js +18 -4
- package/dist/srpc/common-rpc.d.ts +5 -0
- package/dist/srpc/common-rpc.js +37 -8
- package/dist/srpc/common-rpc.test.js +2 -0
- package/dist/srpc/errors.d.ts +2 -0
- package/dist/srpc/errors.js +14 -0
- package/dist/srpc/index.d.ts +1 -1
- package/dist/srpc/index.js +1 -1
- package/dist/srpc/packet-codec.test.d.ts +1 -0
- package/dist/srpc/packet-codec.test.js +75 -0
- package/dist/srpc/packet.d.ts +1 -1
- package/dist/srpc/packet.js +11 -1
- package/dist/srpc/server.js +19 -6
- package/dist/srpc/server.test.js +43 -0
- package/dist/srpc/stream.d.ts +4 -1
- package/dist/srpc/stream.js +62 -3
- package/dist/srpc/stream.test.js +110 -1
- package/dist/srpc/termination.d.ts +27 -0
- package/dist/srpc/termination.js +56 -0
- package/dist/srpc/termination.test.d.ts +1 -0
- package/dist/srpc/termination.test.js +24 -0
- package/dist/testdata/packet-codec-vectors.json +64 -0
- package/echo/Cargo.toml +8 -0
- package/echo/client-test.ts +124 -2
- package/echo/echo_e2e_test.cpp +542 -985
- package/echo/echo_pb2.py +40 -0
- package/echo/echo_pb2.pyi +13 -0
- package/echo/echo_srpc.pb.cpp +3 -5
- package/echo/echo_srpc.pb.go +6 -9
- package/echo/echo_srpc.pb.hpp +23 -3
- package/echo/echo_srpc.pb.rs +61 -26
- package/echo/echo_srpc.py +309 -0
- package/echo/echo_srpc.pyi +85 -0
- package/echo/half_close_test.rs +178 -0
- package/echo/integration_server.rs +14 -21
- package/echo/main.rs +28 -48
- package/go.mod +3 -3
- package/go.sum +6 -8
- package/integration/cross-language/cpp-client.cpp +446 -402
- package/integration/cross-language/cpp-pipe-server.cpp +139 -0
- package/integration/cross-language/cpp-pipe_test.go +114 -0
- package/integration/cross-language/cpp-server.cpp +222 -186
- package/integration/cross-language/go-client/main.go +79 -3
- package/integration/cross-language/python-client.py +146 -0
- package/integration/cross-language/python-server.py +140 -0
- package/integration/cross-language/run.bash +190 -65
- package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
- package/integration/cross-language/tcp-packet-stream.ts +121 -0
- package/integration/cross-language/ts-client.ts +62 -40
- package/integration/cross-language/ts-server.ts +1 -45
- package/mock/mock_pb2.py +38 -0
- package/mock/mock_pb2.pyi +11 -0
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +10 -9
- package/mock/mock_srpc.py +71 -0
- package/mock/mock_srpc.pyi +27 -0
- package/package.json +19 -5
- package/srpc/CMakeLists.txt +3 -58
- package/srpc/README-cpp.md +69 -0
- package/srpc/__init__.py +0 -0
- package/srpc/accept.go +10 -4
- package/srpc/accept_test.go +25 -0
- package/srpc/build.rs +129 -34
- package/srpc/client-half-close_test.go +152 -0
- package/srpc/client-invoker.go +32 -42
- package/srpc/client-invoker_test.go +53 -0
- package/srpc/client-rpc.cpp +65 -80
- package/srpc/client-rpc.go +32 -33
- package/srpc/client-rpc.hpp +52 -38
- package/srpc/client-rpc.test.ts +67 -0
- package/srpc/client-rpc.ts +10 -0
- package/srpc/client-rpc_test.go +76 -0
- package/srpc/client.cpp +99 -87
- package/srpc/client.hpp +46 -35
- package/srpc/client.rs +78 -38
- package/srpc/client.ts +20 -4
- package/srpc/client_half_close_test.cc +331 -0
- package/srpc/client_invoker.rs +75 -0
- package/srpc/codec.rs +6 -0
- package/srpc/common-rpc.cpp +186 -146
- package/srpc/common-rpc.go +5 -4
- package/srpc/common-rpc.hpp +119 -89
- package/srpc/common-rpc.test.ts +2 -0
- package/srpc/common-rpc.ts +43 -8
- package/srpc/common-rpc_test.go +23 -0
- package/srpc/error.rs +19 -2
- package/srpc/errors.hpp +80 -50
- package/srpc/errors.ts +16 -0
- package/srpc/handler.hpp +9 -7
- package/srpc/handler.rs +7 -45
- package/srpc/index.ts +2 -0
- package/srpc/invoker.hpp +82 -55
- package/srpc/invoker.rs +14 -52
- package/srpc/lib.rs +2 -0
- package/srpc/message.hpp +17 -12
- package/srpc/msg-stream.go +1 -0
- package/srpc/msg-stream.hpp +108 -61
- package/srpc/mux.cpp +84 -84
- package/srpc/mux.hpp +48 -38
- package/srpc/mux.rs +4 -4
- package/srpc/net.go +12 -3
- package/srpc/packet-codec-vectors_test.go +195 -0
- package/srpc/packet-codec.test.ts +139 -0
- package/srpc/packet-rw.go +9 -2
- package/srpc/packet.cpp +60 -58
- package/srpc/packet.hpp +35 -20
- package/srpc/packet.rs +8 -27
- package/srpc/packet.ts +15 -2
- package/srpc/py.typed +0 -0
- package/srpc/rpc.rs +332 -115
- package/srpc/rpc_lifecycle_test.cc +333 -0
- package/srpc/rpcproto_pb2.py +40 -0
- package/srpc/rpcproto_pb2.pyi +40 -0
- package/srpc/server-cancel_test.go +39 -0
- package/srpc/server-rpc.cpp +83 -73
- package/srpc/server-rpc.go +21 -19
- package/srpc/server-rpc.hpp +56 -37
- package/srpc/server.rs +76 -131
- package/srpc/server.test.ts +50 -0
- package/srpc/server.ts +22 -6
- package/srpc/starpc.hpp +4 -2
- package/srpc/stream.go +4 -0
- package/srpc/stream.hpp +95 -34
- package/srpc/stream.rs +7 -2
- package/srpc/stream.test.ts +132 -1
- package/srpc/stream.ts +65 -9
- package/srpc/termination.test.ts +30 -0
- package/srpc/termination.ts +70 -0
- package/srpc/testing.rs +11 -66
- package/srpc/transport.rs +21 -71
- package/srpc/websocket.rs +106 -57
- package/srpc/writer.hpp +46 -32
- package/srpc/yamux.rs +82 -26
- package/srpc/tests/integration_test.rs +0 -495
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
|
|
31
|
+
config.service_generator(Box::new(StarpcServiceGenerator));
|
|
21
32
|
config
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
///
|
|
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("
|
|
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.
|
|
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(
|
|
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(
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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("
|
|
369
|
-
self.line(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
434
|
-
|
|
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
|
-
|
|
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
|
}
|