fix(CLI-03): Rust typed invoke validates HRESULT/MXSTATUS_PROXY

Typed write/command wrappers now call ensure_mxaccess_success after
ensure_command_success, failing on hresult < 0 (COM-correct, matching
CLI-08/Python) or any MxStatusProxy.success == 0, via a new boxed
Error::MxAccess variant with credential-safe formatting. The raw invoke
escape hatch stays unvalidated.

archreview: CLI-03 (P0). Verified: cargo fmt/check/test (28) and clippy --all-targets -D warnings clean.
This commit is contained in:
Joseph Doherty
2026-07-09 05:51:45 -04:00
parent 1eb00276bc
commit 5faef6c012
4 changed files with 194 additions and 8 deletions
+15 -5
View File
@@ -11,7 +11,9 @@ use tonic::transport::Channel;
use tonic::Request;
use crate::auth::AuthInterceptor;
use crate::error::{ensure_command_success, ensure_protocol_success, Error};
use crate::error::{
ensure_command_success, ensure_mxaccess_success, ensure_protocol_success, Error,
};
use crate::generated::mxaccess_gateway::v1::mx_access_gateway_client::MxAccessGatewayClient;
use crate::generated::mxaccess_gateway::v1::{
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AlarmFeedMessage,
@@ -166,16 +168,24 @@ impl GatewayClient {
Ok(response.into_inner())
}
/// Issue an `Invoke` RPC and surface a non-OK reply as
/// [`Error::Command`].
/// Issue an `Invoke` RPC and surface a failing reply as a typed error.
///
/// The reply is validated twice: [`ensure_command_success`] rejects a
/// non-OK protocol envelope as [`Error::Command`], then
/// [`ensure_mxaccess_success`] rejects an MXAccess-level failure (negative
/// `hresult` or a non-success `MXSTATUS_PROXY` entry) as
/// [`Error::MxAccess`], preserving MXAccess parity. Callers that need the
/// unvalidated reply should use [`invoke_raw`](Self::invoke_raw) instead.
///
/// # Errors
///
/// Returns [`Error::Command`] when the reply's `protocol_status` is not
/// `Ok`, plus any errors propagated by
/// `Ok`, [`Error::MxAccess`] when the reply reports an MXAccess-level
/// failure, plus any errors propagated by
/// [`invoke_raw`](Self::invoke_raw).
pub async fn invoke(&self, request: MxCommandRequest) -> Result<MxCommandReply, Error> {
ensure_command_success(self.invoke_raw(request).await?)
let reply = ensure_command_success(self.invoke_raw(request).await?)?;
ensure_mxaccess_success(reply)
}
/// Open the server-streaming `StreamEvents` RPC.