haystack_client/transport/
http.rs1use std::time::Duration;
2
3use reqwest::Client;
4
5use crate::error::ClientError;
6use crate::transport::Transport;
7use haystack_core::codecs::codec_for;
8use haystack_core::data::HGrid;
9use haystack_core::kinds::Kind;
10
11const GET_OPS: &[&str] = &["about", "ops", "formats"];
13
14pub struct HttpTransport {
19 client: Client,
20 base_url: String,
21 auth_token: String,
22 format: String,
23}
24
25impl HttpTransport {
26 pub fn new(base_url: &str, auth_token: String) -> Self {
31 Self {
32 client: Client::builder()
33 .timeout(Duration::from_secs(30))
34 .build()
35 .unwrap_or_else(|_| Client::new()),
36 base_url: base_url.trim_end_matches('/').to_string(),
37 auth_token,
38 format: "text/zinc".to_string(),
39 }
40 }
41
42 pub fn with_format(base_url: &str, auth_token: String, format: &str) -> Self {
44 Self {
45 client: Client::builder()
46 .timeout(Duration::from_secs(30))
47 .build()
48 .unwrap_or_else(|_| Client::new()),
49 base_url: base_url.trim_end_matches('/').to_string(),
50 auth_token,
51 format: format.to_string(),
52 }
53 }
54}
55
56impl Transport for HttpTransport {
57 async fn call(&self, op: &str, req: &HGrid) -> Result<HGrid, ClientError> {
58 let url = format!("{}/{}", self.base_url, op);
59 let codec = codec_for(&self.format)
60 .ok_or_else(|| ClientError::Codec(format!("unsupported format: {}", self.format)))?;
61
62 let response = if GET_OPS.contains(&op) {
63 self.client
65 .get(&url)
66 .header(
67 "Authorization",
68 format!("BEARER authToken={}", self.auth_token),
69 )
70 .header("Accept", codec.mime_type())
71 .send()
72 .await
73 .map_err(|e| ClientError::Transport(e.to_string()))?
74 } else {
75 let body = codec
77 .encode_grid(req)
78 .map_err(|e| ClientError::Codec(e.to_string()))?;
79
80 self.client
81 .post(&url)
82 .header(
83 "Authorization",
84 format!("BEARER authToken={}", self.auth_token),
85 )
86 .header("Content-Type", codec.mime_type())
87 .header("Accept", codec.mime_type())
88 .body(body)
89 .send()
90 .await
91 .map_err(|e| ClientError::Transport(e.to_string()))?
92 };
93
94 let status = response.status();
95 let resp_body = response
96 .text()
97 .await
98 .map_err(|e| ClientError::Transport(e.to_string()))?;
99
100 if !status.is_success() {
101 return Err(ClientError::ServerError(format!(
102 "HTTP {} — {}",
103 status, resp_body
104 )));
105 }
106
107 let grid = codec
109 .decode_grid(&resp_body)
110 .map_err(|e| ClientError::Codec(e.to_string()))?;
111
112 if grid.is_err() {
114 let dis = grid
115 .meta
116 .get("dis")
117 .and_then(|k| {
118 if let Kind::Str(s) = k {
119 Some(s.as_str())
120 } else {
121 None
122 }
123 })
124 .unwrap_or("unknown server error");
125 return Err(ClientError::ServerError(dis.to_string()));
126 }
127
128 Ok(grid)
129 }
130
131 async fn close(&self) -> Result<(), ClientError> {
132 Ok(())
134 }
135}