starpc 0.41.2 → 0.43.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.
- package/README.md +101 -20
- package/dist/mock/mock.pb.d.ts +1 -1
- package/dist/mock/mock.pb.js +4 -5
- package/dist/mock/mock_srpc.pb.d.ts +3 -3
- package/dist/mock/mock_srpc.pb.js +5 -5
- package/echo/Cargo.toml +21 -0
- package/echo/build.rs +15 -0
- package/echo/echo.pb.cc +405 -0
- package/echo/echo.pb.go +9 -27
- package/echo/echo.pb.h +364 -0
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/gen/mod.rs +3 -0
- package/echo/main.rs +162 -0
- package/go.mod +18 -10
- package/go.sum +28 -18
- package/mock/mock.pb.cc +394 -0
- package/mock/mock.pb.go +9 -27
- package/mock/mock.pb.h +366 -0
- package/mock/mock.pb.ts +11 -13
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.ts +12 -9
- package/package.json +27 -25
- package/srpc/Cargo.toml +26 -0
- package/srpc/build.rs +15 -0
- package/srpc/client.rs +356 -0
- package/srpc/codec.rs +225 -0
- package/srpc/error.rs +177 -0
- package/srpc/handler.rs +163 -0
- package/srpc/invoker.rs +192 -0
- package/srpc/lib.rs +107 -0
- package/srpc/message.rs +9 -0
- package/srpc/mux.rs +353 -0
- package/srpc/packet.rs +334 -0
- package/srpc/proto/mod.rs +10 -0
- package/srpc/rpc.rs +777 -0
- package/srpc/rpcproto.pb.cc +1381 -0
- package/srpc/rpcproto.pb.go +75 -183
- package/srpc/rpcproto.pb.h +1451 -0
- package/srpc/server.rs +337 -0
- package/srpc/stream.rs +304 -0
- package/srpc/testing.rs +290 -0
- package/srpc/tests/integration_test.rs +495 -0
- package/srpc/transport.rs +218 -0
- package/Makefile +0 -154
package/srpc/mux.rs
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
//! Service multiplexer for routing RPC calls.
|
|
2
|
+
//!
|
|
3
|
+
//! The Mux provides a registry for RPC handlers and routes incoming calls
|
|
4
|
+
//! to the appropriate handler based on service and method IDs.
|
|
5
|
+
//!
|
|
6
|
+
//! This implementation matches the Go Mux behavior, supporting:
|
|
7
|
+
//! - Service-level handler registration
|
|
8
|
+
//! - Method-level routing within services
|
|
9
|
+
//! - Fallback invokers for unmatched calls
|
|
10
|
+
//! - Empty service ID handling (searches all services)
|
|
11
|
+
|
|
12
|
+
use async_trait::async_trait;
|
|
13
|
+
use std::collections::HashMap;
|
|
14
|
+
use std::sync::{Arc, RwLock};
|
|
15
|
+
|
|
16
|
+
use crate::error::{Error, Result};
|
|
17
|
+
use crate::handler::Handler;
|
|
18
|
+
use crate::invoker::Invoker;
|
|
19
|
+
use crate::stream::Stream;
|
|
20
|
+
|
|
21
|
+
/// A shared handler reference.
|
|
22
|
+
pub type ArcHandler = Arc<dyn Handler>;
|
|
23
|
+
|
|
24
|
+
/// Method handlers map: method_id -> handler.
|
|
25
|
+
type MethodHandlers = HashMap<String, ArcHandler>;
|
|
26
|
+
|
|
27
|
+
/// Service multiplexer for routing RPC calls to handlers.
|
|
28
|
+
///
|
|
29
|
+
/// The Mux maintains a registry of handlers and routes incoming RPC calls
|
|
30
|
+
/// to the appropriate handler based on service and method IDs.
|
|
31
|
+
///
|
|
32
|
+
/// # Example
|
|
33
|
+
///
|
|
34
|
+
/// ```ignore
|
|
35
|
+
/// use starpc::{Mux, Handler};
|
|
36
|
+
///
|
|
37
|
+
/// let mux = Mux::new();
|
|
38
|
+
/// mux.register(Arc::new(MyServiceHandler))?;
|
|
39
|
+
///
|
|
40
|
+
/// // The mux can now route calls to MyServiceHandler
|
|
41
|
+
/// ```
|
|
42
|
+
pub struct Mux {
|
|
43
|
+
/// Map of service ID -> method ID -> handler.
|
|
44
|
+
/// This matches the Go implementation's structure.
|
|
45
|
+
services: RwLock<HashMap<String, MethodHandlers>>,
|
|
46
|
+
|
|
47
|
+
/// Fallback invokers to try if no handler is found.
|
|
48
|
+
fallbacks: RwLock<Vec<Arc<dyn Invoker>>>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl Default for Mux {
|
|
52
|
+
fn default() -> Self {
|
|
53
|
+
Self::new()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl Mux {
|
|
58
|
+
/// Creates a new empty Mux.
|
|
59
|
+
pub fn new() -> Self {
|
|
60
|
+
Self {
|
|
61
|
+
services: RwLock::new(HashMap::new()),
|
|
62
|
+
fallbacks: RwLock::new(Vec::new()),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Creates a new Mux with fallback invokers.
|
|
67
|
+
///
|
|
68
|
+
/// Fallback invokers are called in order when no handler matches
|
|
69
|
+
/// the requested service/method.
|
|
70
|
+
pub fn with_fallbacks(fallbacks: Vec<Arc<dyn Invoker>>) -> Self {
|
|
71
|
+
Self {
|
|
72
|
+
services: RwLock::new(HashMap::new()),
|
|
73
|
+
fallbacks: RwLock::new(fallbacks),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Registers a handler for its service.
|
|
78
|
+
///
|
|
79
|
+
/// The handler is registered for all methods it declares via `method_ids()`.
|
|
80
|
+
///
|
|
81
|
+
/// # Errors
|
|
82
|
+
///
|
|
83
|
+
/// Returns `Error::EmptyServiceId` if the handler's service ID is empty.
|
|
84
|
+
pub fn register(&self, handler: ArcHandler) -> Result<()> {
|
|
85
|
+
let service_id = handler.service_id();
|
|
86
|
+
if service_id.is_empty() {
|
|
87
|
+
return Err(Error::EmptyServiceId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let method_ids = handler.method_ids();
|
|
91
|
+
|
|
92
|
+
let mut services = self.services.write().unwrap();
|
|
93
|
+
let service_methods = services
|
|
94
|
+
.entry(service_id.to_string())
|
|
95
|
+
.or_insert_with(HashMap::new);
|
|
96
|
+
|
|
97
|
+
for method_id in method_ids {
|
|
98
|
+
if !method_id.is_empty() {
|
|
99
|
+
service_methods.insert(method_id.to_string(), handler.clone());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Ok(())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Adds a fallback invoker.
|
|
107
|
+
///
|
|
108
|
+
/// Fallback invokers are tried in order when no handler matches.
|
|
109
|
+
pub fn add_fallback(&self, invoker: Arc<dyn Invoker>) {
|
|
110
|
+
self.fallbacks.write().unwrap().push(invoker);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Checks if a service is registered.
|
|
114
|
+
pub fn has_service(&self, service_id: &str) -> bool {
|
|
115
|
+
if service_id.is_empty() {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let services = self.services.read().unwrap();
|
|
120
|
+
services
|
|
121
|
+
.get(service_id)
|
|
122
|
+
.map(|methods| !methods.is_empty())
|
|
123
|
+
.unwrap_or(false)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Checks if a service method is registered.
|
|
127
|
+
pub fn has_service_method(&self, service_id: &str, method_id: &str) -> bool {
|
|
128
|
+
if service_id.is_empty() || method_id.is_empty() {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let services = self.services.read().unwrap();
|
|
133
|
+
services
|
|
134
|
+
.get(service_id)
|
|
135
|
+
.and_then(|methods| methods.get(method_id))
|
|
136
|
+
.is_some()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// Gets a handler by service ID and method ID.
|
|
140
|
+
fn get_handler(&self, service_id: &str, method_id: &str) -> Option<ArcHandler> {
|
|
141
|
+
let services = self.services.read().unwrap();
|
|
142
|
+
services
|
|
143
|
+
.get(service_id)
|
|
144
|
+
.and_then(|methods| methods.get(method_id).cloned())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Finds a handler that implements the given method (searching all services).
|
|
148
|
+
///
|
|
149
|
+
/// This is used when service_id is empty.
|
|
150
|
+
fn find_handler_for_method(&self, method_id: &str) -> Option<ArcHandler> {
|
|
151
|
+
let services = self.services.read().unwrap();
|
|
152
|
+
for methods in services.values() {
|
|
153
|
+
if let Some(handler) = methods.get(method_id) {
|
|
154
|
+
return Some(handler.clone());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
None
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// QueryableInvoker allows checking if methods are implemented without invoking.
|
|
162
|
+
///
|
|
163
|
+
/// This matches the Go interface of the same name.
|
|
164
|
+
pub trait QueryableInvoker {
|
|
165
|
+
/// Checks if the service is registered.
|
|
166
|
+
fn has_service(&self, service_id: &str) -> bool;
|
|
167
|
+
|
|
168
|
+
/// Checks if the service method is registered.
|
|
169
|
+
fn has_service_method(&self, service_id: &str, method_id: &str) -> bool;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
impl QueryableInvoker for Mux {
|
|
173
|
+
fn has_service(&self, service_id: &str) -> bool {
|
|
174
|
+
Mux::has_service(self, service_id)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn has_service_method(&self, service_id: &str, method_id: &str) -> bool {
|
|
178
|
+
Mux::has_service_method(self, service_id, method_id)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[async_trait]
|
|
183
|
+
impl Invoker for Mux {
|
|
184
|
+
async fn invoke_method(
|
|
185
|
+
&self,
|
|
186
|
+
service_id: &str,
|
|
187
|
+
method_id: &str,
|
|
188
|
+
stream: Box<dyn Stream>,
|
|
189
|
+
) -> (bool, Result<()>) {
|
|
190
|
+
// Look up the handler
|
|
191
|
+
let handler = if service_id.is_empty() {
|
|
192
|
+
// If service_id is empty, search all services for the method
|
|
193
|
+
self.find_handler_for_method(method_id)
|
|
194
|
+
} else {
|
|
195
|
+
self.get_handler(service_id, method_id)
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if let Some(h) = handler {
|
|
199
|
+
// Invoke the handler
|
|
200
|
+
return h.invoke_method(service_id, method_id, stream).await;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Try the first fallback invoker.
|
|
204
|
+
// Only the first fallback gets a chance because the stream is consumed by the call.
|
|
205
|
+
// This matches Go behavior where the stream cannot be reused after an invocation attempt.
|
|
206
|
+
let maybe_fallback = self.fallbacks.read().unwrap().first().cloned();
|
|
207
|
+
if let Some(fallback) = maybe_fallback {
|
|
208
|
+
return fallback.invoke_method(service_id, method_id, stream).await;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
(false, Err(Error::Unimplemented))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[cfg(test)]
|
|
216
|
+
mod tests {
|
|
217
|
+
use super::*;
|
|
218
|
+
use crate::stream::Context;
|
|
219
|
+
|
|
220
|
+
struct TestHandler {
|
|
221
|
+
service_id: &'static str,
|
|
222
|
+
method_ids: &'static [&'static str],
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[async_trait]
|
|
226
|
+
impl Invoker for TestHandler {
|
|
227
|
+
async fn invoke_method(
|
|
228
|
+
&self,
|
|
229
|
+
_service_id: &str,
|
|
230
|
+
_method_id: &str,
|
|
231
|
+
_stream: Box<dyn Stream>,
|
|
232
|
+
) -> (bool, Result<()>) {
|
|
233
|
+
(true, Ok(()))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl Handler for TestHandler {
|
|
238
|
+
fn service_id(&self) -> &'static str {
|
|
239
|
+
self.service_id
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn method_ids(&self) -> &'static [&'static str] {
|
|
243
|
+
self.method_ids
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn test_mux_register() {
|
|
249
|
+
let mux = Mux::new();
|
|
250
|
+
let handler = Arc::new(TestHandler {
|
|
251
|
+
service_id: "test.Service",
|
|
252
|
+
method_ids: &["Method1", "Method2"],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
mux.register(handler).unwrap();
|
|
256
|
+
|
|
257
|
+
assert!(mux.has_service("test.Service"));
|
|
258
|
+
assert!(mux.has_service_method("test.Service", "Method1"));
|
|
259
|
+
assert!(mux.has_service_method("test.Service", "Method2"));
|
|
260
|
+
assert!(!mux.has_service_method("test.Service", "Method3"));
|
|
261
|
+
assert!(!mux.has_service("other.Service"));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn test_mux_register_empty_service_id() {
|
|
266
|
+
let mux = Mux::new();
|
|
267
|
+
let handler = Arc::new(TestHandler {
|
|
268
|
+
service_id: "",
|
|
269
|
+
method_ids: &["Method1"],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
let result = mux.register(handler);
|
|
273
|
+
assert!(matches!(result, Err(Error::EmptyServiceId)));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn test_mux_has_service_empty_id() {
|
|
278
|
+
let mux = Mux::new();
|
|
279
|
+
assert!(!mux.has_service(""));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#[test]
|
|
283
|
+
fn test_mux_has_service_method_empty_ids() {
|
|
284
|
+
let mux = Mux::new();
|
|
285
|
+
let handler = Arc::new(TestHandler {
|
|
286
|
+
service_id: "test.Service",
|
|
287
|
+
method_ids: &["Method1"],
|
|
288
|
+
});
|
|
289
|
+
mux.register(handler).unwrap();
|
|
290
|
+
|
|
291
|
+
assert!(!mux.has_service_method("", "Method1"));
|
|
292
|
+
assert!(!mux.has_service_method("test.Service", ""));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[test]
|
|
296
|
+
fn test_mux_find_handler_for_method() {
|
|
297
|
+
let mux = Mux::new();
|
|
298
|
+
let handler = Arc::new(TestHandler {
|
|
299
|
+
service_id: "test.Service",
|
|
300
|
+
method_ids: &["UniqueMethod"],
|
|
301
|
+
});
|
|
302
|
+
mux.register(handler).unwrap();
|
|
303
|
+
|
|
304
|
+
// Should find handler when searching all services
|
|
305
|
+
let found = mux.find_handler_for_method("UniqueMethod");
|
|
306
|
+
assert!(found.is_some());
|
|
307
|
+
|
|
308
|
+
let not_found = mux.find_handler_for_method("NonExistent");
|
|
309
|
+
assert!(not_found.is_none());
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#[tokio::test]
|
|
313
|
+
async fn test_mux_invoke_with_empty_service_id() {
|
|
314
|
+
let mux = Mux::new();
|
|
315
|
+
let handler = Arc::new(TestHandler {
|
|
316
|
+
service_id: "test.Service",
|
|
317
|
+
method_ids: &["TestMethod"],
|
|
318
|
+
});
|
|
319
|
+
mux.register(handler).unwrap();
|
|
320
|
+
|
|
321
|
+
// Create a mock stream
|
|
322
|
+
struct MockStream;
|
|
323
|
+
|
|
324
|
+
#[async_trait]
|
|
325
|
+
impl Stream for MockStream {
|
|
326
|
+
fn context(&self) -> &Context {
|
|
327
|
+
static CTX: std::sync::OnceLock<Context> = std::sync::OnceLock::new();
|
|
328
|
+
CTX.get_or_init(Context::new)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async fn send_bytes(&self, _data: bytes::Bytes) -> Result<()> {
|
|
332
|
+
Ok(())
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async fn recv_bytes(&self) -> Result<bytes::Bytes> {
|
|
336
|
+
Err(Error::StreamClosed)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async fn close_send(&self) -> Result<()> {
|
|
340
|
+
Ok(())
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async fn close(&self) -> Result<()> {
|
|
344
|
+
Ok(())
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Should find the method even with empty service ID
|
|
349
|
+
let (handled, result) = mux.invoke_method("", "TestMethod", Box::new(MockStream)).await;
|
|
350
|
+
assert!(handled);
|
|
351
|
+
assert!(result.is_ok());
|
|
352
|
+
}
|
|
353
|
+
}
|
package/srpc/packet.rs
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//! Packet utility functions and validation.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides helper functions for creating and validating
|
|
4
|
+
//! starpc protocol packets.
|
|
5
|
+
|
|
6
|
+
use crate::error::{Error, Result};
|
|
7
|
+
use crate::proto::{packet::Body, CallData, CallStart, Packet};
|
|
8
|
+
use crate::transport::encode_optional_data;
|
|
9
|
+
use bytes::Bytes;
|
|
10
|
+
|
|
11
|
+
/// Creates a new CallStart packet.
|
|
12
|
+
///
|
|
13
|
+
/// # Arguments
|
|
14
|
+
/// * `service` - The service ID
|
|
15
|
+
/// * `method` - The method ID
|
|
16
|
+
/// * `data` - Optional initial data (None = no data, Some(empty) = empty data)
|
|
17
|
+
pub fn new_call_start(
|
|
18
|
+
service: impl Into<String>,
|
|
19
|
+
method: impl Into<String>,
|
|
20
|
+
data: Option<Bytes>,
|
|
21
|
+
) -> Packet {
|
|
22
|
+
let (data_bytes, data_is_zero) = encode_optional_data(data);
|
|
23
|
+
|
|
24
|
+
Packet {
|
|
25
|
+
body: Some(Body::CallStart(CallStart {
|
|
26
|
+
rpc_service: service.into(),
|
|
27
|
+
rpc_method: method.into(),
|
|
28
|
+
data: data_bytes,
|
|
29
|
+
data_is_zero,
|
|
30
|
+
})),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Creates a new CallData packet with data.
|
|
35
|
+
///
|
|
36
|
+
/// # Arguments
|
|
37
|
+
/// * `data` - The data to send (can be empty)
|
|
38
|
+
pub fn new_call_data(data: Vec<u8>) -> Packet {
|
|
39
|
+
let data_is_zero = data.is_empty();
|
|
40
|
+
Packet {
|
|
41
|
+
body: Some(Body::CallData(CallData {
|
|
42
|
+
data,
|
|
43
|
+
data_is_zero,
|
|
44
|
+
complete: false,
|
|
45
|
+
error: String::new(),
|
|
46
|
+
})),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Creates a new CallData packet with optional data and flags.
|
|
51
|
+
///
|
|
52
|
+
/// # Arguments
|
|
53
|
+
/// * `data` - Optional data bytes
|
|
54
|
+
/// * `data_is_zero` - True if empty data should be sent
|
|
55
|
+
/// * `complete` - True if this completes the stream
|
|
56
|
+
/// * `error` - Optional error message
|
|
57
|
+
pub fn new_call_data_full(
|
|
58
|
+
data: Option<Bytes>,
|
|
59
|
+
complete: bool,
|
|
60
|
+
error: Option<String>,
|
|
61
|
+
) -> Packet {
|
|
62
|
+
let (data_bytes, data_is_zero) = encode_optional_data(data);
|
|
63
|
+
|
|
64
|
+
Packet {
|
|
65
|
+
body: Some(Body::CallData(CallData {
|
|
66
|
+
data: data_bytes,
|
|
67
|
+
data_is_zero,
|
|
68
|
+
complete: complete || error.is_some(),
|
|
69
|
+
error: error.unwrap_or_default(),
|
|
70
|
+
})),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Creates a new CallData packet indicating completion.
|
|
75
|
+
pub fn new_call_complete() -> Packet {
|
|
76
|
+
Packet {
|
|
77
|
+
body: Some(Body::CallData(CallData {
|
|
78
|
+
data: vec![],
|
|
79
|
+
data_is_zero: false,
|
|
80
|
+
complete: true,
|
|
81
|
+
error: String::new(),
|
|
82
|
+
})),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Creates a new CallData packet with an error.
|
|
87
|
+
///
|
|
88
|
+
/// # Arguments
|
|
89
|
+
/// * `error` - The error message
|
|
90
|
+
pub fn new_call_error(error: impl Into<String>) -> Packet {
|
|
91
|
+
Packet {
|
|
92
|
+
body: Some(Body::CallData(CallData {
|
|
93
|
+
data: vec![],
|
|
94
|
+
data_is_zero: false,
|
|
95
|
+
complete: true,
|
|
96
|
+
error: error.into(),
|
|
97
|
+
})),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Creates a new CallCancel packet.
|
|
102
|
+
pub fn new_call_cancel() -> Packet {
|
|
103
|
+
Packet {
|
|
104
|
+
body: Some(Body::CallCancel(true)),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Extracts the body type name for debugging.
|
|
109
|
+
pub fn body_type_name(packet: &Packet) -> &'static str {
|
|
110
|
+
match &packet.body {
|
|
111
|
+
Some(Body::CallStart(_)) => "CallStart",
|
|
112
|
+
Some(Body::CallData(_)) => "CallData",
|
|
113
|
+
Some(Body::CallCancel(_)) => "CallCancel",
|
|
114
|
+
None => "Empty",
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Packet validation trait.
|
|
119
|
+
///
|
|
120
|
+
/// Provides validation methods matching the Go implementation's
|
|
121
|
+
/// `Packet.Validate()`, `CallStart.Validate()`, and `CallData.Validate()`.
|
|
122
|
+
pub trait Validate {
|
|
123
|
+
/// Validates the packet/message, returning an error if invalid.
|
|
124
|
+
fn validate(&self) -> Result<()>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
impl Validate for Packet {
|
|
128
|
+
fn validate(&self) -> Result<()> {
|
|
129
|
+
match &self.body {
|
|
130
|
+
Some(Body::CallStart(cs)) => cs.validate(),
|
|
131
|
+
Some(Body::CallData(cd)) => cd.validate(),
|
|
132
|
+
Some(Body::CallCancel(_)) => Ok(()),
|
|
133
|
+
None => Err(Error::EmptyPacket),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
impl Validate for CallStart {
|
|
139
|
+
fn validate(&self) -> Result<()> {
|
|
140
|
+
if self.rpc_method.is_empty() {
|
|
141
|
+
return Err(Error::EmptyMethodId);
|
|
142
|
+
}
|
|
143
|
+
if self.rpc_service.is_empty() {
|
|
144
|
+
return Err(Error::EmptyServiceId);
|
|
145
|
+
}
|
|
146
|
+
Ok(())
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl Validate for CallData {
|
|
151
|
+
fn validate(&self) -> Result<()> {
|
|
152
|
+
// A CallData packet must have at least one of:
|
|
153
|
+
// - Non-empty data
|
|
154
|
+
// - data_is_zero flag set (indicating intentionally empty data)
|
|
155
|
+
// - complete flag set
|
|
156
|
+
// - error message
|
|
157
|
+
if self.data.is_empty()
|
|
158
|
+
&& !self.data_is_zero
|
|
159
|
+
&& !self.complete
|
|
160
|
+
&& self.error.is_empty()
|
|
161
|
+
{
|
|
162
|
+
return Err(Error::EmptyPacket);
|
|
163
|
+
}
|
|
164
|
+
Ok(())
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Extension trait for Packet to check body type.
|
|
169
|
+
impl Packet {
|
|
170
|
+
/// Returns true if this is a CallStart packet.
|
|
171
|
+
pub fn is_call_start(&self) -> bool {
|
|
172
|
+
matches!(&self.body, Some(Body::CallStart(_)))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Returns true if this is a CallData packet.
|
|
176
|
+
pub fn is_call_data(&self) -> bool {
|
|
177
|
+
matches!(&self.body, Some(Body::CallData(_)))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Returns true if this is a CallCancel packet.
|
|
181
|
+
pub fn is_call_cancel(&self) -> bool {
|
|
182
|
+
matches!(&self.body, Some(Body::CallCancel(true)))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Extracts the CallStart body, if present.
|
|
186
|
+
pub fn into_call_start(self) -> Option<CallStart> {
|
|
187
|
+
match self.body {
|
|
188
|
+
Some(Body::CallStart(cs)) => Some(cs),
|
|
189
|
+
_ => None,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Extracts the CallData body, if present.
|
|
194
|
+
pub fn into_call_data(self) -> Option<CallData> {
|
|
195
|
+
match self.body {
|
|
196
|
+
Some(Body::CallData(cd)) => Some(cd),
|
|
197
|
+
_ => None,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// Returns true if this packet indicates completion (CallData with complete=true or error).
|
|
202
|
+
pub fn is_complete(&self) -> bool {
|
|
203
|
+
match &self.body {
|
|
204
|
+
Some(Body::CallData(cd)) => cd.complete || !cd.error.is_empty(),
|
|
205
|
+
Some(Body::CallCancel(true)) => true,
|
|
206
|
+
_ => false,
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[cfg(test)]
|
|
212
|
+
mod tests {
|
|
213
|
+
use super::*;
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn test_new_call_start() {
|
|
217
|
+
let pkt = new_call_start("test.Service", "Method", None);
|
|
218
|
+
let cs = pkt.into_call_start().unwrap();
|
|
219
|
+
assert_eq!(cs.rpc_service, "test.Service");
|
|
220
|
+
assert_eq!(cs.rpc_method, "Method");
|
|
221
|
+
assert!(cs.data.is_empty());
|
|
222
|
+
assert!(!cs.data_is_zero);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#[test]
|
|
226
|
+
fn test_new_call_start_with_empty_data() {
|
|
227
|
+
let pkt = new_call_start("svc", "method", Some(Bytes::new()));
|
|
228
|
+
let cs = pkt.into_call_start().unwrap();
|
|
229
|
+
assert!(cs.data.is_empty());
|
|
230
|
+
assert!(cs.data_is_zero);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
fn test_new_call_start_with_data() {
|
|
235
|
+
let pkt = new_call_start("svc", "method", Some(Bytes::from(vec![1, 2, 3])));
|
|
236
|
+
let cs = pkt.into_call_start().unwrap();
|
|
237
|
+
assert_eq!(cs.data, vec![1, 2, 3]);
|
|
238
|
+
assert!(!cs.data_is_zero);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[test]
|
|
242
|
+
fn test_validate_call_start_valid() {
|
|
243
|
+
let cs = CallStart {
|
|
244
|
+
rpc_service: "svc".into(),
|
|
245
|
+
rpc_method: "method".into(),
|
|
246
|
+
data: vec![],
|
|
247
|
+
data_is_zero: false,
|
|
248
|
+
};
|
|
249
|
+
assert!(cs.validate().is_ok());
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn test_validate_call_start_empty_method() {
|
|
254
|
+
let cs = CallStart {
|
|
255
|
+
rpc_service: "svc".into(),
|
|
256
|
+
rpc_method: String::new(),
|
|
257
|
+
data: vec![],
|
|
258
|
+
data_is_zero: false,
|
|
259
|
+
};
|
|
260
|
+
assert!(matches!(cs.validate(), Err(Error::EmptyMethodId)));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#[test]
|
|
264
|
+
fn test_validate_call_start_empty_service() {
|
|
265
|
+
let cs = CallStart {
|
|
266
|
+
rpc_service: String::new(),
|
|
267
|
+
rpc_method: "method".into(),
|
|
268
|
+
data: vec![],
|
|
269
|
+
data_is_zero: false,
|
|
270
|
+
};
|
|
271
|
+
assert!(matches!(cs.validate(), Err(Error::EmptyServiceId)));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[test]
|
|
275
|
+
fn test_validate_call_data_valid_with_data() {
|
|
276
|
+
let cd = CallData {
|
|
277
|
+
data: vec![1, 2, 3],
|
|
278
|
+
data_is_zero: false,
|
|
279
|
+
complete: false,
|
|
280
|
+
error: String::new(),
|
|
281
|
+
};
|
|
282
|
+
assert!(cd.validate().is_ok());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#[test]
|
|
286
|
+
fn test_validate_call_data_valid_with_complete() {
|
|
287
|
+
let cd = CallData {
|
|
288
|
+
data: vec![],
|
|
289
|
+
data_is_zero: false,
|
|
290
|
+
complete: true,
|
|
291
|
+
error: String::new(),
|
|
292
|
+
};
|
|
293
|
+
assert!(cd.validate().is_ok());
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn test_validate_call_data_valid_with_error() {
|
|
298
|
+
let cd = CallData {
|
|
299
|
+
data: vec![],
|
|
300
|
+
data_is_zero: false,
|
|
301
|
+
complete: false,
|
|
302
|
+
error: "some error".into(),
|
|
303
|
+
};
|
|
304
|
+
assert!(cd.validate().is_ok());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn test_validate_call_data_valid_with_zero_data() {
|
|
309
|
+
let cd = CallData {
|
|
310
|
+
data: vec![],
|
|
311
|
+
data_is_zero: true,
|
|
312
|
+
complete: false,
|
|
313
|
+
error: String::new(),
|
|
314
|
+
};
|
|
315
|
+
assert!(cd.validate().is_ok());
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
fn test_validate_call_data_invalid_empty() {
|
|
320
|
+
let cd = CallData {
|
|
321
|
+
data: vec![],
|
|
322
|
+
data_is_zero: false,
|
|
323
|
+
complete: false,
|
|
324
|
+
error: String::new(),
|
|
325
|
+
};
|
|
326
|
+
assert!(matches!(cd.validate(), Err(Error::EmptyPacket)));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
#[test]
|
|
330
|
+
fn test_validate_packet_empty() {
|
|
331
|
+
let pkt = Packet { body: None };
|
|
332
|
+
assert!(matches!(pkt.validate(), Err(Error::EmptyPacket)));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//! Generated protocol buffer types for starpc.
|
|
2
|
+
//!
|
|
3
|
+
//! This module contains the Packet, CallStart, CallData types generated from
|
|
4
|
+
//! rpcproto.proto. These types define the wire protocol for starpc.
|
|
5
|
+
|
|
6
|
+
// Include the generated protobuf types.
|
|
7
|
+
include!(concat!(env!("OUT_DIR"), "/srpc.rs"));
|
|
8
|
+
|
|
9
|
+
// Re-export commonly used items.
|
|
10
|
+
pub use self::packet::Body;
|