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:
@@ -203,7 +203,7 @@ fn value_conversion_fixtures_keep_typed_projection_and_raw_metadata() {
|
||||
});
|
||||
assert_eq!(
|
||||
int64_value.projection(),
|
||||
&MxValueProjection::Int64(9_223_372_036_854_770_000)
|
||||
MxValueProjection::Int64(9_223_372_036_854_770_000)
|
||||
);
|
||||
|
||||
let raw_case = case_by_id(cases, "raw-fallback.variant");
|
||||
@@ -220,7 +220,7 @@ fn value_conversion_fixtures_keep_typed_projection_and_raw_metadata() {
|
||||
});
|
||||
assert_eq!(
|
||||
raw_value.projection(),
|
||||
&MxValueProjection::Raw(vec![1, 2, 3, 4, 5])
|
||||
MxValueProjection::Raw(vec![1, 2, 3, 4, 5])
|
||||
);
|
||||
assert_eq!(raw_value.raw().raw_data_type, 32767);
|
||||
assert!(raw_value.raw().raw_diagnostic.contains("No lossless"));
|
||||
@@ -272,11 +272,76 @@ fn command_error_display_keeps_raw_reply_accessible() {
|
||||
assert!(error.to_string().contains("MxaccessFailure"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn add_item_bulk_rejects_input_above_the_thousand_item_cap() {
|
||||
let state = Arc::new(FakeState::default());
|
||||
let endpoint = spawn_fake_gateway(state.clone()).await;
|
||||
let client = GatewayClient::connect(ClientOptions::new(endpoint))
|
||||
.await
|
||||
.unwrap();
|
||||
let session = client.session("session-fixture");
|
||||
|
||||
let oversized: Vec<String> = (0..1001).map(|index| format!("Tag{index}")).collect();
|
||||
let error = session.add_item_bulk(12, oversized).await.unwrap_err();
|
||||
|
||||
assert!(
|
||||
matches!(&error, Error::InvalidArgument { name, .. } if name.as_str() == "tag_addresses"),
|
||||
"expected InvalidArgument for tag_addresses, got {error:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn event_stream_surfaces_a_mid_stream_status_fault() {
|
||||
let state = Arc::new(FakeState::default());
|
||||
state.emit_stream_fault.store(true, Ordering::SeqCst);
|
||||
let endpoint = spawn_fake_gateway(state.clone()).await;
|
||||
let client = GatewayClient::connect(ClientOptions::new(endpoint))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut stream = client
|
||||
.stream_events(StreamEventsRequest {
|
||||
session_id: "session-fixture".to_owned(),
|
||||
after_worker_sequence: 0,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(stream.next().await.unwrap().unwrap().worker_sequence, 1);
|
||||
assert_eq!(stream.next().await.unwrap().unwrap().worker_sequence, 2);
|
||||
|
||||
let fault = stream.next().await.unwrap().unwrap_err();
|
||||
|
||||
assert!(
|
||||
matches!(fault, Error::Unavailable { .. }),
|
||||
"expected Error::Unavailable, got {fault:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_with_unreadable_ca_file_reports_invalid_endpoint() {
|
||||
let options = ClientOptions::new("https://127.0.0.1:65000")
|
||||
.with_plaintext(false)
|
||||
.with_ca_file("definitely-not-a-real-ca-file.pem");
|
||||
|
||||
// GatewayClient is not Debug, so unwrap_err is unavailable here.
|
||||
let error = match GatewayClient::connect(options).await {
|
||||
Ok(_) => panic!("connect should fail when the CA file cannot be read"),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert!(
|
||||
matches!(error, Error::InvalidEndpoint { .. }),
|
||||
"expected Error::InvalidEndpoint, got {error:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeState {
|
||||
authorization: Mutex<Option<String>>,
|
||||
last_command_kind: Mutex<Option<i32>>,
|
||||
stream_dropped: Arc<AtomicBool>,
|
||||
emit_stream_fault: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -376,6 +441,12 @@ impl MxAccessGateway for FakeGateway {
|
||||
let (sender, receiver) = mpsc::channel(4);
|
||||
sender.send(Ok(event(1))).await.unwrap();
|
||||
sender.send(Ok(event(2))).await.unwrap();
|
||||
if self.state.emit_stream_fault.load(Ordering::SeqCst) {
|
||||
sender
|
||||
.send(Err(Status::unavailable("worker dropped the session")))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(Response::new(DropAwareStream {
|
||||
inner: ReceiverStream::new(receiver),
|
||||
|
||||
Reference in New Issue
Block a user