[F33] mxaccess-asb: complete InvalidConnectionId tolerance propagation
rust / build / test / clippy / fmt (push) Has been cancelled
rust / build / test / clippy / fmt (push) Has been cancelled
Closes F33. Final commit in the three-step F33 closure (218f4c4→7a5f251→ this) — propagates the F31 InvalidConnectionId tolerance pattern to every remaining response decoder + adds publish-loop detection so the F26 stream terminates cleanly on server-side rejections instead of spinning silently. Decoders updated to tolerate empty / missing payloads + surface result_code/success: - decode_publish_response (the F26 stream's hot path) - decode_unregister_items_response - decode_delete_monitored_items_response - decode_write_response - decode_publish_write_complete_response Shared `extract_result_status(body_tokens)` helper in operations.rs consolidates the per-decoder find_text_in_named_element calls for resultCodeField + successField — a single source of truth for the F31-pattern wrapper extraction. Public response structs gain `result_code: Option<u32>` and `success: Option<bool>`: - PublishResponse - UnregisterItemsResponse - DeleteMonitoredItemsResponse - WriteResponse - PublishWriteCompleteResponse asb_session.rs::publish_loop: when PublishResponse.result_code is Some(non_zero), the loop now sends Err(ConnectionError::TransportFailure { detail: "publish returned result_code 0xXX (server-side rejection)" }) as the stream's terminal item, then returns. Without this, an InvalidConnectionId-poisoned subscription would generate empty PublishResponse forever. 5 new tests synthesise the InvalidConnectionId wire shape (`<Result><resultCodeField>1</><successField>false</></><ASBIData/><ASBIData/>`) for each decoder via the shared synthesise_invalid_connection_id_body helper — pin the tolerance for Publish, Unregister, Delete*, Write, and PublishWriteComplete. Updated obsolete write_response_missing_status_fails test to write_response_missing_status_returns_empty_with_no_result_code since the decoder no longer errors. Live read regression test: TestChildObject.TestInt = 99 returned end-to-end after all changes (cargo run -p mxaccess --example asb-subscribe). Workspace: mxaccess-asb 82 → 87 tests (+5). All other crates unchanged. Default-feature clippy clean. design/followups.md: F33 moved to Resolved with the full three-commit audit trail. M5 status block stable: F32 + F33 closed, only F28 (canonical XML for the remaining 8 ops) remains as P2 latent — works in practice under empty hashAlgorithm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -363,6 +363,25 @@ async fn publish_loop<F, Fut>(
|
||||
loop {
|
||||
match publish_fn().await {
|
||||
Ok(response) => {
|
||||
// F33: if the server short-circuited with a non-zero
|
||||
// resultCodeField (e.g. InvalidConnectionId), terminate
|
||||
// the stream rather than silently delivering empty
|
||||
// value batches forever. Caller can still inspect the
|
||||
// error via the final stream item.
|
||||
if let Some(code) = response.result_code {
|
||||
if code != 0 {
|
||||
let _ = tx
|
||||
.send(Err(Error::Connection(
|
||||
ConnectionError::TransportFailure {
|
||||
detail: format!(
|
||||
"publish returned result_code 0x{code:08X} (server-side rejection)"
|
||||
),
|
||||
},
|
||||
)))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for value in response.values {
|
||||
if tx.send(Ok(value)).await.is_err() {
|
||||
return; // consumer dropped the stream
|
||||
@@ -471,6 +490,8 @@ mod tests {
|
||||
PublishResponse {
|
||||
status: Vec::<ItemStatus>::new(),
|
||||
values,
|
||||
result_code: None,
|
||||
success: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user