1use crate::error::{Error, RequestNotSuccessful};
4use async_trait::async_trait;
5use dotenv::dotenv;
6use exponential_backoff::Backoff;
7use hyper::{
8 client::connect::HttpConnector, header::HeaderValue, Body, Client as HyperClient, Method,
9 Response, StatusCode,
10};
11use hyper_rustls::HttpsConnector;
12use log::{debug, error};
13use serde_json::Value;
14use std::{fmt, ops, thread, time::Duration};
15
16pub const GET: Method = Method::GET;
18pub const POST: Method = Method::POST;
20pub const PUT: Method = Method::PUT;
22pub const DELETE: Method = Method::DELETE;
24
25const RETRY_ATTEMPTS: u32 = 5;
26
27#[derive(Debug)]
29pub struct Client {
30 api_key: String,
31 server_url: String,
32 https_client: HyperClient<HttpsConnector<HttpConnector>>,
33}
34
35#[async_trait]
37pub trait HTTPClient {
38 async fn send_request<T>(
40 &self,
41 method: &str,
42 endpoint: &str,
43 params: &[(&str, &str)],
44 body: Option<String>,
45 ) -> Result<(T, Value), Error>
46 where
47 T: serde::de::DeserializeOwned + std::fmt::Debug;
48}
49
50#[async_trait]
51impl HTTPClient for Client {
52 async fn send_request<T>(
53 &self,
54 method: &str,
55 endpoint: &str,
56 params: &[(&str, &str)],
57 body: Option<String>,
58 ) -> Result<(T, Value), Error>
59 where
60 T: serde::de::DeserializeOwned + std::fmt::Debug,
61 {
62 let api_key: &String = &self.api_key;
63
64 let method = match method {
65 "GET" => GET,
66 "POST" => POST,
67 "PUT" => PUT,
68 "DELETE" => DELETE,
69 &_ => GET,
70 };
71
72 let resp = retry_with_backoff(self, &method, &api_key[..], endpoint, params, body).await?;
73 let status = resp.status();
74
75 let data: (Result<T, Error>, Value) = hyper::body::to_bytes(resp.into_body())
76 .await
77 .map_err(Error::new_network_error)
78 .map(|mut bytes| {
79 dbg!(&bytes);
80
81 if &bytes.len() == &0 {
82 bytes = hyper::body::Bytes::from("{}");
83 }
84 let json = serde_json::from_slice(&bytes).map_err(Error::new_parsing_error);
85
86 (json, bytes)
87 })
88 .and_then(|data| {
89 let json = data.0;
90 let bytes = std::str::from_utf8(&data.1)?;
91 let json_raw: Value = dbg!(serde_json::from_str(bytes)?);
92
93 dbg!(&json);
94 dbg!(&json_raw);
95
96 match status {
97 StatusCode::OK => {}
98 StatusCode::NO_CONTENT => {}
99 _ => {
100 debug!(
101 "Client error! Status: {}, JSON: {}",
102 status,
103 &bytes.to_string()
104 );
105
106 return Err(RequestNotSuccessful::new(status, bytes.to_string()).into());
107 }
108 };
109
110 Ok((json, json_raw))
111 })?;
112 let decoded = data.0?;
113 let raw_json = data.1;
114
115 dbg!(&decoded);
116
117 Ok((decoded, raw_json))
118 }
119}
120
121impl Client {
122 pub fn new(token: &str, server_url: &str) -> Self {
129 let https = hyper_rustls::HttpsConnectorBuilder::new()
130 .with_native_roots()
131 .https_or_http()
132 .enable_http1()
133 .enable_http2()
134 .build();
135
136 Self {
137 api_key: token.to_string(),
138 server_url: server_url.to_string(),
139 https_client: hyper::Client::builder().build::<_, hyper::Body>(https),
140 }
141 }
142
143 pub fn default() -> Self {
150 let token = std::env::var("OP_API_TOKEN").expect("1Password API token expected!");
151 let host = std::env::var("OP_SERVER_URL").expect("1Password Connect server URL expected!");
152
153 dotenv().ok();
155
156 Client::new(&token, &host)
157 }
158
159 pub fn token(&self) -> String {
161 self.api_key.clone()
162 }
163}
164
165struct RetryErrors<'a>(pub(crate) &'a mut Vec<String>);
166
167impl<'a> fmt::Display for RetryErrors<'a> {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 self.iter().fold(Ok(()), |result, error_msg| {
170 result.and_then(|_| writeln!(f, "{}", error_msg))
171 })
172 }
173}
174
175impl ops::Deref for RetryErrors<'_> {
176 type Target = Vec<String>;
177
178 fn deref(&self) -> &Self::Target {
179 self.0
180 }
181}
182
183async fn retry_with_backoff(
185 client: &Client,
186 method: &hyper::Method,
187 api_key: &str,
188 endpoint: &str,
189 params: &[(&str, &str)],
190 body: Option<String>,
191) -> Result<Response<Body>, Error> {
192 let retries = RETRY_ATTEMPTS;
193 let min = Duration::from_millis(100);
194 let max = Duration::from_secs(20);
195 let backoff = Backoff::new(retries, min, max);
196 let mut retry_error_messages: Vec<String> = vec![];
197 let mut retry_errors = vec![];
198
199 for duration in &backoff {
200 let url = format!("{}/{}?{}", client.server_url, endpoint, url_encode(params));
201
202 let body_data = match body {
203 Some(ref value) => Body::from(value.clone()),
204 None => Body::empty(),
205 };
206 let mut req = hyper::Request::builder()
207 .method(method)
208 .uri(&*url)
209 .body(body_data)?;
210
211 let auth = String::from("Bearer ") + api_key;
212 req.headers_mut()
213 .insert("Accept", HeaderValue::from_str("application/json")?);
214 req.headers_mut()
215 .insert("Authorization", HeaderValue::from_str(&auth)?);
216
217 match client.https_client.request(req).await {
218 Ok(value) => return Ok(value),
219 Err(err) => {
220 let error_message = format!("[ Retrying ]: Client error: {}", err);
221 retry_error_messages.push(error_message);
222 retry_errors.push(err);
223
224 thread::sleep(duration)
225 }
226 }
227 }
228
229 let err = if let Some(val) = retry_errors.pop() {
230 val
231 } else {
232 error!("Unable to unwrap error");
233 return Err(Error::new_internal_error());
234 };
235
236 Err(Error::new_retry_error(err))
237}
238
239fn url_encode(params: &[(&str, &str)]) -> String {
240 params
241 .iter()
242 .map(|&t| {
243 let (k, v) = t;
244 format!("{}={}", k, v)
245 })
246 .fold("".to_string(), |mut acc, item| {
247 acc.push_str(&item);
248 acc.push('&');
249 acc.replace('+', "%2B")
250 })
251}