Rust client: port stream-alarms and acknowledge-alarm + fix stream-events family + 8MB Windows stack

Adds the session-less alarm CLI subcommands to mxgw. stream-alarms attaches to
the gateway's central alarm feed (--filter-prefix, --max-events, --json/--jsonl;
aggregate shape `{messageCount, messages: [...]}`); acknowledge-alarm is a unary
ack (--reference required, --comment, --operator). stream_alarms joins
query_active_alarms on GatewayClient and re-exports AlarmFeedStream.

Also extends stream-events JSON to emit a full `events` array (itemHandle, value
projected to protojson-shaped `*Value` keys, etc.) instead of just `eventCount`,
matching the other four CLIs, and renders MxEvent.family as the protobuf enum
NAME (MX_EVENT_FAMILY_ON_WRITE_COMPLETE) rather than the raw i32 so the e2e
write round-trip can recognise the OnWriteComplete echo.

Adds clients/rust/.cargo/config.toml bumping the Windows main-thread stack to
8 MB via /STACK:8388608. clap-derive's Command enum (one variant per subcommand)
overflowed the default 1 MB stack in debug builds after the new variants
landed; release builds were unaffected but the e2e matrix runs Rust via
`cargo run` (debug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-24 06:45:46 -04:00
parent 6f0d142639
commit 7de4efeb02
6 changed files with 368 additions and 39 deletions
+42 -4
View File
@@ -16,9 +16,10 @@ use crate::auth::AuthInterceptor;
use crate::error::{ensure_command_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, CloseSessionReply,
CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply,
OpenSessionRequest, QueryActiveAlarmsRequest, StreamEventsRequest,
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AlarmFeedMessage,
CloseSessionReply, CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent,
OpenSessionReply, OpenSessionRequest, QueryActiveAlarmsRequest, StreamAlarmsRequest,
StreamEventsRequest,
};
use crate::options::ClientOptions;
use crate::session::Session;
@@ -40,6 +41,13 @@ pub type ActiveAlarmStream = std::pin::Pin<
Box<dyn futures_core::Stream<Item = Result<ActiveAlarmSnapshot, Error>> + Send + 'static>,
>;
/// Pinned, boxed [`AlarmFeedMessage`] stream returned by
/// [`GatewayClient::stream_alarms`]. Errors are pre-mapped from
/// `tonic::Status` to [`Error`]; dropping the stream cancels the call.
pub type AlarmFeedStream = std::pin::Pin<
Box<dyn futures_core::Stream<Item = Result<AlarmFeedMessage, Error>> + Send + 'static>,
>;
/// Thin async wrapper around the generated gateway client.
///
/// The wrapper is `Clone`: every clone shares the underlying tonic channel
@@ -219,7 +227,9 @@ impl GatewayClient {
request: AcknowledgeAlarmRequest,
) -> Result<AcknowledgeAlarmReply, Error> {
let mut client = self.inner.clone();
let response = client.acknowledge_alarm(self.unary_request(request)).await?;
let response = client
.acknowledge_alarm(self.unary_request(request))
.await?;
let reply = response.into_inner();
ensure_protocol_success("acknowledge alarm", reply.protocol_status.as_ref())?;
Ok(reply)
@@ -252,6 +262,34 @@ impl GatewayClient {
Ok(Box::pin(stream))
}
/// Attach to the gateway's central `StreamAlarms` feed.
///
/// The returned [`AlarmFeedStream`] opens with one [`AlarmFeedMessage`]
/// per currently-active alarm (the ConditionRefresh snapshot), then a
/// single `snapshot_complete`, then a `transition` for every subsequent
/// raise / acknowledge / clear. It is served by the gateway's always-on
/// alarm monitor — no worker session is opened — so any number of clients
/// may attach. Dropping the stream cancels the gRPC call cooperatively.
/// Optional alarm-reference prefix scoping (`request.alarm_filter_prefix`)
/// limits the stream to a sub-tree.
///
/// # Errors
///
/// Returns the `tonic::Status` mapped through [`Error::from`] if the
/// server rejects the request.
pub async fn stream_alarms(
&self,
request: StreamAlarmsRequest,
) -> Result<AlarmFeedStream, Error> {
let mut client = self.inner.clone();
let response = client.stream_alarms(self.stream_request(request)).await?;
let stream = futures_util::StreamExt::map(response.into_inner(), |result| {
result.map_err(Error::from)
});
Ok(Box::pin(stream))
}
fn unary_request<T>(&self, message: T) -> Request<T> {
let mut request = Request::new(message);
request.set_timeout(self.call_timeout);
+1 -1
View File
@@ -24,7 +24,7 @@ pub mod version;
#[doc(inline)]
pub use auth::{ApiKey, AuthInterceptor};
#[doc(inline)]
pub use client::{EventStream, GatewayClient};
pub use client::{AlarmFeedStream, EventStream, GatewayClient};
#[doc(inline)]
pub use error::{CommandError, Error};
#[doc(inline)]