Fix all MxGateway.Client.Rust code-review findings
Resolves Client.Rust-001 through Client.Rust-011.
Build/test/clippy gate (Client.Rust-001/002/003):
- options.rs: doc comments on with_max_grpc_message_bytes /
max_grpc_message_bytes (#![warn(missing_docs)])
- session.rs: rename BulkReplyKind variants to drop the shared `Bulk`
suffix (clippy::enum_variant_names)
- galaxy.rs: deref instead of clone on Option<Timestamp>
(clippy::clone_on_copy — an extra violation the gate also hit)
- mxgw-cli: assert version_json against GATEWAY/WORKER_PROTOCOL_VERSION
constants instead of the stale literal 2
`cargo clippy --workspace --all-targets -- -D warnings` now passes.
Correctness / error handling:
- version.rs: CLIENT_VERSION = env!("CARGO_PKG_VERSION") (Client.Rust-004)
- session.rs: register/add_item/add_item2 handle extractors and
bulk_results now return Err(Error::MalformedReply) instead of a
silent 0 / empty vec on a shapeless OK reply (Client.Rust-005/006)
- error.rs: new Error::Unavailable classifies Code::Unavailable /
ResourceExhausted as transient (Client.Rust-010)
- session.rs: per-call unique correlation ids via an atomic counter
(Client.Rust-011)
Other:
- value.rs: MxValue/MxArrayValue compute the projection on demand
instead of caching it, so a wire-only value pays no projection cost
(Client.Rust-008)
- RustClientDesign.md: correct the crate layout, drop the unused
`tracing` dependency (Client.Rust-007)
- client_behavior.rs: tests for the bulk-size cap, a mid-stream status
fault, and the unreadable-CA-file path (Client.Rust-009)
cargo fmt / test --workspace (27 tests) / clippy all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+68
-44
@@ -8,6 +8,8 @@
|
||||
//! Bulk commands enforce a 1000-item cap before contacting the worker, in
|
||||
//! line with the gateway's documented `MAX_BULK_ITEMS`.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crate::client::{EventStream, GatewayClient};
|
||||
use crate::error::{ensure_protocol_success, Error};
|
||||
use crate::generated::mxaccess_gateway::v1::mx_command::Payload;
|
||||
@@ -23,6 +25,16 @@ use crate::value::MxValue;
|
||||
|
||||
const MAX_BULK_ITEMS: usize = 1_000;
|
||||
|
||||
/// Process-wide monotonic counter that keeps client correlation ids unique.
|
||||
static CORRELATION_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Build a unique `client_correlation_id` for a request so concurrent or
|
||||
/// repeated calls of the same command kind can be told apart in gateway logs.
|
||||
fn next_correlation_id(label: &str) -> String {
|
||||
let sequence = CORRELATION_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
||||
format!("rust-client-{label}-{sequence}")
|
||||
}
|
||||
|
||||
/// Handle to an opened gateway session.
|
||||
///
|
||||
/// `Session` carries the gateway-issued session id and a cloned
|
||||
@@ -76,7 +88,7 @@ impl Session {
|
||||
.client
|
||||
.close_session_raw(CloseSessionRequest {
|
||||
session_id: self.id.clone(),
|
||||
client_correlation_id: "rust-client-close-session".to_owned(),
|
||||
client_correlation_id: next_correlation_id("close-session"),
|
||||
})
|
||||
.await?;
|
||||
ensure_protocol_success("close session", reply.protocol_status.as_ref())?;
|
||||
@@ -99,7 +111,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(register_server_handle(&reply))
|
||||
register_server_handle(&reply)
|
||||
}
|
||||
|
||||
/// Run MXAccess `AddItem` against `server_handle` and return the
|
||||
@@ -120,7 +132,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(add_item_handle(&reply))
|
||||
add_item_handle(&reply)
|
||||
}
|
||||
|
||||
/// Run MXAccess `AddItem2` (item with a caller-supplied context string)
|
||||
@@ -146,7 +158,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(add_item2_handle(&reply))
|
||||
add_item2_handle(&reply)
|
||||
}
|
||||
|
||||
/// Run MXAccess `RemoveItem` for the given handle pair.
|
||||
@@ -226,7 +238,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::AddItemBulk))
|
||||
bulk_results(reply, BulkReplyKind::AddItem)
|
||||
}
|
||||
|
||||
/// Bulk variant of [`Session::advise`].
|
||||
@@ -250,7 +262,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::AdviseItemBulk))
|
||||
bulk_results(reply, BulkReplyKind::AdviseItem)
|
||||
}
|
||||
|
||||
/// Bulk variant of [`Session::remove_item`].
|
||||
@@ -274,7 +286,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::RemoveItemBulk))
|
||||
bulk_results(reply, BulkReplyKind::RemoveItem)
|
||||
}
|
||||
|
||||
/// Bulk variant of [`Session::un_advise`].
|
||||
@@ -298,7 +310,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::UnAdviseItemBulk))
|
||||
bulk_results(reply, BulkReplyKind::UnAdviseItem)
|
||||
}
|
||||
|
||||
/// Bulk `Subscribe` (atomic add-and-advise) for a list of tag addresses.
|
||||
@@ -322,7 +334,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::SubscribeBulk))
|
||||
bulk_results(reply, BulkReplyKind::Subscribe)
|
||||
}
|
||||
|
||||
/// Bulk `Unsubscribe` (atomic un-advise-and-remove) for a list of
|
||||
@@ -347,7 +359,7 @@ impl Session {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(bulk_results(reply, BulkReplyKind::UnsubscribeBulk))
|
||||
bulk_results(reply, BulkReplyKind::Unsubscribe)
|
||||
}
|
||||
|
||||
/// Run MXAccess `Write` (single-value, no caller-supplied timestamp).
|
||||
@@ -466,7 +478,7 @@ impl Session {
|
||||
fn command_request(&self, kind: MxCommandKind, payload: Payload) -> MxCommandRequest {
|
||||
MxCommandRequest {
|
||||
session_id: self.id.clone(),
|
||||
client_correlation_id: format!("rust-client-{}", kind.as_str_name()),
|
||||
client_correlation_id: next_correlation_id(kind.as_str_name()),
|
||||
command: Some(MxCommand {
|
||||
kind: kind as i32,
|
||||
payload: Some(payload),
|
||||
@@ -486,71 +498,83 @@ fn ensure_bulk_size(name: &'static str, len: usize) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn register_server_handle(reply: &MxCommandReply) -> i32 {
|
||||
fn register_server_handle(reply: &MxCommandReply) -> Result<i32, Error> {
|
||||
match reply.payload.as_ref() {
|
||||
Some(mx_command_reply::Payload::Register(register)) => register.server_handle,
|
||||
Some(mx_command_reply::Payload::Register(register)) => Ok(register.server_handle),
|
||||
_ => reply
|
||||
.return_value
|
||||
.as_ref()
|
||||
.and_then(int32_reply_value)
|
||||
.unwrap_or_default(),
|
||||
.ok_or_else(|| Error::MalformedReply {
|
||||
detail: "Register reply carried neither a register payload nor an \
|
||||
int32 return value"
|
||||
.to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_item_handle(reply: &MxCommandReply) -> i32 {
|
||||
fn add_item_handle(reply: &MxCommandReply) -> Result<i32, Error> {
|
||||
match reply.payload.as_ref() {
|
||||
Some(mx_command_reply::Payload::AddItem(add_item)) => add_item.item_handle,
|
||||
Some(mx_command_reply::Payload::AddItem(add_item)) => Ok(add_item.item_handle),
|
||||
_ => reply
|
||||
.return_value
|
||||
.as_ref()
|
||||
.and_then(int32_reply_value)
|
||||
.unwrap_or_default(),
|
||||
.ok_or_else(|| Error::MalformedReply {
|
||||
detail: "AddItem reply carried neither an add_item payload nor an \
|
||||
int32 return value"
|
||||
.to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_item2_handle(reply: &MxCommandReply) -> i32 {
|
||||
fn add_item2_handle(reply: &MxCommandReply) -> Result<i32, Error> {
|
||||
match reply.payload.as_ref() {
|
||||
Some(mx_command_reply::Payload::AddItem2(add_item)) => add_item.item_handle,
|
||||
Some(mx_command_reply::Payload::AddItem2(add_item)) => Ok(add_item.item_handle),
|
||||
_ => reply
|
||||
.return_value
|
||||
.as_ref()
|
||||
.and_then(int32_reply_value)
|
||||
.unwrap_or_default(),
|
||||
.ok_or_else(|| Error::MalformedReply {
|
||||
detail: "AddItem2 reply carried neither an add_item2 payload nor an \
|
||||
int32 return value"
|
||||
.to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
enum BulkReplyKind {
|
||||
AddItemBulk,
|
||||
AdviseItemBulk,
|
||||
RemoveItemBulk,
|
||||
UnAdviseItemBulk,
|
||||
SubscribeBulk,
|
||||
UnsubscribeBulk,
|
||||
AddItem,
|
||||
AdviseItem,
|
||||
RemoveItem,
|
||||
UnAdviseItem,
|
||||
Subscribe,
|
||||
Unsubscribe,
|
||||
}
|
||||
|
||||
fn bulk_results(reply: MxCommandReply, kind: BulkReplyKind) -> Vec<SubscribeResult> {
|
||||
fn bulk_results(reply: MxCommandReply, kind: BulkReplyKind) -> Result<Vec<SubscribeResult>, Error> {
|
||||
match (reply.payload, kind) {
|
||||
(Some(mx_command_reply::Payload::AddItemBulk(reply)), BulkReplyKind::AddItemBulk) => {
|
||||
reply.results
|
||||
(Some(mx_command_reply::Payload::AddItemBulk(reply)), BulkReplyKind::AddItem) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
(Some(mx_command_reply::Payload::AdviseItemBulk(reply)), BulkReplyKind::AdviseItemBulk) => {
|
||||
reply.results
|
||||
(Some(mx_command_reply::Payload::AdviseItemBulk(reply)), BulkReplyKind::AdviseItem) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
(Some(mx_command_reply::Payload::RemoveItemBulk(reply)), BulkReplyKind::RemoveItemBulk) => {
|
||||
reply.results
|
||||
(Some(mx_command_reply::Payload::RemoveItemBulk(reply)), BulkReplyKind::RemoveItem) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
(
|
||||
Some(mx_command_reply::Payload::UnAdviseItemBulk(reply)),
|
||||
BulkReplyKind::UnAdviseItemBulk,
|
||||
) => reply.results,
|
||||
(Some(mx_command_reply::Payload::SubscribeBulk(reply)), BulkReplyKind::SubscribeBulk) => {
|
||||
reply.results
|
||||
(Some(mx_command_reply::Payload::UnAdviseItemBulk(reply)), BulkReplyKind::UnAdviseItem) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
(
|
||||
Some(mx_command_reply::Payload::UnsubscribeBulk(reply)),
|
||||
BulkReplyKind::UnsubscribeBulk,
|
||||
) => reply.results,
|
||||
_ => Vec::new(),
|
||||
(Some(mx_command_reply::Payload::SubscribeBulk(reply)), BulkReplyKind::Subscribe) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
(Some(mx_command_reply::Payload::UnsubscribeBulk(reply)), BulkReplyKind::Unsubscribe) => {
|
||||
Ok(reply.results)
|
||||
}
|
||||
_ => Err(Error::MalformedReply {
|
||||
detail: "bulk command reply did not carry the expected bulk result payload".to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user