use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use crate::error::{Error, Result};
use crate::options::APIRequestOptions;
use crate::types::Headers;
#[derive(Clone)]
pub struct APIRequestContext {
client: reqwest::Client,
default_headers: Headers,
}
impl APIRequestContext {
pub fn new(default_headers: Headers) -> Self {
let client = reqwest::Client::builder()
.build()
.unwrap_or_else(|_| reqwest::Client::new());
Self {
client,
default_headers,
}
}
pub fn default_headers(&self) -> &Headers {
&self.default_headers
}
pub async fn get(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
self.send(reqwest::Method::GET, url, options).await
}
pub async fn post(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
self.send(reqwest::Method::POST, url, options).await
}
pub async fn put(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
self.send(reqwest::Method::PUT, url, options).await
}
pub async fn patch(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
self.send(reqwest::Method::PATCH, url, options).await
}
pub async fn delete(
&self,
url: &str,
options: Option<APIRequestOptions>,
) -> Result<APIResponse> {
self.send(reqwest::Method::DELETE, url, options).await
}
pub async fn head(&self, url: &str, options: Option<APIRequestOptions>) -> Result<APIResponse> {
self.send(reqwest::Method::HEAD, url, options).await
}
async fn send(
&self,
method: reqwest::Method,
url: &str,
options: Option<APIRequestOptions>,
) -> Result<APIResponse> {
let options = options.unwrap_or_default();
let mut builder = self.client.request(method, url);
for (k, v) in &self.default_headers {
builder = builder.header(k.as_str(), v.as_str());
}
if let Some(headers) = options.headers.as_ref() {
for (k, v) in headers {
builder = builder.header(k.as_str(), v.as_str());
}
}
if let Some(params) = options.params.as_ref() {
builder = builder.query(¶ms);
}
if let Some(data) = options.data.as_ref() {
builder = builder.json(data);
} else if let Some(form) = options.form.as_ref() {
builder = builder.form(form);
}
if let Some(timeout_ms) = options.timeout {
builder = builder.timeout(Duration::from_millis(timeout_ms.max(0.0) as u64));
}
let resp = builder
.send()
.await
.map_err(|e| Error::Http(format!("request failed: {e}")))?;
let url = resp.url().to_string();
let status = resp.status().as_u16();
let mut headers: Headers = HashMap::with_capacity(resp.headers().len());
for (name, value) in resp.headers().iter() {
let key = name.as_str().to_ascii_lowercase();
let val = match value.to_str() {
Ok(s) => s.to_string(),
Err(_) => {
String::from_utf8_lossy(value.as_bytes()).into_owned()
}
};
headers.insert(key, val);
}
let body = resp
.bytes()
.await
.map_err(|e| Error::Http(format!("failed to read body: {e}")))?;
Ok(APIResponse {
url,
status,
headers,
body: Arc::from(body.as_ref()),
})
}
}
#[derive(Debug, Clone)]
pub struct APIResponse {
url: String,
status: u16,
headers: Headers,
body: Arc<[u8]>,
}
impl APIResponse {
pub fn url(&self) -> &str {
&self.url
}
pub fn status(&self) -> u16 {
self.status
}
pub fn ok(&self) -> bool {
(200..300).contains(&self.status)
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub async fn body(&self) -> Result<Vec<u8>> {
Ok(self.body.to_vec())
}
pub async fn text(&self) -> Result<String> {
Ok(String::from_utf8_lossy(&self.body).into_owned())
}
pub async fn json(&self) -> Result<serde_json::Value> {
serde_json::from_slice(&self.body).map_err(Into::into)
}
}