1use std::collections::HashSet;
2
3use crate::error::ClientError;
4use crate::transport::Transport;
5use crate::transport::http::HttpTransport;
6use crate::transport::ws::WsTransport;
7use haystack_core::data::{HCol, HDict, HGrid};
8use haystack_core::kinds::{HRef, Kind, Number};
9
10pub struct HaystackClient<T: Transport> {
17 transport: T,
18}
19
20impl HaystackClient<HttpTransport> {
21 pub async fn connect(url: &str, username: &str, password: &str) -> Result<Self, ClientError> {
28 let client = reqwest::Client::new();
29 let auth_token = crate::auth::authenticate(&client, url, username, password).await?;
30 let transport = HttpTransport::new(url, auth_token);
31 Ok(Self { transport })
32 }
33
34 pub async fn connect_with_tls(
47 url: &str,
48 username: &str,
49 password: &str,
50 tls: &crate::tls::TlsConfig,
51 ) -> Result<Self, ClientError> {
52 let mut combined_pem = tls.client_cert_pem.clone();
54 combined_pem.extend_from_slice(&tls.client_key_pem);
55
56 let identity = reqwest::Identity::from_pem(&combined_pem)
57 .map_err(|e| ClientError::Connection(format!("invalid client certificate: {e}")))?;
58
59 let mut builder = reqwest::Client::builder().identity(identity);
60
61 if let Some(ref ca) = tls.ca_cert_pem {
62 let cert = reqwest::Certificate::from_pem(ca)
63 .map_err(|e| ClientError::Connection(format!("invalid CA certificate: {e}")))?;
64 builder = builder.add_root_certificate(cert);
65 }
66
67 let client = builder
68 .build()
69 .map_err(|e| ClientError::Connection(format!("TLS client build failed: {e}")))?;
70
71 let auth_token = crate::auth::authenticate(&client, url, username, password).await?;
72 let transport = HttpTransport::new(url, auth_token);
73 Ok(Self { transport })
74 }
75}
76
77impl HaystackClient<WsTransport> {
78 pub async fn connect_ws(
89 url: &str,
90 ws_url: &str,
91 username: &str,
92 password: &str,
93 ) -> Result<Self, ClientError> {
94 let client = reqwest::Client::new();
96 let auth_token = crate::auth::authenticate(&client, url, username, password).await?;
97
98 let transport = WsTransport::connect(ws_url, &auth_token).await?;
100 Ok(Self { transport })
101 }
102}
103
104impl<T: Transport> HaystackClient<T> {
105 pub fn from_transport(transport: T) -> Self {
107 Self { transport }
108 }
109
110 pub async fn call(&self, op: &str, req: &HGrid) -> Result<HGrid, ClientError> {
112 self.transport.call(op, req).await
113 }
114
115 pub async fn about(&self) -> Result<HGrid, ClientError> {
121 self.call("about", &HGrid::new()).await
122 }
123
124 pub async fn ops(&self) -> Result<HGrid, ClientError> {
126 self.call("ops", &HGrid::new()).await
127 }
128
129 pub async fn formats(&self) -> Result<HGrid, ClientError> {
131 self.call("formats", &HGrid::new()).await
132 }
133
134 pub async fn libs(&self) -> Result<HGrid, ClientError> {
136 self.call("libs", &HGrid::new()).await
137 }
138
139 pub async fn read(&self, filter: &str, limit: Option<usize>) -> Result<HGrid, ClientError> {
141 let mut row = HDict::new();
142 row.set("filter", Kind::Str(filter.to_string()));
143 if let Some(lim) = limit {
144 row.set("limit", Kind::Number(Number::unitless(lim as f64)));
145 }
146 let cols = if limit.is_some() {
147 vec![HCol::new("filter"), HCol::new("limit")]
148 } else {
149 vec![HCol::new("filter")]
150 };
151 let grid = HGrid::from_parts(HDict::new(), cols, vec![row]);
152 self.call("read", &grid).await
153 }
154
155 pub async fn read_by_ids(&self, ids: &[&str]) -> Result<HGrid, ClientError> {
157 let rows: Vec<HDict> = ids
158 .iter()
159 .map(|id| {
160 let mut d = HDict::new();
161 d.set("id", Kind::Ref(HRef::from_val(*id)));
162 d
163 })
164 .collect();
165 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("id")], rows);
166 self.call("read", &grid).await
167 }
168
169 pub async fn nav(&self, nav_id: Option<&str>) -> Result<HGrid, ClientError> {
171 let mut row = HDict::new();
172 if let Some(id) = nav_id {
173 row.set("navId", Kind::Str(id.to_string()));
174 }
175 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("navId")], vec![row]);
176 self.call("nav", &grid).await
177 }
178
179 pub async fn defs(&self, filter: Option<&str>) -> Result<HGrid, ClientError> {
181 let mut row = HDict::new();
182 if let Some(f) = filter {
183 row.set("filter", Kind::Str(f.to_string()));
184 }
185 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("filter")], vec![row]);
186 self.call("defs", &grid).await
187 }
188
189 pub async fn watch_sub(&self, ids: &[&str], lease: Option<&str>) -> Result<HGrid, ClientError> {
193 let rows: Vec<HDict> = ids
194 .iter()
195 .map(|id| {
196 let mut d = HDict::new();
197 d.set("id", Kind::Ref(HRef::from_val(*id)));
198 d
199 })
200 .collect();
201 let mut meta = HDict::new();
202 if let Some(l) = lease {
203 meta.set("lease", Kind::Str(l.to_string()));
204 }
205 let grid = HGrid::from_parts(meta, vec![HCol::new("id")], rows);
206 self.call("watchSub", &grid).await
207 }
208
209 pub async fn watch_poll(&self, watch_id: &str) -> Result<HGrid, ClientError> {
211 let mut meta = HDict::new();
212 meta.set("watchId", Kind::Str(watch_id.to_string()));
213 let grid = HGrid::from_parts(meta, vec![], vec![]);
214 self.call("watchPoll", &grid).await
215 }
216
217 pub async fn watch_unsub(&self, watch_id: &str, ids: &[&str]) -> Result<HGrid, ClientError> {
219 let rows: Vec<HDict> = ids
220 .iter()
221 .map(|id| {
222 let mut d = HDict::new();
223 d.set("id", Kind::Ref(HRef::from_val(*id)));
224 d
225 })
226 .collect();
227 let mut meta = HDict::new();
228 meta.set("watchId", Kind::Str(watch_id.to_string()));
229 let grid = HGrid::from_parts(meta, vec![HCol::new("id")], rows);
230 self.call("watchUnsub", &grid).await
231 }
232
233 pub async fn point_write(&self, id: &str, level: u8, val: Kind) -> Result<HGrid, ClientError> {
235 let mut row = HDict::new();
236 row.set("id", Kind::Ref(HRef::from_val(id)));
237 row.set("level", Kind::Number(Number::unitless(level as f64)));
238 row.set("val", val);
239 let grid = HGrid::from_parts(
240 HDict::new(),
241 vec![HCol::new("id"), HCol::new("level"), HCol::new("val")],
242 vec![row],
243 );
244 self.call("pointWrite", &grid).await
245 }
246
247 pub async fn his_read(&self, id: &str, range: &str) -> Result<HGrid, ClientError> {
252 let mut row = HDict::new();
253 row.set("id", Kind::Ref(HRef::from_val(id)));
254 row.set("range", Kind::Str(range.to_string()));
255 let grid = HGrid::from_parts(
256 HDict::new(),
257 vec![HCol::new("id"), HCol::new("range")],
258 vec![row],
259 );
260 self.call("hisRead", &grid).await
261 }
262
263 pub async fn his_write(&self, id: &str, items: Vec<HDict>) -> Result<HGrid, ClientError> {
267 let mut meta = HDict::new();
268 meta.set("id", Kind::Ref(HRef::from_val(id)));
269 let grid = HGrid::from_parts(meta, vec![HCol::new("ts"), HCol::new("val")], items);
270 self.call("hisWrite", &grid).await
271 }
272
273 pub async fn invoke_action(
275 &self,
276 id: &str,
277 action: &str,
278 args: HDict,
279 ) -> Result<HGrid, ClientError> {
280 let mut row = args;
281 row.set("id", Kind::Ref(HRef::from_val(id)));
282 row.set("action", Kind::Str(action.to_string()));
283 let grid = HGrid::from_parts(
284 HDict::new(),
285 vec![HCol::new("id"), HCol::new("action")],
286 vec![row],
287 );
288 self.call("invokeAction", &grid).await
289 }
290
291 pub async fn close_session(&self) -> Result<HGrid, ClientError> {
296 self.call("close", &HGrid::new()).await
297 }
298
299 pub async fn specs(&self, lib: Option<&str>) -> Result<HGrid, ClientError> {
305 let mut row = HDict::new();
306 if let Some(lib_name) = lib {
307 row.set("lib", Kind::Str(lib_name.to_string()));
308 }
309 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("lib")], vec![row]);
310 self.call("specs", &grid).await
311 }
312
313 pub async fn spec(&self, qname: &str) -> Result<HGrid, ClientError> {
315 let mut row = HDict::new();
316 row.set("qname", Kind::Str(qname.to_string()));
317 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("qname")], vec![row]);
318 self.call("spec", &grid).await
319 }
320
321 pub async fn load_lib(&self, name: &str, source: &str) -> Result<HGrid, ClientError> {
323 let mut row = HDict::new();
324 row.set("name", Kind::Str(name.to_string()));
325 row.set("source", Kind::Str(source.to_string()));
326 let grid = HGrid::from_parts(
327 HDict::new(),
328 vec![HCol::new("name"), HCol::new("source")],
329 vec![row],
330 );
331 self.call("loadLib", &grid).await
332 }
333
334 pub async fn unload_lib(&self, name: &str) -> Result<HGrid, ClientError> {
336 let mut row = HDict::new();
337 row.set("name", Kind::Str(name.to_string()));
338 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("name")], vec![row]);
339 self.call("unloadLib", &grid).await
340 }
341
342 pub async fn export_lib(&self, name: &str) -> Result<HGrid, ClientError> {
344 let mut row = HDict::new();
345 row.set("name", Kind::Str(name.to_string()));
346 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("name")], vec![row]);
347 self.call("exportLib", &grid).await
348 }
349
350 pub async fn validate(&self, entities: Vec<HDict>) -> Result<HGrid, ClientError> {
352 let mut col_names: Vec<String> = Vec::new();
354 let mut seen = HashSet::new();
355 for entity in &entities {
356 for name in entity.tag_names() {
357 if seen.insert(name.to_string()) {
358 col_names.push(name.to_string());
359 }
360 }
361 }
362 col_names.sort();
363 let cols: Vec<HCol> = col_names.iter().map(|n| HCol::new(n.as_str())).collect();
364 let grid = HGrid::from_parts(HDict::new(), cols, entities);
365 self.call("validate", &grid).await
366 }
367
368 pub async fn close(&self) -> Result<(), ClientError> {
370 self.transport.close().await
371 }
372}