use crate::{ffi::http_request_v1 as ffi, Error, ErrorCode};
use std::{
borrow::Cow,
future::Future,
pin::Pin,
string::FromUtf8Error,
task::{Context, Poll},
};
#[doc(hidden)]
pub use ffi::API as FFI_API;
pub use ffi::Method;
#[derive(Clone, Debug, PartialEq)]
pub enum Url {
Static(&'static str),
StaticDomain {
domain: &'static str,
path: Cow<'static, str>,
},
}
impl Url {
pub const fn const_from_str(url: &'static str) -> Self {
Self::Static(url)
}
pub const fn const_with_path(domain: &'static str, path: &'static str) -> Self {
Self::StaticDomain {
domain,
path: Cow::Borrowed(path),
}
}
pub fn join_path(&self, extra_path: &str) -> Self {
match self {
Self::StaticDomain { domain, path } => Self::StaticDomain {
domain,
path: format!("{path}{extra_path}").into(),
},
Self::Static(url) => {
let url = url
.strip_prefix("https://")
.expect("invalid protocol in URL");
let (domain, path) = url.split_once('/').expect("domain/path split not found");
Self::StaticDomain {
domain,
path: format!("{path}{extra_path}").into(),
}
}
}
}
}
impl std::fmt::Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Static(url) => write!(f, "{url}"),
Self::StaticDomain { domain, path } => write!(f, "https://{domain}{path}"),
}
}
}
pub struct Response {
pub status_code: u16,
bytes: Vec<u8>,
}
impl Response {
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn as_text(&self) -> Result<String, FromUtf8Error> {
String::from_utf8(self.bytes.clone())
}
pub fn as_text_lossy(&self) -> Cow<'_, str> {
String::from_utf8_lossy(&self.bytes)
}
}
struct RequestFuture(Result<ffi::RequestHandle, ErrorCode>);
pub type ResponseResult = Result<Response, Error>;
impl Future for RequestFuture {
type Output = ResponseResult;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.0 {
Ok(handle) => {
let mut status: u32 = 0;
if let Err(error_code) = ffi::is_ready(handle, &mut status) {
return Poll::Ready(Err(Error::from(error_code)));
}
if status != ffi::STATUS_PENDING {
match ffi::retrieve(handle) {
Ok(bytes) => {
assert!(u16::try_from(status).is_ok());
let response = Response {
bytes,
status_code: status as u16,
};
Poll::Ready(Ok(response))
}
Err(error_code) => Poll::Ready(Err(Error::from(error_code))),
}
} else {
Poll::Pending
}
}
Err(error_code) => Poll::Ready(Err(Error::from(error_code))),
}
}
}
pub fn http_request(
method: Method,
url: &Url,
body: &[u8],
) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(method, &url.to_string(), body))
}
pub fn http_get(url: &Url) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Get, &url.to_string(), &[]))
}
pub fn http_post(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Post, &url.to_string(), body))
}
pub fn http_put(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Put, &url.to_string(), body))
}
pub fn http_delete(url: &Url) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Delete, &url.to_string(), &[]))
}
pub fn http_patch(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Patch, &url.to_string(), body))
}
pub fn http_head(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Head, &url.to_string(), body))
}
pub fn http_options(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Options, &url.to_string(), body))
}
pub fn http_trace(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Trace, &url.to_string(), body))
}
pub fn http_connect(url: &Url, body: &[u8]) -> impl Future<Output = ResponseResult> {
RequestFuture(ffi::request(ffi::Method::Connect, &url.to_string(), body))
}