use std::{string::FromUtf8Error, time::Duration};
use crate::{types::Headers, wit::HttpMethod};
pub use http::{HeaderName, HeaderValue, Method, StatusCode};
pub use serde_json::Error as JsonDeserializeError;
pub use url::Url;
use crate::{
types::{AsHeaderName, AsHeaderValue},
wit::{self, HttpClient},
};
use serde::Serialize;
pub fn execute(request: impl Into<HttpRequest>) -> Result<HttpResponse, HttpError> {
let request: HttpRequest = request.into();
HttpClient::execute(request.0).map(Into::into).map_err(Into::into)
}
pub fn execute_many(requests: BatchHttpRequest) -> Vec<Result<HttpResponse, HttpError>> {
HttpClient::execute_many(requests.requests)
.into_iter()
.map(|r| r.map(Into::into).map_err(Into::into))
.collect()
}
impl From<http::Method> for HttpMethod {
fn from(value: http::Method) -> Self {
if value == http::Method::GET {
Self::Get
} else if value == http::Method::POST {
Self::Post
} else if value == http::Method::PUT {
Self::Put
} else if value == http::Method::DELETE {
Self::Delete
} else if value == http::Method::HEAD {
Self::Head
} else if value == http::Method::OPTIONS {
Self::Options
} else if value == http::Method::CONNECT {
Self::Connect
} else if value == http::Method::TRACE {
Self::Trace
} else if value == http::Method::PATCH {
Self::Patch
} else {
unreachable!()
}
}
}
impl From<HttpMethod> for http::Method {
fn from(value: HttpMethod) -> Self {
match value {
HttpMethod::Get => http::Method::GET,
HttpMethod::Post => http::Method::POST,
HttpMethod::Put => http::Method::PUT,
HttpMethod::Delete => http::Method::DELETE,
HttpMethod::Patch => http::Method::PATCH,
HttpMethod::Head => http::Method::HEAD,
HttpMethod::Options => http::Method::OPTIONS,
HttpMethod::Connect => http::Method::CONNECT,
HttpMethod::Trace => http::Method::TRACE,
}
}
}
#[derive(Clone, Debug)]
pub enum HttpError {
Timeout,
Request(String),
Connect(String),
}
impl std::error::Error for HttpError {}
impl std::fmt::Display for HttpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timeout => write!(f, "The request timed out"),
Self::Request(msg) => write!(f, "The request could not be built correctly: {}", msg),
Self::Connect(msg) => write!(f, "The request failed due to an error: {}", msg),
}
}
}
impl From<wit::HttpError> for HttpError {
fn from(value: wit::HttpError) -> Self {
match value {
wit::HttpError::Timeout => Self::Timeout,
wit::HttpError::Request(msg) => Self::Request(msg),
wit::HttpError::Connect(msg) => Self::Connect(msg),
}
}
}
#[derive(Debug)]
pub struct HttpRequest(wit::HttpRequest);
impl HttpRequest {
pub fn get(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::GET)
}
pub fn post(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::POST)
}
pub fn put(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::PUT)
}
pub fn delete(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::DELETE)
}
pub fn patch(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::PATCH)
}
pub fn head(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::HEAD)
}
pub fn options(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::OPTIONS)
}
pub fn trace(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::TRACE)
}
pub fn connect(url: Url) -> HttpRequestBuilder {
Self::builder(url, http::Method::CONNECT)
}
pub fn builder(url: Url, method: http::Method) -> HttpRequestBuilder {
HttpRequestBuilder {
method,
url,
headers: wit::Headers::new().into(),
body: Default::default(),
timeout: Default::default(),
}
}
}
pub struct HttpRequestBuilder {
url: Url,
method: http::Method,
headers: Headers,
body: Vec<u8>,
timeout: Option<Duration>,
}
impl HttpRequestBuilder {
pub fn url(&mut self) -> &mut url::Url {
&mut self.url
}
pub fn headers(&mut self) -> &mut Headers {
&mut self.headers
}
pub fn header(&mut self, name: impl AsHeaderName, value: impl AsHeaderValue) -> &mut Self {
self.headers.append(name, value);
self
}
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
self.timeout = Some(timeout);
self
}
pub fn json<T: Serialize>(mut self, body: T) -> HttpRequest {
self.headers.append("Content-Type", "application/json");
self.body(serde_json::to_vec(&body).unwrap())
}
pub fn form<T: Serialize>(mut self, body: T) -> HttpRequest {
self.headers.append("Content-Type", "application/x-www-form-urlencoded");
self.body(serde_urlencoded::to_string(&body).unwrap().into_bytes())
}
pub fn body(mut self, body: Vec<u8>) -> HttpRequest {
self.body = body;
self.build()
}
pub fn build(self) -> HttpRequest {
HttpRequest(wit::HttpRequest {
method: self.method.into(),
url: self.url.to_string(),
headers: self.headers.into(),
body: self.body,
timeout_ms: self.timeout.map(|d| d.as_millis() as u64),
})
}
}
impl From<HttpRequestBuilder> for HttpRequest {
fn from(builder: HttpRequestBuilder) -> Self {
builder.build()
}
}
pub struct BatchHttpRequest {
pub(crate) requests: Vec<wit::HttpRequest>,
}
impl BatchHttpRequest {
pub fn new() -> Self {
Self { requests: Vec::new() }
}
pub fn push(&mut self, request: HttpRequest) {
self.requests.push(request.0);
}
pub fn len(&self) -> usize {
self.requests.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Default for BatchHttpRequest {
fn default() -> Self {
Self::new()
}
}
pub struct HttpResponse {
status_code: http::StatusCode,
headers: Headers,
body: Vec<u8>,
}
impl From<wit::HttpResponse> for HttpResponse {
fn from(response: wit::HttpResponse) -> Self {
Self {
status_code: http::StatusCode::from_u16(response.status).expect("Provided by the host"),
headers: response.headers.into(),
body: response.body,
}
}
}
impl HttpResponse {
pub fn status(&self) -> http::StatusCode {
self.status_code
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn into_bytes(self) -> Vec<u8> {
self.body
}
pub fn text(self) -> Result<String, FromUtf8Error> {
String::from_utf8(self.body)
}
pub fn json<'de, T>(&'de self) -> Result<T, JsonDeserializeError>
where
T: serde::de::Deserialize<'de>,
{
serde_json::from_slice(&self.body)
}
}