office_convert_client/
client.rs1use bytes::Bytes;
2use reqwest::multipart::{Form, Part};
3use serde::Deserialize;
4use std::{sync::Arc, time::Duration};
5use thiserror::Error;
6
7#[derive(Clone)]
8pub struct OfficeConvertClient {
9 http: reqwest::Client,
11 host: Arc<str>,
13}
14
15#[derive(Debug, Error)]
17pub enum CreateError {
18 #[error(transparent)]
20 Builder(reqwest::Error),
21}
22
23#[derive(Debug, Error)]
25pub enum RequestError {
26 #[error(transparent)]
28 RequestFailed(reqwest::Error),
29
30 #[error(transparent)]
32 InvalidResponse(reqwest::Error),
33
34 #[error("server connection timed out")]
36 ServerConnectTimeout,
37
38 #[error("{reason}")]
40 ErrorResponse {
41 reason: String,
42 backtrace: Option<String>,
43 },
44}
45
46impl RequestError {
47 pub fn is_retry(&self) -> bool {
49 matches!(
50 self,
51 RequestError::RequestFailed(_)
52 | RequestError::InvalidResponse(_)
53 | RequestError::ServerConnectTimeout
54 )
55 }
56}
57
58#[derive(Debug, Deserialize)]
59pub struct StatusResponse {
60 pub is_busy: bool,
61}
62
63#[derive(Debug, Deserialize)]
64pub struct SupportedFormat {
65 pub name: String,
67 pub mime: String,
69}
70
71#[derive(Debug, Deserialize)]
72pub struct VersionResponse {
73 pub major: u32,
75 pub minor: u32,
77 pub build_id: String,
79}
80
81#[derive(Debug, Deserialize)]
82#[serde(rename_all = "camelCase")]
83struct ErrorResponse {
84 reason: String,
86 backtrace: Option<String>,
88}
89
90#[derive(Debug, Clone)]
91pub struct ClientOptions {
92 pub connect_timeout: Option<Duration>,
94
95 pub read_timeout: Option<Duration>,
97}
98
99impl Default for ClientOptions {
100 fn default() -> Self {
101 Self {
102 connect_timeout: Some(Duration::from_millis(700)),
104 read_timeout: None,
105 }
106 }
107}
108
109impl OfficeConvertClient {
110 pub fn new<T>(host: T) -> Result<Self, CreateError>
115 where
116 T: Into<Arc<str>>,
117 {
118 Self::new_with_options(host, ClientOptions::default())
119 }
120
121 pub fn new_with_options<T>(host: T, options: ClientOptions) -> Result<Self, CreateError>
127 where
128 T: Into<Arc<str>>,
129 {
130 let mut builder = reqwest::Client::builder();
131
132 if let Some(connect_timeout) = options.connect_timeout {
133 builder = builder.connect_timeout(connect_timeout);
134 }
135
136 if let Some(connect_timeout) = options.read_timeout {
137 builder = builder.read_timeout(connect_timeout);
138 }
139
140 let client = builder.build().map_err(CreateError::Builder)?;
141 Ok(Self::from_client(host, client))
142 }
143
144 pub fn from_client<T>(host: T, client: reqwest::Client) -> Self
151 where
152 T: Into<Arc<str>>,
153 {
154 Self {
155 http: client,
156 host: host.into(),
157 }
158 }
159
160 pub async fn get_status(&self) -> Result<StatusResponse, RequestError> {
162 let route = format!("{}/status", self.host);
163 let response = self
164 .http
165 .get(route)
166 .send()
167 .await
168 .map_err(RequestError::RequestFailed)?;
169
170 let status = response.status();
171
172 if status.is_client_error() || status.is_server_error() {
174 let body: ErrorResponse = response
175 .json()
176 .await
177 .map_err(RequestError::InvalidResponse)?;
178
179 return Err(RequestError::ErrorResponse {
180 reason: body.reason,
181 backtrace: body.backtrace,
182 });
183 }
184
185 let response: StatusResponse = response
187 .json()
188 .await
189 .map_err(RequestError::InvalidResponse)?;
190
191 Ok(response)
192 }
193
194 pub async fn get_office_version(&self) -> Result<VersionResponse, RequestError> {
196 let route = format!("{}/office-version", self.host);
197 let response = self
198 .http
199 .get(route)
200 .send()
201 .await
202 .map_err(RequestError::RequestFailed)?;
203
204 let status = response.status();
205
206 if status.is_client_error() || status.is_server_error() {
208 let body: ErrorResponse = response
209 .json()
210 .await
211 .map_err(RequestError::InvalidResponse)?;
212
213 return Err(RequestError::ErrorResponse {
214 reason: body.reason,
215 backtrace: body.backtrace,
216 });
217 }
218
219 let response: VersionResponse = response
221 .json()
222 .await
223 .map_err(RequestError::InvalidResponse)?;
224
225 Ok(response)
226 }
227
228 pub async fn get_supported_formats(&self) -> Result<Vec<SupportedFormat>, RequestError> {
232 let route = format!("{}/supported-formats", self.host);
233 let response = self
234 .http
235 .get(route)
236 .send()
237 .await
238 .map_err(RequestError::RequestFailed)?;
239
240 let status = response.status();
241
242 if status.is_client_error() || status.is_server_error() {
244 let body: ErrorResponse = response
245 .json()
246 .await
247 .map_err(RequestError::InvalidResponse)?;
248
249 return Err(RequestError::ErrorResponse {
250 reason: body.reason,
251 backtrace: body.backtrace,
252 });
253 }
254
255 let response: Vec<SupportedFormat> = response
257 .json()
258 .await
259 .map_err(RequestError::InvalidResponse)?;
260
261 Ok(response)
262 }
263
264 pub async fn is_busy(&self) -> Result<bool, RequestError> {
266 let status = self.get_status().await?;
267 Ok(status.is_busy)
268 }
269
270 pub async fn collect_garbage(&self) -> Result<(), RequestError> {
272 let route = format!("{}/collect-garbage", self.host);
273 let response = self
274 .http
275 .post(route)
276 .send()
277 .await
278 .map_err(RequestError::RequestFailed)?;
279
280 let status = response.status();
281
282 if status.is_client_error() || status.is_server_error() {
284 let body: ErrorResponse = response
285 .json()
286 .await
287 .map_err(RequestError::InvalidResponse)?;
288
289 return Err(RequestError::ErrorResponse {
290 reason: body.reason,
291 backtrace: body.backtrace,
292 });
293 }
294
295 Ok(())
296 }
297
298 pub async fn convert(&self, file: Bytes) -> Result<Bytes, RequestError> {
299 let route = format!("{}/convert", self.host);
300 let form = Form::new().part("file", Part::stream(file));
301 let response = self
302 .http
303 .post(route)
304 .multipart(form)
305 .send()
306 .await
307 .map_err(RequestError::RequestFailed)?;
308
309 let status = response.status();
310
311 if status.is_client_error() || status.is_server_error() {
313 let body: ErrorResponse = response
314 .json()
315 .await
316 .map_err(RequestError::InvalidResponse)?;
317
318 return Err(RequestError::ErrorResponse {
319 reason: body.reason,
320 backtrace: body.backtrace,
321 });
322 }
323
324 let response = response
325 .bytes()
326 .await
327 .map_err(RequestError::InvalidResponse)?;
328
329 Ok(response)
330 }
331}