Point the Rust client at the StreamAlarms alarm feed
Replace GatewayClient::query_active_alarms with stream_alarms, an AlarmFeedStream over AlarmFeedMessage served by the gateway's central alarm monitor (snapshot, snapshot_complete, then live transitions). Drops session_id from the acknowledge surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-18
@@ -16,9 +16,9 @@ use crate::auth::AuthInterceptor;
|
|||||||
use crate::error::{ensure_command_success, ensure_protocol_success, Error};
|
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::mx_access_gateway_client::MxAccessGatewayClient;
|
||||||
use crate::generated::mxaccess_gateway::v1::{
|
use crate::generated::mxaccess_gateway::v1::{
|
||||||
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, CloseSessionReply,
|
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, AlarmFeedMessage, CloseSessionReply,
|
||||||
CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply,
|
CloseSessionRequest, MxCommandReply, MxCommandRequest, MxEvent, OpenSessionReply,
|
||||||
OpenSessionRequest, QueryActiveAlarmsRequest, StreamEventsRequest,
|
OpenSessionRequest, StreamAlarmsRequest, StreamEventsRequest,
|
||||||
};
|
};
|
||||||
use crate::options::ClientOptions;
|
use crate::options::ClientOptions;
|
||||||
use crate::session::Session;
|
use crate::session::Session;
|
||||||
@@ -33,11 +33,11 @@ pub type RawGatewayClient = MxAccessGatewayClient<InterceptedService<Channel, Au
|
|||||||
pub type EventStream =
|
pub type EventStream =
|
||||||
std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<MxEvent, Error>> + Send + 'static>>;
|
std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<MxEvent, Error>> + Send + 'static>>;
|
||||||
|
|
||||||
/// Pinned, boxed [`ActiveAlarmSnapshot`] stream returned by
|
/// Pinned, boxed [`AlarmFeedMessage`] stream returned by
|
||||||
/// [`GatewayClient::query_active_alarms`]. Errors are pre-mapped from
|
/// [`GatewayClient::stream_alarms`]. Errors are pre-mapped from
|
||||||
/// `tonic::Status` to [`Error`]; dropping the stream cancels the call.
|
/// `tonic::Status` to [`Error`]; dropping the stream cancels the call.
|
||||||
pub type ActiveAlarmStream = std::pin::Pin<
|
pub type AlarmFeedStream = std::pin::Pin<
|
||||||
Box<dyn futures_core::Stream<Item = Result<ActiveAlarmSnapshot, Error>> + Send + 'static>,
|
Box<dyn futures_core::Stream<Item = Result<AlarmFeedMessage, Error>> + Send + 'static>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Thin async wrapper around the generated gateway client.
|
/// Thin async wrapper around the generated gateway client.
|
||||||
@@ -227,26 +227,27 @@ impl GatewayClient {
|
|||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the server-streaming `QueryActiveAlarms` RPC — the gateway's
|
/// Attach to the gateway's central `StreamAlarms` feed.
|
||||||
/// ConditionRefresh equivalent.
|
|
||||||
///
|
///
|
||||||
/// The returned [`ActiveAlarmStream`] yields one [`ActiveAlarmSnapshot`]
|
/// The returned [`AlarmFeedStream`] opens with one [`AlarmFeedMessage`]
|
||||||
/// per currently-active alarm. Dropping the stream cancels the gRPC call
|
/// per currently-active alarm (the ConditionRefresh snapshot), then a
|
||||||
/// cooperatively. Optional alarm-reference prefix scoping
|
/// single `snapshot_complete`, then a `transition` for every subsequent
|
||||||
/// (`request.alarm_filter_prefix`) limits the stream to a sub-tree.
|
/// 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
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns the `tonic::Status` mapped through [`Error::from`] if the
|
/// Returns the `tonic::Status` mapped through [`Error::from`] if the
|
||||||
/// server rejects the request.
|
/// server rejects the request.
|
||||||
pub async fn query_active_alarms(
|
pub async fn stream_alarms(
|
||||||
&self,
|
&self,
|
||||||
request: QueryActiveAlarmsRequest,
|
request: StreamAlarmsRequest,
|
||||||
) -> Result<ActiveAlarmStream, Error> {
|
) -> Result<AlarmFeedStream, Error> {
|
||||||
let mut client = self.inner.clone();
|
let mut client = self.inner.clone();
|
||||||
let response = client
|
let response = client.stream_alarms(self.stream_request(request)).await?;
|
||||||
.query_active_alarms(self.stream_request(request))
|
|
||||||
.await?;
|
|
||||||
let stream = futures_util::StreamExt::map(response.into_inner(), |result| {
|
let stream = futures_util::StreamExt::map(response.into_inner(), |result| {
|
||||||
result.map_err(Error::from)
|
result.map_err(Error::from)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
use mxgateway_client::generated::mxaccess_gateway::v1::alarm_feed_message;
|
||||||
use mxgateway_client::generated::mxaccess_gateway::v1::mx_access_gateway_server::{
|
use mxgateway_client::generated::mxaccess_gateway::v1::mx_access_gateway_server::{
|
||||||
MxAccessGateway, MxAccessGatewayServer,
|
MxAccessGateway, MxAccessGatewayServer,
|
||||||
};
|
};
|
||||||
@@ -16,12 +17,12 @@ use mxgateway_client::generated::mxaccess_gateway::v1::mx_command_reply;
|
|||||||
use mxgateway_client::generated::mxaccess_gateway::v1::mx_value::Kind;
|
use mxgateway_client::generated::mxaccess_gateway::v1::mx_value::Kind;
|
||||||
use mxgateway_client::generated::mxaccess_gateway::v1::{
|
use mxgateway_client::generated::mxaccess_gateway::v1::{
|
||||||
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AddItemReply,
|
AcknowledgeAlarmReply, AcknowledgeAlarmRequest, ActiveAlarmSnapshot, AddItemReply,
|
||||||
BulkReadReply, BulkReadResult, BulkSubscribeReply, BulkWriteReply, BulkWriteResult,
|
AlarmFeedMessage, BulkReadReply, BulkReadResult, BulkSubscribeReply, BulkWriteReply,
|
||||||
CloseSessionReply, CloseSessionRequest, MxCommandKind, MxCommandReply, MxDataType, MxEvent,
|
BulkWriteResult, CloseSessionReply, CloseSessionRequest, MxCommandKind, MxCommandReply,
|
||||||
MxEventFamily, MxStatusCategory, MxStatusProxy, MxStatusSource, MxValue, OpenSessionReply,
|
MxDataType, MxEvent, MxEventFamily, MxStatusCategory, MxStatusProxy, MxStatusSource, MxValue,
|
||||||
OpenSessionRequest, ProtocolStatus, ProtocolStatusCode, QueryActiveAlarmsRequest, SessionState,
|
OpenSessionReply, OpenSessionRequest, ProtocolStatus, ProtocolStatusCode, SessionState,
|
||||||
StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry, WriteSecured2BulkEntry,
|
StreamAlarmsRequest, StreamEventsRequest, SubscribeResult, Write2BulkEntry, WriteBulkEntry,
|
||||||
WriteSecuredBulkEntry,
|
WriteSecured2BulkEntry, WriteSecuredBulkEntry,
|
||||||
};
|
};
|
||||||
use mxgateway_client::{
|
use mxgateway_client::{
|
||||||
ApiKey, ClientOptions, CommandError, Error, GatewayClient, MxStatus, MxValue as ClientMxValue,
|
ApiKey, ClientOptions, CommandError, Error, GatewayClient, MxStatus, MxValue as ClientMxValue,
|
||||||
@@ -208,7 +209,6 @@ async fn acknowledge_alarm_returns_reply_with_native_status() {
|
|||||||
|
|
||||||
let reply = client
|
let reply = client
|
||||||
.acknowledge_alarm(AcknowledgeAlarmRequest {
|
.acknowledge_alarm(AcknowledgeAlarmRequest {
|
||||||
session_id: "session-fixture".to_owned(),
|
|
||||||
client_correlation_id: "corr-1".to_owned(),
|
client_correlation_id: "corr-1".to_owned(),
|
||||||
alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
|
alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
|
||||||
comment: "investigating".to_owned(),
|
comment: "investigating".to_owned(),
|
||||||
@@ -225,7 +225,7 @@ async fn acknowledge_alarm_returns_reply_with_native_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn query_active_alarms_streams_snapshot_rows() {
|
async fn stream_alarms_streams_snapshot_then_complete() {
|
||||||
let state = Arc::new(FakeState::default());
|
let state = Arc::new(FakeState::default());
|
||||||
let endpoint = spawn_fake_gateway(state.clone()).await;
|
let endpoint = spawn_fake_gateway(state.clone()).await;
|
||||||
let client = GatewayClient::connect(ClientOptions::new(endpoint))
|
let client = GatewayClient::connect(ClientOptions::new(endpoint))
|
||||||
@@ -233,15 +233,23 @@ async fn query_active_alarms_streams_snapshot_rows() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut stream = client
|
let mut stream = client
|
||||||
.query_active_alarms(QueryActiveAlarmsRequest {
|
.stream_alarms(StreamAlarmsRequest::default())
|
||||||
session_id: "session-fixture".to_owned(),
|
|
||||||
..QueryActiveAlarmsRequest::default()
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let first = stream.next().await.unwrap().unwrap();
|
let first = stream.next().await.unwrap().unwrap();
|
||||||
assert_eq!(first.alarm_full_reference, "Tank01.Level.HiHi");
|
match first.payload {
|
||||||
|
Some(alarm_feed_message::Payload::ActiveAlarm(snapshot)) => {
|
||||||
|
assert_eq!(snapshot.alarm_full_reference, "Tank01.Level.HiHi");
|
||||||
|
}
|
||||||
|
other => panic!("expected an active-alarm snapshot, got {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let second = stream.next().await.unwrap().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
second.payload,
|
||||||
|
Some(alarm_feed_message::Payload::SnapshotComplete(true)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -907,7 +915,6 @@ impl MxAccessGateway for FakeGateway {
|
|||||||
_request: Request<AcknowledgeAlarmRequest>,
|
_request: Request<AcknowledgeAlarmRequest>,
|
||||||
) -> Result<Response<AcknowledgeAlarmReply>, Status> {
|
) -> Result<Response<AcknowledgeAlarmReply>, Status> {
|
||||||
Ok(Response::new(AcknowledgeAlarmReply {
|
Ok(Response::new(AcknowledgeAlarmReply {
|
||||||
session_id: "session-fixture".to_owned(),
|
|
||||||
correlation_id: "corr-1".to_owned(),
|
correlation_id: "corr-1".to_owned(),
|
||||||
protocol_status: Some(ok_status("ack ok")),
|
protocol_status: Some(ok_status("ack ok")),
|
||||||
status: Some(MxStatusProxy {
|
status: Some(MxStatusProxy {
|
||||||
@@ -920,18 +927,28 @@ impl MxAccessGateway for FakeGateway {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryActiveAlarmsStream =
|
type StreamAlarmsStream =
|
||||||
Pin<Box<dyn Stream<Item = Result<ActiveAlarmSnapshot, Status>> + Send + 'static>>;
|
Pin<Box<dyn Stream<Item = Result<AlarmFeedMessage, Status>> + Send + 'static>>;
|
||||||
|
|
||||||
async fn query_active_alarms(
|
async fn stream_alarms(
|
||||||
&self,
|
&self,
|
||||||
_request: Request<QueryActiveAlarmsRequest>,
|
_request: Request<StreamAlarmsRequest>,
|
||||||
) -> Result<Response<Self::QueryActiveAlarmsStream>, Status> {
|
) -> Result<Response<Self::StreamAlarmsStream>, Status> {
|
||||||
let (sender, receiver) = mpsc::channel(4);
|
let (sender, receiver) = mpsc::channel(4);
|
||||||
sender
|
sender
|
||||||
.send(Ok(ActiveAlarmSnapshot {
|
.send(Ok(AlarmFeedMessage {
|
||||||
alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
|
payload: Some(alarm_feed_message::Payload::ActiveAlarm(
|
||||||
..ActiveAlarmSnapshot::default()
|
ActiveAlarmSnapshot {
|
||||||
|
alarm_full_reference: "Tank01.Level.HiHi".to_owned(),
|
||||||
|
..ActiveAlarmSnapshot::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
sender
|
||||||
|
.send(Ok(AlarmFeedMessage {
|
||||||
|
payload: Some(alarm_feed_message::Payload::SnapshotComplete(true)),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user