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).map_err(|e| {
63 ClientError::Connection(format!("invalid CA certificate: {e}"))
64 })?;
65 builder = builder.add_root_certificate(cert);
66 }
67
68 let client = builder
69 .build()
70 .map_err(|e| ClientError::Connection(format!("TLS client build failed: {e}")))?;
71
72 let auth_token =
73 crate::auth::authenticate(&client, url, username, password).await?;
74 let transport = HttpTransport::new(url, auth_token);
75 Ok(Self { transport })
76 }
77}
78
79impl HaystackClient<WsTransport> {
80 pub async fn connect_ws(
91 url: &str,
92 ws_url: &str,
93 username: &str,
94 password: &str,
95 ) -> Result<Self, ClientError> {
96 let client = reqwest::Client::new();
98 let auth_token = crate::auth::authenticate(&client, url, username, password).await?;
99
100 let transport = WsTransport::connect(ws_url, &auth_token).await?;
102 Ok(Self { transport })
103 }
104}
105
106impl<T: Transport> HaystackClient<T> {
107 pub fn from_transport(transport: T) -> Self {
109 Self { transport }
110 }
111
112 pub async fn call(&self, op: &str, req: &HGrid) -> Result<HGrid, ClientError> {
114 self.transport.call(op, req).await
115 }
116
117 pub async fn about(&self) -> Result<HGrid, ClientError> {
123 self.call("about", &HGrid::new()).await
124 }
125
126 pub async fn ops(&self) -> Result<HGrid, ClientError> {
128 self.call("ops", &HGrid::new()).await
129 }
130
131 pub async fn formats(&self) -> Result<HGrid, ClientError> {
133 self.call("formats", &HGrid::new()).await
134 }
135
136 pub async fn libs(&self) -> Result<HGrid, ClientError> {
138 self.call("libs", &HGrid::new()).await
139 }
140
141 pub async fn read(&self, filter: &str, limit: Option<usize>) -> Result<HGrid, ClientError> {
143 let mut row = HDict::new();
144 row.set("filter", Kind::Str(filter.to_string()));
145 if let Some(lim) = limit {
146 row.set("limit", Kind::Number(Number::unitless(lim as f64)));
147 }
148 let cols = if limit.is_some() {
149 vec![HCol::new("filter"), HCol::new("limit")]
150 } else {
151 vec![HCol::new("filter")]
152 };
153 let grid = HGrid::from_parts(HDict::new(), cols, vec![row]);
154 self.call("read", &grid).await
155 }
156
157 pub async fn read_by_ids(&self, ids: &[&str]) -> Result<HGrid, ClientError> {
159 let rows: Vec<HDict> = ids
160 .iter()
161 .map(|id| {
162 let mut d = HDict::new();
163 d.set("id", Kind::Ref(HRef::from_val(*id)));
164 d
165 })
166 .collect();
167 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("id")], rows);
168 self.call("read", &grid).await
169 }
170
171 pub async fn nav(&self, nav_id: Option<&str>) -> Result<HGrid, ClientError> {
173 let mut row = HDict::new();
174 if let Some(id) = nav_id {
175 row.set("navId", Kind::Str(id.to_string()));
176 }
177 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("navId")], vec![row]);
178 self.call("nav", &grid).await
179 }
180
181 pub async fn defs(&self, filter: Option<&str>) -> Result<HGrid, ClientError> {
183 let mut row = HDict::new();
184 if let Some(f) = filter {
185 row.set("filter", Kind::Str(f.to_string()));
186 }
187 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("filter")], vec![row]);
188 self.call("defs", &grid).await
189 }
190
191 pub async fn watch_sub(&self, ids: &[&str], lease: Option<&str>) -> Result<HGrid, ClientError> {
195 let rows: Vec<HDict> = ids
196 .iter()
197 .map(|id| {
198 let mut d = HDict::new();
199 d.set("id", Kind::Ref(HRef::from_val(*id)));
200 d
201 })
202 .collect();
203 let mut meta = HDict::new();
204 if let Some(l) = lease {
205 meta.set("lease", Kind::Str(l.to_string()));
206 }
207 let grid = HGrid::from_parts(meta, vec![HCol::new("id")], rows);
208 self.call("watchSub", &grid).await
209 }
210
211 pub async fn watch_poll(&self, watch_id: &str) -> Result<HGrid, ClientError> {
213 let mut meta = HDict::new();
214 meta.set("watchId", Kind::Str(watch_id.to_string()));
215 let grid = HGrid::from_parts(meta, vec![], vec![]);
216 self.call("watchPoll", &grid).await
217 }
218
219 pub async fn watch_unsub(&self, watch_id: &str, ids: &[&str]) -> Result<HGrid, ClientError> {
221 let rows: Vec<HDict> = ids
222 .iter()
223 .map(|id| {
224 let mut d = HDict::new();
225 d.set("id", Kind::Ref(HRef::from_val(*id)));
226 d
227 })
228 .collect();
229 let mut meta = HDict::new();
230 meta.set("watchId", Kind::Str(watch_id.to_string()));
231 let grid = HGrid::from_parts(meta, vec![HCol::new("id")], rows);
232 self.call("watchUnsub", &grid).await
233 }
234
235 pub async fn point_write(&self, id: &str, level: u8, val: Kind) -> Result<HGrid, ClientError> {
237 let mut row = HDict::new();
238 row.set("id", Kind::Ref(HRef::from_val(id)));
239 row.set("level", Kind::Number(Number::unitless(level as f64)));
240 row.set("val", val);
241 let grid = HGrid::from_parts(
242 HDict::new(),
243 vec![HCol::new("id"), HCol::new("level"), HCol::new("val")],
244 vec![row],
245 );
246 self.call("pointWrite", &grid).await
247 }
248
249 pub async fn his_read(&self, id: &str, range: &str) -> Result<HGrid, ClientError> {
254 let mut row = HDict::new();
255 row.set("id", Kind::Ref(HRef::from_val(id)));
256 row.set("range", Kind::Str(range.to_string()));
257 let grid = HGrid::from_parts(
258 HDict::new(),
259 vec![HCol::new("id"), HCol::new("range")],
260 vec![row],
261 );
262 self.call("hisRead", &grid).await
263 }
264
265 pub async fn his_write(&self, id: &str, items: Vec<HDict>) -> Result<HGrid, ClientError> {
269 let mut meta = HDict::new();
270 meta.set("id", Kind::Ref(HRef::from_val(id)));
271 let grid = HGrid::from_parts(meta, vec![HCol::new("ts"), HCol::new("val")], items);
272 self.call("hisWrite", &grid).await
273 }
274
275 pub async fn invoke_action(
277 &self,
278 id: &str,
279 action: &str,
280 args: HDict,
281 ) -> Result<HGrid, ClientError> {
282 let mut row = args;
283 row.set("id", Kind::Ref(HRef::from_val(id)));
284 row.set("action", Kind::Str(action.to_string()));
285 let grid = HGrid::from_parts(
286 HDict::new(),
287 vec![HCol::new("id"), HCol::new("action")],
288 vec![row],
289 );
290 self.call("invokeAction", &grid).await
291 }
292
293 pub async fn close_session(&self) -> Result<HGrid, ClientError> {
298 self.call("close", &HGrid::new()).await
299 }
300
301 pub async fn specs(&self, lib: Option<&str>) -> Result<HGrid, ClientError> {
307 let mut row = HDict::new();
308 if let Some(lib_name) = lib {
309 row.set("lib", Kind::Str(lib_name.to_string()));
310 }
311 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("lib")], vec![row]);
312 self.call("specs", &grid).await
313 }
314
315 pub async fn spec(&self, qname: &str) -> Result<HGrid, ClientError> {
317 let mut row = HDict::new();
318 row.set("qname", Kind::Str(qname.to_string()));
319 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("qname")], vec![row]);
320 self.call("spec", &grid).await
321 }
322
323 pub async fn load_lib(&self, name: &str, source: &str) -> Result<HGrid, ClientError> {
325 let mut row = HDict::new();
326 row.set("name", Kind::Str(name.to_string()));
327 row.set("source", Kind::Str(source.to_string()));
328 let grid = HGrid::from_parts(
329 HDict::new(),
330 vec![HCol::new("name"), HCol::new("source")],
331 vec![row],
332 );
333 self.call("loadLib", &grid).await
334 }
335
336 pub async fn unload_lib(&self, name: &str) -> Result<HGrid, ClientError> {
338 let mut row = HDict::new();
339 row.set("name", Kind::Str(name.to_string()));
340 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("name")], vec![row]);
341 self.call("unloadLib", &grid).await
342 }
343
344 pub async fn export_lib(&self, name: &str) -> Result<HGrid, ClientError> {
346 let mut row = HDict::new();
347 row.set("name", Kind::Str(name.to_string()));
348 let grid = HGrid::from_parts(HDict::new(), vec![HCol::new("name")], vec![row]);
349 self.call("exportLib", &grid).await
350 }
351
352 pub async fn validate(&self, entities: Vec<HDict>) -> Result<HGrid, ClientError> {
354 let mut col_names: Vec<String> = Vec::new();
356 let mut seen = HashSet::new();
357 for entity in &entities {
358 for name in entity.tag_names() {
359 if seen.insert(name.to_string()) {
360 col_names.push(name.to_string());
361 }
362 }
363 }
364 col_names.sort();
365 let cols: Vec<HCol> = col_names.iter().map(|n| HCol::new(n.as_str())).collect();
366 let grid = HGrid::from_parts(HDict::new(), cols, entities);
367 self.call("validate", &grid).await
368 }
369
370 pub async fn close(&self) -> Result<(), ClientError> {
372 self.transport.close().await
373 }
374}