bhtune_driver/
readonly.rs1use async_trait::async_trait;
4
5use crate::{
6 Driver, DriverCapabilities, DriverError, DriverResult, TagId, TagValue, TagWrite, WriteOutcome,
7};
8
9pub struct ReadOnlyDriver {
15 inner: Box<dyn Driver>,
16}
17
18impl ReadOnlyDriver {
19 pub fn new(inner: Box<dyn Driver>) -> Self {
21 Self { inner }
22 }
23}
24
25#[async_trait]
26impl Driver for ReadOnlyDriver {
27 async fn read(&self, tags: &[TagId]) -> DriverResult<Vec<TagValue>> {
28 self.inner.read(tags).await
29 }
30
31 async fn write(&self, _tag: &TagId, _value: TagWrite) -> DriverResult<WriteOutcome> {
32 Err(DriverError::Unsupported { operation: "write" })
33 }
34
35 async fn capabilities(&self) -> DriverResult<DriverCapabilities> {
36 self.inner.capabilities().await
37 }
38
39 async fn browse(&self, _request: crate::BrowsePageRequest) -> DriverResult<crate::BrowsePage> {
40 Err(DriverError::Unsupported {
41 operation: "browse",
42 })
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use std::sync::{Arc, Mutex};
49
50 use super::*;
51 use crate::{
52 BrowseNodeKind, BrowsePage, BrowsePageRequest, BrowseSource, DriverCapabilities,
53 NamespaceOrganization, Quality,
54 };
55
56 struct RecordingDriver {
57 writes: Arc<Mutex<Vec<(TagId, TagWrite)>>>,
58 }
59
60 #[async_trait]
61 impl Driver for RecordingDriver {
62 async fn read(&self, tags: &[TagId]) -> DriverResult<Vec<TagValue>> {
63 Ok(tags
64 .iter()
65 .map(|tag| TagValue {
66 tag: tag.clone(),
67 value: "1".to_string(),
68 quality: Quality::Good,
69 timestamp: None,
70 })
71 .collect())
72 }
73
74 async fn write(&self, tag: &TagId, value: TagWrite) -> DriverResult<WriteOutcome> {
75 self.writes.lock().unwrap().push((tag.clone(), value));
76 Ok(WriteOutcome::success())
77 }
78
79 async fn capabilities(&self) -> DriverResult<DriverCapabilities> {
80 Ok(DriverCapabilities {
81 application_version: "test".to_string(),
82 protocol_version: "1".to_string(),
83 max_page_size: 1,
84 supports_browse_sessions: false,
85 supports_search: false,
86 organization: NamespaceOrganization::Unspecified,
87 source: BrowseSource::Unspecified,
88 supports_indexed_search: false,
89 indexed_search_protocol_version: String::new(),
90 max_indexed_search_results: 0,
91 search_index_state: crate::SearchIndexState::Unspecified,
92 })
93 }
94
95 async fn browse(&self, _request: BrowsePageRequest) -> DriverResult<BrowsePage> {
96 Ok(BrowsePage {
97 session_id: "test".to_string(),
98 nodes: vec![crate::BrowseNode {
99 node_key: "node".to_string(),
100 display_name: "Node".to_string(),
101 kind: BrowseNodeKind::Item,
102 item_id: Some("tag".to_string()),
103 }],
104 next_page_token: None,
105 complete: true,
106 organization: NamespaceOrganization::Unspecified,
107 source: BrowseSource::Unspecified,
108 warning: None,
109 })
110 }
111 }
112
113 #[tokio::test]
114 async fn attempted_write_does_not_reach_the_underlying_driver() {
115 let writes = Arc::new(Mutex::new(Vec::new()));
116 let driver = ReadOnlyDriver::new(Box::new(RecordingDriver {
117 writes: Arc::clone(&writes),
118 }));
119
120 assert!(matches!(
121 driver
122 .write(&"Unit1.LIC101.MV".to_string(), TagWrite::Float(50.0))
123 .await,
124 Err(DriverError::Unsupported { operation: "write" })
125 ));
126 assert!(writes.lock().unwrap().is_empty());
127 }
128
129 #[tokio::test]
130 async fn recording_driver_write_records_an_underlying_write() {
131 let writes = Arc::new(Mutex::new(Vec::new()));
132 let driver = RecordingDriver {
133 writes: Arc::clone(&writes),
134 };
135
136 driver
137 .write(&"Unit1.LIC101.MV".to_string(), TagWrite::Float(50.0))
138 .await
139 .unwrap();
140 assert_eq!(writes.lock().unwrap().len(), 1);
141 }
142
143 #[tokio::test]
144 async fn recording_driver_browse_returns_its_fixture_page() {
145 let driver = RecordingDriver {
146 writes: Arc::new(Mutex::new(Vec::new())),
147 };
148
149 let page = driver.browse(BrowsePageRequest::root(1)).await.unwrap();
150 assert_eq!(page.session_id, "test");
151 assert_eq!(page.nodes[0].item_id.as_deref(), Some("tag"));
152 }
153
154 #[tokio::test]
155 async fn read_only_driver_forwards_reads_and_capabilities_but_not_browse() {
156 let driver = ReadOnlyDriver::new(Box::new(RecordingDriver {
157 writes: Arc::new(Mutex::new(Vec::new())),
158 }));
159
160 let values = driver.read(&["Unit1.LIC101.PV".to_string()]).await.unwrap();
161 assert_eq!(values[0].value, "1");
162 assert_eq!(
163 driver.capabilities().await.unwrap().application_version,
164 "test"
165 );
166 assert!(matches!(
167 driver.browse(BrowsePageRequest::root(1)).await,
168 Err(DriverError::Unsupported {
169 operation: "browse"
170 })
171 ));
172 }
173}