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::{self, 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 is_binary = self.format == codecs::HBF_MIME;
60
61 let response = if GET_OPS.contains(&op) {
62 self.client
64 .get(&url)
65 .header(
66 "Authorization",
67 format!("BEARER authToken={}", self.auth_token),
68 )
69 .header("Accept", &self.format)
70 .send()
71 .await
72 .map_err(|e| ClientError::Transport(e.to_string()))?
73 } else {
74 let zinc = codec_for("text/zinc").expect("zinc codec must exist");
77 let (body_bytes, content_type) = if is_binary {
78 let text = zinc
79 .encode_grid(req)
80 .map_err(|e| ClientError::Codec(e.to_string()))?;
81 (text.into_bytes(), zinc.mime_type())
82 } else {
83 let codec = codec_for(&self.format).ok_or_else(|| {
84 ClientError::Codec(format!("unsupported format: {}", self.format))
85 })?;
86 let text = codec
87 .encode_grid(req)
88 .map_err(|e| ClientError::Codec(e.to_string()))?;
89 (text.into_bytes(), codec.mime_type())
90 };
91
92 self.client
93 .post(&url)
94 .header(
95 "Authorization",
96 format!("BEARER authToken={}", self.auth_token),
97 )
98 .header("Content-Type", content_type)
99 .header("Accept", &self.format)
100 .body(body_bytes)
101 .send()
102 .await
103 .map_err(|e| ClientError::Transport(e.to_string()))?
104 };
105
106 let status = response.status();
107
108 let grid = if is_binary {
110 let bytes = response
111 .bytes()
112 .await
113 .map_err(|e| ClientError::Transport(e.to_string()))?;
114 if !status.is_success() {
115 return Err(ClientError::ServerError(format!(
116 "HTTP {} — ({} bytes)",
117 status,
118 bytes.len()
119 )));
120 }
121 codecs::decode_grid_binary(&bytes).map_err(ClientError::Codec)?
122 } else {
123 let resp_body = response
124 .text()
125 .await
126 .map_err(|e| ClientError::Transport(e.to_string()))?;
127 if !status.is_success() {
128 return Err(ClientError::ServerError(format!(
129 "HTTP {} — {}",
130 status, resp_body
131 )));
132 }
133 let codec = codec_for(&self.format).ok_or_else(|| {
134 ClientError::Codec(format!("unsupported format: {}", self.format))
135 })?;
136 codec
137 .decode_grid(&resp_body)
138 .map_err(|e| ClientError::Codec(e.to_string()))?
139 };
140
141 if grid.is_err() {
143 let dis = grid
144 .meta
145 .get("dis")
146 .and_then(|k| {
147 if let Kind::Str(s) = k {
148 Some(s.as_str())
149 } else {
150 None
151 }
152 })
153 .unwrap_or("unknown server error");
154 return Err(ClientError::ServerError(dis.to_string()));
155 }
156
157 Ok(grid)
158 }
159
160 async fn close(&self) -> Result<(), ClientError> {
161 Ok(())
163 }
164}