[F37] mxaccess: AsbSession::subscribe_buffered returns Unsupported

ASB has no `SetBufferedUpdateInterval` analogue — the per-monitored-
item `MinimalMonitoredItem::sample_interval` plays the cadence-knob
role. Calling `subscribe_buffered` on an ASB session now returns
`Error::Unsupported { transport: TransportKind::Asb, operation: ... }`
synchronously, without touching the wire.

The error-construction logic is split into a free fn
`unsupported_subscribe_buffered_error()` so the gate's exact shape
is unit-testable without spinning up a live authenticator + transport
fake. New unit test asserts both the variant tag and that the
operation message names the unsupported method + hints at the
`sample_interval` analogue.

Workspace 758 → 759 tests, clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-06 04:32:45 -04:00
parent 2546710604
commit 34045c2f6d
+61 -1
View File
@@ -64,7 +64,7 @@ use tokio::task::JoinHandle;
use tokio_stream::wrappers::ReceiverStream;
use crate::transport_asb::AsbTransport;
use crate::{ConnectionError, Error};
use crate::{BufferedOptions, ConnectionError, Error, TransportKind};
/// Channel buffer for [`AsbSubscription`]'s publish-loop. 256 samples is
/// generous for the typical sample-rate budget (1-100 Hz) — bounded so
@@ -345,6 +345,36 @@ impl AsbSession {
join: Some(join),
}
}
/// `subscribe_buffered` is **not supported** on the ASB transport.
/// ASB has no buffered-cadence equivalent of NMX's
/// `SetBufferedUpdateInterval`; the per-monitored-item `SampleInterval`
/// (see [`mxaccess_asb::MinimalMonitoredItem::sample_interval`])
/// already plays the rate-limit role — set it on each
/// `MonitoredItem` passed to [`Self::add_monitored_items`] instead.
///
/// Returns [`Error::Unsupported`] synchronously, without touching
/// the wire. F37.
pub async fn subscribe_buffered(
&self,
_reference: &str,
_options: BufferedOptions,
) -> Result<AsbSubscription, Error> {
Err(unsupported_subscribe_buffered_error())
}
}
/// F37 — typed `Error::Unsupported` returned by
/// [`AsbSession::subscribe_buffered`]. Extracted into a free fn so the
/// gate's exact shape is unit-testable without constructing a full
/// `AsbSession` (which requires a live authenticator + transport).
fn unsupported_subscribe_buffered_error() -> Error {
Error::Unsupported {
operation: std::borrow::Cow::Borrowed(
"AsbSession::subscribe_buffered (use MinimalMonitoredItem::sample_interval; ASB has no SetBufferedUpdateInterval analogue)",
),
transport: TransportKind::Asb,
}
}
/// Inner publish-loop body — testable in isolation by passing any
@@ -477,6 +507,36 @@ mod tests {
assert_stream_send_unpin::<AsbSubscription>();
}
/// F37 — the `Error::Unsupported` returned by
/// `AsbSession::subscribe_buffered` carries `TransportKind::Asb`
/// and an operation message mentioning `subscribe_buffered`.
/// Targets the helper directly so the test doesn't need to spin
/// up a live authenticator / transport.
#[test]
fn subscribe_buffered_unsupported_error_shape() {
let err = unsupported_subscribe_buffered_error();
match err {
Error::Unsupported {
transport,
operation,
} => {
assert!(
matches!(transport, TransportKind::Asb),
"expected TransportKind::Asb, got {transport:?}"
);
assert!(
operation.contains("subscribe_buffered"),
"operation should name the unsupported method, got {operation:?}"
);
assert!(
operation.contains("sample_interval"),
"operation should hint at the ASB analogue, got {operation:?}"
);
}
other => panic!("expected Error::Unsupported, got {other:?}"),
}
}
fn fake_value(idx: i32) -> MonitoredItemValue {
MonitoredItemValue {
item: ItemIdentity::absolute_by_name(format!("Tag{idx}")),