From 34045c2f6dc4f7077485db2d7c74a64e25251740 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 6 May 2026 04:32:45 -0400 Subject: [PATCH] [F37] mxaccess: AsbSession::subscribe_buffered returns Unsupported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- rust/crates/mxaccess/src/asb_session.rs | 62 ++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/rust/crates/mxaccess/src/asb_session.rs b/rust/crates/mxaccess/src/asb_session.rs index ab03b59..d67dc9f 100644 --- a/rust/crates/mxaccess/src/asb_session.rs +++ b/rust/crates/mxaccess/src/asb_session.rs @@ -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 { + 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::(); } + /// 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}")),