starpc 0.52.0 → 0.52.1

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 (65) hide show
  1. package/dist/echo/client-test.d.ts +1 -1
  2. package/dist/echo/client-test.js +110 -2
  3. package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
  4. package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
  5. package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
  6. package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
  7. package/dist/integration/cross-language/ts-client.js +50 -37
  8. package/dist/integration/cross-language/ts-server.js +1 -36
  9. package/dist/rpcstream/rpcstream.d.ts +5 -1
  10. package/dist/rpcstream/rpcstream.js +75 -28
  11. package/dist/rpcstream/rpcstream.test.d.ts +1 -0
  12. package/dist/rpcstream/rpcstream.test.js +92 -0
  13. package/dist/srpc/client.js +18 -4
  14. package/dist/srpc/common-rpc.test.js +2 -0
  15. package/dist/srpc/packet-codec.test.d.ts +1 -0
  16. package/dist/srpc/packet-codec.test.js +75 -0
  17. package/dist/srpc/packet.d.ts +1 -1
  18. package/dist/srpc/packet.js +11 -1
  19. package/dist/srpc/server.js +19 -6
  20. package/dist/srpc/server.test.js +43 -0
  21. package/dist/srpc/stream.d.ts +4 -1
  22. package/dist/srpc/stream.js +62 -3
  23. package/dist/srpc/stream.test.js +110 -1
  24. package/dist/srpc/termination.d.ts +27 -0
  25. package/dist/srpc/termination.js +56 -0
  26. package/dist/srpc/termination.test.d.ts +1 -0
  27. package/dist/srpc/termination.test.js +24 -0
  28. package/dist/testdata/packet-codec-vectors.json +64 -0
  29. package/echo/client-test.ts +124 -2
  30. package/echo/echo_pb2.py +40 -0
  31. package/echo/echo_pb2.pyi +13 -0
  32. package/echo/echo_srpc.py +306 -0
  33. package/echo/echo_srpc.pyi +85 -0
  34. package/go.mod +2 -2
  35. package/go.sum +14 -0
  36. package/integration/cross-language/go-client/main.go +79 -3
  37. package/integration/cross-language/python-client.py +146 -0
  38. package/integration/cross-language/python-server.py +140 -0
  39. package/integration/cross-language/run.bash +190 -65
  40. package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
  41. package/integration/cross-language/tcp-packet-stream.ts +121 -0
  42. package/integration/cross-language/ts-client.ts +62 -40
  43. package/integration/cross-language/ts-server.ts +1 -45
  44. package/mock/mock_pb2.py +38 -0
  45. package/mock/mock_pb2.pyi +11 -0
  46. package/mock/mock_srpc.py +71 -0
  47. package/mock/mock_srpc.pyi +27 -0
  48. package/package.json +19 -5
  49. package/srpc/__init__.py +0 -0
  50. package/srpc/client.ts +20 -4
  51. package/srpc/codec.rs +6 -0
  52. package/srpc/common-rpc.test.ts +2 -0
  53. package/srpc/packet-codec-vectors_test.go +195 -0
  54. package/srpc/packet-codec.test.ts +139 -0
  55. package/srpc/packet-rw.go +9 -2
  56. package/srpc/packet.ts +15 -2
  57. package/srpc/py.typed +0 -0
  58. package/srpc/rpcproto_pb2.py +40 -0
  59. package/srpc/rpcproto_pb2.pyi +40 -0
  60. package/srpc/server.test.ts +50 -0
  61. package/srpc/server.ts +22 -6
  62. package/srpc/stream.test.ts +132 -1
  63. package/srpc/stream.ts +65 -9
  64. package/srpc/termination.test.ts +30 -0
  65. package/srpc/termination.ts +70 -0
@@ -0,0 +1,306 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterable, AsyncIterator
4
+ from typing import Protocol
5
+
6
+ from google.protobuf import empty_pb2 as _google_protobuf_empty_pb2
7
+
8
+ from echo import (
9
+ echo_pb2 as _github_com_aperturerobotics_starpc_echo_echo_pb2,
10
+ )
11
+ from rpcstream import (
12
+ rpcstream_pb2 as _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2,
13
+ )
14
+ from starpc.call import Call, CallProtocolError
15
+ from starpc.client import Client
16
+ from starpc.server import ServiceRegistry
17
+ from starpc.service import MethodDescriptor, ServiceDescriptor, bidirectional_bytes
18
+
19
+ ECHOER_SERVICE = ServiceDescriptor(
20
+ "echo.Echoer",
21
+ (
22
+ MethodDescriptor(
23
+ "Echo",
24
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
25
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
26
+ False,
27
+ False,
28
+ ),
29
+ MethodDescriptor(
30
+ "EchoServerStream",
31
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
32
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
33
+ False,
34
+ True,
35
+ ),
36
+ MethodDescriptor(
37
+ "EchoClientStream",
38
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
39
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
40
+ True,
41
+ False,
42
+ ),
43
+ MethodDescriptor(
44
+ "EchoBidiStream",
45
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
46
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg,
47
+ True,
48
+ True,
49
+ ),
50
+ MethodDescriptor(
51
+ "RpcStream",
52
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket,
53
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket,
54
+ True,
55
+ True,
56
+ ),
57
+ MethodDescriptor(
58
+ "DoNothing",
59
+ _google_protobuf_empty_pb2.Empty,
60
+ _google_protobuf_empty_pb2.Empty,
61
+ False,
62
+ False,
63
+ ),
64
+ ),
65
+ )
66
+
67
+
68
+ class EchoerClient:
69
+ def __init__(self, client: Client, service: str | None = None) -> None:
70
+ self._client = client
71
+ self._service = service or "echo.Echoer"
72
+
73
+ async def echo(
74
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
75
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg:
76
+ call = await self._client.open_call(
77
+ self._service, "Echo", request.SerializeToString(deterministic=True)
78
+ )
79
+ try:
80
+ data = await call.receive()
81
+ if data is None:
82
+ raise CallProtocolError("missing unary response")
83
+ response = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
84
+ response.ParseFromString(data)
85
+ if await call.receive() is not None:
86
+ raise CallProtocolError("extra unary response")
87
+ return response
88
+ finally:
89
+ await call.aclose()
90
+
91
+ async def echo_server_stream(
92
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
93
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]:
94
+ call = await self._client.open_call(
95
+ self._service,
96
+ "EchoServerStream",
97
+ request.SerializeToString(deterministic=True),
98
+ )
99
+ try:
100
+ while True:
101
+ data = await call.receive()
102
+ if data is None:
103
+ return
104
+ response = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
105
+ response.ParseFromString(data)
106
+ yield response
107
+ finally:
108
+ await call.aclose()
109
+
110
+ async def echo_client_stream(
111
+ self,
112
+ requests: AsyncIterable[
113
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
114
+ ],
115
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg:
116
+ call = await self._client.open_call(self._service, "EchoClientStream")
117
+ try:
118
+ async for request in requests:
119
+ await call.send(request.SerializeToString(deterministic=True))
120
+ await call.finish()
121
+ data = await call.receive()
122
+ if data is None:
123
+ raise CallProtocolError("missing client-stream response")
124
+ response = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
125
+ response.ParseFromString(data)
126
+ if await call.receive() is not None:
127
+ raise CallProtocolError("extra client-stream response")
128
+ return response
129
+ finally:
130
+ await call.aclose()
131
+
132
+ async def echo_bidi_stream(
133
+ self,
134
+ requests: AsyncIterable[
135
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
136
+ ],
137
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]:
138
+ call = await self._client.open_call(self._service, "EchoBidiStream")
139
+
140
+ async def encoded() -> AsyncIterator[bytes]:
141
+ async for request in requests:
142
+ yield request.SerializeToString(deterministic=True)
143
+
144
+ async for data in bidirectional_bytes(call, encoded()):
145
+ response = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
146
+ response.ParseFromString(data)
147
+ yield response
148
+
149
+ async def rpc_stream(
150
+ self,
151
+ requests: AsyncIterable[
152
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
153
+ ],
154
+ ) -> AsyncIterator[
155
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
156
+ ]:
157
+ call = await self._client.open_call(self._service, "RpcStream")
158
+
159
+ async def encoded() -> AsyncIterator[bytes]:
160
+ async for request in requests:
161
+ yield request.SerializeToString(deterministic=True)
162
+
163
+ async for data in bidirectional_bytes(call, encoded()):
164
+ response = _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket()
165
+ response.ParseFromString(data)
166
+ yield response
167
+
168
+ async def do_nothing(
169
+ self, request: _google_protobuf_empty_pb2.Empty
170
+ ) -> _google_protobuf_empty_pb2.Empty:
171
+ call = await self._client.open_call(
172
+ self._service, "DoNothing", request.SerializeToString(deterministic=True)
173
+ )
174
+ try:
175
+ data = await call.receive()
176
+ if data is None:
177
+ raise CallProtocolError("missing unary response")
178
+ response = _google_protobuf_empty_pb2.Empty()
179
+ response.ParseFromString(data)
180
+ if await call.receive() is not None:
181
+ raise CallProtocolError("extra unary response")
182
+ return response
183
+ finally:
184
+ await call.aclose()
185
+
186
+
187
+ class EchoerServer(Protocol):
188
+ async def echo(
189
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
190
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
191
+ def echo_server_stream(
192
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
193
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
194
+ async def echo_client_stream(
195
+ self,
196
+ requests: AsyncIterator[
197
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
198
+ ],
199
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
200
+ def echo_bidi_stream(
201
+ self,
202
+ requests: AsyncIterator[
203
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
204
+ ],
205
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
206
+ def rpc_stream(
207
+ self,
208
+ requests: AsyncIterator[
209
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
210
+ ],
211
+ ) -> AsyncIterator[
212
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
213
+ ]: ...
214
+ async def do_nothing(
215
+ self, request: _google_protobuf_empty_pb2.Empty
216
+ ) -> _google_protobuf_empty_pb2.Empty: ...
217
+
218
+
219
+ def register_echoer(
220
+ registry: ServiceRegistry,
221
+ implementation: EchoerServer,
222
+ service: str = "echo.Echoer",
223
+ ) -> None:
224
+ async def echo_handler(call: Call) -> None:
225
+ first = await call.receive()
226
+ if first is None:
227
+ raise CallProtocolError("missing initial request")
228
+ request = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
229
+ request.ParseFromString(first)
230
+ response = await implementation.echo(request)
231
+ await call.send(response.SerializeToString(deterministic=True))
232
+
233
+ registry.register(service, "Echo", echo_handler)
234
+
235
+ async def echo_server_stream_handler(call: Call) -> None:
236
+ first = await call.receive()
237
+ if first is None:
238
+ raise CallProtocolError("missing initial request")
239
+ request = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
240
+ request.ParseFromString(first)
241
+ async for response in implementation.echo_server_stream(request):
242
+ await call.send(response.SerializeToString(deterministic=True))
243
+
244
+ registry.register(service, "EchoServerStream", echo_server_stream_handler)
245
+
246
+ async def echo_client_stream_handler(call: Call) -> None:
247
+ async def requests() -> AsyncIterator[
248
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
249
+ ]:
250
+ while True:
251
+ data = await call.receive()
252
+ if data is None:
253
+ return
254
+ request = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
255
+ request.ParseFromString(data)
256
+ yield request
257
+
258
+ response = await implementation.echo_client_stream(requests())
259
+ await call.send(response.SerializeToString(deterministic=True))
260
+
261
+ registry.register(service, "EchoClientStream", echo_client_stream_handler)
262
+
263
+ async def echo_bidi_stream_handler(call: Call) -> None:
264
+ async def requests() -> AsyncIterator[
265
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
266
+ ]:
267
+ while True:
268
+ data = await call.receive()
269
+ if data is None:
270
+ return
271
+ request = _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg()
272
+ request.ParseFromString(data)
273
+ yield request
274
+
275
+ async for response in implementation.echo_bidi_stream(requests()):
276
+ await call.send(response.SerializeToString(deterministic=True))
277
+
278
+ registry.register(service, "EchoBidiStream", echo_bidi_stream_handler)
279
+
280
+ async def rpc_stream_handler(call: Call) -> None:
281
+ async def requests() -> AsyncIterator[
282
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
283
+ ]:
284
+ while True:
285
+ data = await call.receive()
286
+ if data is None:
287
+ return
288
+ request = _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket()
289
+ request.ParseFromString(data)
290
+ yield request
291
+
292
+ async for response in implementation.rpc_stream(requests()):
293
+ await call.send(response.SerializeToString(deterministic=True))
294
+
295
+ registry.register(service, "RpcStream", rpc_stream_handler)
296
+
297
+ async def do_nothing_handler(call: Call) -> None:
298
+ first = await call.receive()
299
+ if first is None:
300
+ raise CallProtocolError("missing initial request")
301
+ request = _google_protobuf_empty_pb2.Empty()
302
+ request.ParseFromString(first)
303
+ response = await implementation.do_nothing(request)
304
+ await call.send(response.SerializeToString(deterministic=True))
305
+
306
+ registry.register(service, "DoNothing", do_nothing_handler)
@@ -0,0 +1,85 @@
1
+ from collections.abc import AsyncIterable, AsyncIterator
2
+ from typing import Protocol
3
+
4
+ from google.protobuf import empty_pb2 as _google_protobuf_empty_pb2
5
+
6
+ from echo import (
7
+ echo_pb2 as _github_com_aperturerobotics_starpc_echo_echo_pb2,
8
+ )
9
+ from rpcstream import (
10
+ rpcstream_pb2 as _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2,
11
+ )
12
+ from starpc.client import Client
13
+ from starpc.server import ServiceRegistry
14
+ from starpc.service import ServiceDescriptor
15
+
16
+ ECHOER_SERVICE: ServiceDescriptor
17
+
18
+ class EchoerClient:
19
+ def __init__(self, client: Client, service: str | None = None) -> None: ...
20
+ async def echo(
21
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
22
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
23
+ def echo_server_stream(
24
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
25
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
26
+ async def echo_client_stream(
27
+ self,
28
+ requests: AsyncIterable[
29
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
30
+ ],
31
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
32
+ def echo_bidi_stream(
33
+ self,
34
+ requests: AsyncIterable[
35
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
36
+ ],
37
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
38
+ def rpc_stream(
39
+ self,
40
+ requests: AsyncIterable[
41
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
42
+ ],
43
+ ) -> AsyncIterator[
44
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
45
+ ]: ...
46
+ async def do_nothing(
47
+ self, request: _google_protobuf_empty_pb2.Empty
48
+ ) -> _google_protobuf_empty_pb2.Empty: ...
49
+
50
+ class EchoerServer(Protocol):
51
+ async def echo(
52
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
53
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
54
+ def echo_server_stream(
55
+ self, request: _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
56
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
57
+ async def echo_client_stream(
58
+ self,
59
+ requests: AsyncIterator[
60
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
61
+ ],
62
+ ) -> _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg: ...
63
+ def echo_bidi_stream(
64
+ self,
65
+ requests: AsyncIterator[
66
+ _github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg
67
+ ],
68
+ ) -> AsyncIterator[_github_com_aperturerobotics_starpc_echo_echo_pb2.EchoMsg]: ...
69
+ def rpc_stream(
70
+ self,
71
+ requests: AsyncIterator[
72
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
73
+ ],
74
+ ) -> AsyncIterator[
75
+ _github_com_aperturerobotics_starpc_rpcstream_rpcstream_pb2.RpcStreamPacket
76
+ ]: ...
77
+ async def do_nothing(
78
+ self, request: _google_protobuf_empty_pb2.Empty
79
+ ) -> _google_protobuf_empty_pb2.Empty: ...
80
+
81
+ def register_echoer(
82
+ registry: ServiceRegistry,
83
+ implementation: EchoerServer,
84
+ service: str = "echo.Echoer",
85
+ ) -> None: ...
package/go.mod CHANGED
@@ -3,7 +3,7 @@ module github.com/aperturerobotics/starpc
3
3
  go 1.25.0
4
4
 
5
5
  require (
6
- github.com/aperturerobotics/common v0.35.0 // latest
6
+ github.com/aperturerobotics/common v0.35.1-0.20260808123156-c77af26f63bd // latest
7
7
  github.com/aperturerobotics/protobuf-go-lite v0.16.0 // latest
8
8
  github.com/aperturerobotics/util v1.34.9 // latest
9
9
  )
@@ -12,7 +12,7 @@ require (
12
12
  github.com/aperturerobotics/abseil-cpp v0.0.0-20260131110040-4bb56e2f9017 // aperture-2
13
13
  github.com/aperturerobotics/cli v1.1.0 // indirect
14
14
  github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260705010911-9f53feac967b // indirect
15
- github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260712054757-d8078c296c17 // indirect
15
+ github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260808023521-7b1595380c3f // indirect
16
16
  github.com/aperturerobotics/go-websocket v1.8.15-0.20260619192713-a096778f08c1 // master
17
17
  github.com/aperturerobotics/json-iterator-lite v1.1.0 // indirect
18
18
  github.com/aperturerobotics/protobuf v0.0.0-20260203024654-8201686529c4 // wasi
package/go.sum CHANGED
@@ -6,10 +6,24 @@ github.com/aperturerobotics/common v0.34.4 h1:RQTOm6LxEnucXkED/kruhKKAOL6tbaPzMg
6
6
  github.com/aperturerobotics/common v0.34.4/go.mod h1:xnb1VBRs3x2o1PaKg0OlP7SqDaC9Hmrmt9xH85rOS+A=
7
7
  github.com/aperturerobotics/common v0.35.0 h1:VU4OPMXJyqT37lTOtNEMa3QLW5tR8YIBAKbjgxSzuZs=
8
8
  github.com/aperturerobotics/common v0.35.0/go.mod h1:xnb1VBRs3x2o1PaKg0OlP7SqDaC9Hmrmt9xH85rOS+A=
9
+ github.com/aperturerobotics/common v0.35.1-0.20260808094909-49d3232b4f83 h1:QkFoyeJ0QBw21a+1YR0kB+JaBSY2mHFiIGiFwWUJ/wQ=
10
+ github.com/aperturerobotics/common v0.35.1-0.20260808094909-49d3232b4f83/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
11
+ github.com/aperturerobotics/common v0.35.1-0.20260808095531-8f6fd4367389 h1:J/vc+AKFh8hIVJJ2LrAJVwEgIYvFeQ5XANg3dK3OnYk=
12
+ github.com/aperturerobotics/common v0.35.1-0.20260808095531-8f6fd4367389/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
13
+ github.com/aperturerobotics/common v0.35.1-0.20260808100945-fe2b561bec93 h1:Bc7ThfLt5ekHZmOH9vGW0PiiuVUl1R+vneFH/G0j5D4=
14
+ github.com/aperturerobotics/common v0.35.1-0.20260808100945-fe2b561bec93/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
15
+ github.com/aperturerobotics/common v0.35.1-0.20260808101203-caeb6303ba93 h1:JoERBAX9HuOzopNLZQCT851YnYz9h9nYSpEe6XjcP0o=
16
+ github.com/aperturerobotics/common v0.35.1-0.20260808101203-caeb6303ba93/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
17
+ github.com/aperturerobotics/common v0.35.1-0.20260808102028-33a08259dd38 h1:c3IXUqJHt1EAlCyD0d0IrI7YYf4txwICtoh+ylQV6tU=
18
+ github.com/aperturerobotics/common v0.35.1-0.20260808102028-33a08259dd38/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
19
+ github.com/aperturerobotics/common v0.35.1-0.20260808123156-c77af26f63bd h1:BTfB1V2X+u3/56negSjrx+AWg+Uk84rFFryvpGTj7f4=
20
+ github.com/aperturerobotics/common v0.35.1-0.20260808123156-c77af26f63bd/go.mod h1:sPjrQZeS/yr9kBro4rN2BrYvG9TCn1eIGk7dMH/E2NQ=
9
21
  github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260705010911-9f53feac967b h1:MMk+AbWPCMfqc2Lm2bJl8lCCsuvlM2+C0CP10fpE3bc=
10
22
  github.com/aperturerobotics/go-protoc-gen-prost v0.0.0-20260705010911-9f53feac967b/go.mod h1:OBb/beWmr/pDIZAUfi86j/4tBh2v5ctTxKMqSnh9c/4=
11
23
  github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260712054757-d8078c296c17 h1:WT1YmZjJf8fUF1zKbeNNKXFA+t8Wtbr8AVMAmdKcrUU=
12
24
  github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260712054757-d8078c296c17/go.mod h1:vEq8i7EKb32+KXGtIEZjjhNns+BdsL2dUMw4uhy3578=
25
+ github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260808023521-7b1595380c3f h1:UszdpmKBfQd0LdOZjypeRGrHz2tcPpmKpw6SV3DYyN0=
26
+ github.com/aperturerobotics/go-protoc-wasi v0.0.0-20260808023521-7b1595380c3f/go.mod h1:vEq8i7EKb32+KXGtIEZjjhNns+BdsL2dUMw4uhy3578=
13
27
  github.com/aperturerobotics/go-websocket v1.8.15-0.20260619192713-a096778f08c1 h1:q2qkdNOrnhAFf6B7TBeUoAYstYWxPvozN+3vJi2jgp0=
14
28
  github.com/aperturerobotics/go-websocket v1.8.15-0.20260619192713-a096778f08c1/go.mod h1:9KnSGuqxSXbdB/Oi0I6vvfPLkclfJwMGAQaDDGVgGow=
15
29
  github.com/aperturerobotics/json-iterator-lite v1.1.0 h1:ZLLqHhHTKYlmCAP873ras2e/yVU/THtwC+ji3tXLMMg=
@@ -6,19 +6,23 @@ import (
6
6
  "io"
7
7
  "net"
8
8
  "os"
9
+ "strings"
9
10
 
10
11
  "github.com/aperturerobotics/starpc/echo"
12
+ "github.com/aperturerobotics/starpc/rpcstream"
11
13
  "github.com/aperturerobotics/starpc/srpc"
12
14
  )
13
15
 
14
16
  const bodyTxt = "hello world via starpc cross-language e2e test"
15
17
 
16
18
  func main() {
17
- if len(os.Args) <= 1 {
18
- fmt.Fprintf(os.Stderr, "usage: go-client <addr>\n")
19
+ nested := len(os.Args) >= 3 && os.Args[1] == "--nested"
20
+ nestedRelease := len(os.Args) >= 3 && os.Args[1] == "--nested-release"
21
+ if len(os.Args) != 2 && !nested && !nestedRelease {
22
+ fmt.Fprintf(os.Stderr, "usage: go-client [--nested] <addr>\n")
19
23
  os.Exit(1)
20
24
  }
21
- addr := os.Args[1]
25
+ addr := os.Args[len(os.Args)-1]
22
26
  openStream := func(ctx context.Context, msgHandler srpc.PacketDataHandler, closeHandler srpc.CloseHandler) (srpc.PacketWriter, error) {
23
27
  conn, err := net.Dial("tcp", addr) //nolint:gosec
24
28
  if err != nil {
@@ -48,6 +52,12 @@ func main() {
48
52
  fmt.Fprintf(os.Stderr, "bidi stream test failed: %v\n", err)
49
53
  os.Exit(1)
50
54
  }
55
+ if nested || nestedRelease {
56
+ if err := testNested(ctx, echoClient, nestedRelease); err != nil {
57
+ fmt.Fprintf(os.Stderr, "nested lifecycle test failed: %v\n", err)
58
+ os.Exit(1)
59
+ }
60
+ }
51
61
  fmt.Println("All tests passed.")
52
62
  }
53
63
 
@@ -143,3 +153,69 @@ func testBidiStream(ctx context.Context, client echo.SRPCEchoerClient) error {
143
153
  fmt.Println(" PASSED")
144
154
  return nil
145
155
  }
156
+
157
+ func testNested(ctx context.Context, parent echo.SRPCEchoerClient, release bool) error {
158
+ proxy := rpcstream.NewRpcStreamClient(parent.RpcStream, "test", true)
159
+ if err := testUnary(ctx, echo.NewSRPCEchoerClient(proxy)); err != nil {
160
+ return fmt.Errorf("nested unary: %w", err)
161
+ }
162
+
163
+ if release {
164
+ missing := rpcstream.NewRpcStreamClient(parent.RpcStream, "missing", true)
165
+ var missingOut echo.EchoMsg
166
+ if err := missing.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{}, &missingOut); err == nil || !strings.Contains(err.Error(), "unknown component: missing") {
167
+ return fmt.Errorf("unknown component returned wrong result: %v", err)
168
+ }
169
+ }
170
+
171
+ stream, err := proxy.NewStream(ctx, "echo.Echoer", "EchoBidiStream", nil)
172
+ if err != nil {
173
+ return fmt.Errorf("nested bidi: %w", err)
174
+ }
175
+ if err := stream.MsgSend(&echo.EchoMsg{Body: "nested later data"}); err != nil {
176
+ return fmt.Errorf("nested later data: %w", err)
177
+ }
178
+ if err := stream.Close(); err != nil {
179
+ return fmt.Errorf("nested cancel: %w", err)
180
+ }
181
+
182
+ var terminal echo.EchoMsg
183
+ if err := proxy.ExecCall(ctx, "missing.Service", "Missing", &echo.EchoMsg{}, &terminal); err == nil || !strings.Contains(err.Error(), "missing.Service") {
184
+ return fmt.Errorf("unknown nested method returned wrong result: %v", err)
185
+ }
186
+
187
+ if release {
188
+ var terminalError echo.EchoMsg
189
+ if err := proxy.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{Body: "__nested_error__"}, &terminalError); err == nil || !strings.Contains(err.Error(), "nested terminal error") {
190
+ return fmt.Errorf("terminal nested error returned wrong result: %v", err)
191
+ }
192
+ }
193
+
194
+ if release {
195
+ releaseClient := rpcstream.NewRpcStreamClient(parent.RpcStream, "release", true)
196
+ var released echo.EchoMsg
197
+ err := releaseClient.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{Body: "__nested_release__"}, &released)
198
+ if err == nil || (!strings.Contains(err.Error(), "stream closed before the remote reported completion") && !strings.Contains(err.Error(), "EOF") && !strings.Contains(err.Error(), "canceled")) {
199
+ return fmt.Errorf("release during active call returned wrong result: %v", err)
200
+ }
201
+ status, err := parent.Echo(ctx, &echo.EchoMsg{Body: "__nested_release_status__"})
202
+ if err != nil {
203
+ return fmt.Errorf("release completion status failed: %w", err)
204
+ }
205
+ if status.GetBody() != "released" {
206
+ return fmt.Errorf("release completion returned %q", status.GetBody())
207
+ }
208
+ if err := releaseClient.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{}, &released); err == nil || !strings.Contains(err.Error(), "unknown component: release") {
209
+ return fmt.Errorf("released component returned wrong result: %v", err)
210
+ }
211
+ }
212
+
213
+ direct, err := rpcstream.OpenRpcStream(ctx, parent.RpcStream, "test", true)
214
+ if err != nil {
215
+ return fmt.Errorf("nested direct open: %w", err)
216
+ }
217
+ if err := direct.Close(); err != nil {
218
+ return fmt.Errorf("nested abrupt close: %w", err)
219
+ }
220
+ return nil
221
+ }