pub use wasip3::http_compat::{IncomingMessage, Request, Response};
use hyperium as http;
pub use hyperium::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
use std::any::Any;
use wasip3::{
http::types,
http_compat::{
http_from_wasi_request, http_from_wasi_response, http_into_wasi_request,
http_into_wasi_response,
},
};
pub mod body;
#[cfg(feature = "grpc")]
#[cfg_attr(docsrs, doc(cfg(feature = "grpc")))]
pub mod grpc;
pub type Result<T, E = Error> = ::std::result::Result<T, E>;
type HttpResult<T> = Result<T, types::ErrorCode>;
#[derive(Debug)]
pub enum Error {
ErrorCode(wasip3::http::types::ErrorCode),
HttpError(http::Error),
Other(Box<dyn std::error::Error + Send + Sync>),
Response(wasip3::http::types::Response),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ErrorCode(e) => write!(f, "{e}"),
Error::HttpError(e) => write!(f, "{e}"),
Error::Other(e) => write!(f, "{e}"),
Error::Response(resp) => match http::StatusCode::from_u16(resp.get_status_code()) {
Ok(status) => write!(f, "{status}"),
Err(_) => write!(f, "invalid status code {}", resp.get_status_code()),
},
}
}
}
impl std::error::Error for Error {}
impl From<http::Error> for Error {
fn from(err: http::Error) -> Error {
Error::HttpError(err)
}
}
impl From<anyhow::Error> for Error {
fn from(err: anyhow::Error) -> Error {
match err.downcast::<types::ErrorCode>() {
Ok(code) => Error::ErrorCode(code),
Err(other) => match other.downcast::<Error>() {
Ok(err) => err,
Err(other) => Error::Other(other.into_boxed_dyn_error()),
},
}
}
}
impl From<std::convert::Infallible> for Error {
fn from(v: std::convert::Infallible) -> Self {
match v {}
}
}
impl From<types::ErrorCode> for Error {
fn from(code: types::ErrorCode) -> Self {
Error::ErrorCode(code)
}
}
impl From<types::Response> for Error {
fn from(resp: types::Response) -> Self {
Error::Response(resp)
}
}
impl From<String> for Error {
fn from(s: String) -> Self {
Error::other(s)
}
}
impl From<&'static str> for Error {
fn from(s: &'static str) -> Self {
Error::other(s)
}
}
impl Error {
pub fn other(msg: impl Into<String>) -> Self {
anyhow::Error::msg(msg.into()).into()
}
}
impl<Ok: IntoResponse, Err: Into<Error>> IntoResponse for Result<Ok, Err> {
fn into_response(self) -> HttpResult<types::Response> {
match self {
Ok(ok) => ok.into_response(),
Err(err) => match err.into() {
Error::ErrorCode(code) => Err(code),
Error::Response(resp) => Ok(resp),
Error::HttpError(err) => match err {
err if err.is::<http::method::InvalidMethod>() => {
Err(types::ErrorCode::HttpRequestMethodInvalid)
}
err if err.is::<http::uri::InvalidUri>() => {
Err(types::ErrorCode::HttpRequestUriInvalid)
}
err => Err(types::ErrorCode::InternalError(Some(err.to_string()))),
},
Error::Other(other) => {
Err(types::ErrorCode::InternalError(Some(other.to_string())))
}
},
}
}
}
pub async fn send(request: impl IntoRequest) -> HttpResult<Response> {
let request = request.into_request()?;
let response = wasip3::http::client::send(request).await?;
Response::from_response(response)
}
pub async fn get(url: impl AsRef<str>) -> HttpResult<Response> {
let request = http::Request::get(url.as_ref())
.body(EmptyBody::new())
.map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?;
send(request).await
}
pub async fn post(url: impl AsRef<str>, body: impl Into<bytes::Bytes>) -> HttpResult<Response> {
let request = http::Request::post(url.as_ref())
.body(FullBody::new(body.into()))
.map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?;
send(request).await
}
pub async fn put(url: impl AsRef<str>, body: impl Into<bytes::Bytes>) -> HttpResult<Response> {
let request = http::Request::put(url.as_ref())
.body(FullBody::new(body.into()))
.map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?;
send(request).await
}
pub async fn patch(url: impl AsRef<str>, body: impl Into<bytes::Bytes>) -> HttpResult<Response> {
let request = http::Request::patch(url.as_ref())
.body(FullBody::new(body.into()))
.map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?;
send(request).await
}
pub async fn delete(url: impl AsRef<str>) -> HttpResult<Response> {
let request = http::Request::delete(url.as_ref())
.body(EmptyBody::new())
.map_err(|_| types::ErrorCode::HttpRequestUriInvalid)?;
send(request).await
}
pub type EmptyBody = http_body_util::Empty<bytes::Bytes>;
pub type FullBody<T> = http_body_util::Full<T>;
pub type OptionalBody<T> = http_body_util::Either<FullBody<T>, EmptyBody>;
pub trait FromRequest {
fn from_request(req: wasip3::http::types::Request) -> HttpResult<Self>
where
Self: Sized;
}
impl FromRequest for types::Request {
fn from_request(req: types::Request) -> HttpResult<Self> {
Ok(req)
}
}
impl FromRequest for Request {
fn from_request(req: types::Request) -> HttpResult<Self> {
http_from_wasi_request(req)
}
}
pub trait IntoRequest {
fn into_request(self) -> HttpResult<wasip3::http::types::Request>;
}
impl<T> IntoRequest for http::Request<T>
where
T: http_body::Body + Any,
T::Data: Into<Vec<u8>>,
T::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
fn into_request(self) -> HttpResult<types::Request> {
http_into_wasi_request(self)
}
}
pub trait FromResponse {
fn from_response(response: wasip3::http::types::Response) -> HttpResult<Self>
where
Self: Sized;
}
impl FromResponse for Response {
fn from_response(resp: types::Response) -> HttpResult<Self> {
http_from_wasi_response(resp)
}
}
pub trait IntoResponse {
fn into_response(self) -> HttpResult<wasip3::http::types::Response>;
}
impl IntoResponse for types::Response {
fn into_response(self) -> HttpResult<types::Response> {
Ok(self)
}
}
impl<T> IntoResponse for (http::StatusCode, T)
where
T: http_body::Body + Any,
T::Data: Into<Vec<u8>>,
T::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
fn into_response(self) -> HttpResult<types::Response> {
http_into_wasi_response(
http::Response::builder()
.status(self.0)
.body(self.1)
.unwrap(),
)
}
}
impl IntoResponse for http::StatusCode {
fn into_response(self) -> HttpResult<types::Response> {
(self, EmptyBody::new()).into_response()
}
}
impl IntoResponse for &'static str {
fn into_response(self) -> HttpResult<types::Response> {
http::Response::new(http_body_util::Full::new(self.as_bytes())).into_response()
}
}
impl IntoResponse for String {
fn into_response(self) -> HttpResult<types::Response> {
http::Response::new(self).into_response()
}
}
impl<T> IntoResponse for http::Response<T>
where
T: http_body::Body + Any,
T::Data: Into<Vec<u8>>,
T::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
fn into_response(self) -> HttpResult<types::Response> {
http_into_wasi_response(self)
}
}
impl IntoResponse for () {
fn into_response(self) -> HttpResult<types::Response> {
http::StatusCode::OK.into_response()
}
}
impl IntoResponse for &[u8] {
fn into_response(self) -> HttpResult<types::Response> {
self.to_vec().into_response()
}
}
impl IntoResponse for Vec<u8> {
fn into_response(self) -> HttpResult<types::Response> {
http::Response::new(FullBody::new(bytes::Bytes::from(self))).into_response()
}
}
impl IntoResponse for bytes::Bytes {
fn into_response(self) -> HttpResult<types::Response> {
http::Response::new(FullBody::new(self)).into_response()
}
}
impl<T> IntoResponse for (http::StatusCode, http::HeaderMap, T)
where
T: http_body::Body + Any,
T::Data: Into<Vec<u8>>,
T::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
fn into_response(self) -> HttpResult<types::Response> {
let (status, headers, body) = self;
let mut resp = http::Response::builder().status(status).body(body).unwrap();
*resp.headers_mut() = headers;
resp.into_response()
}
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub struct Json<T>(pub T);
#[cfg(feature = "json")]
impl<T: serde::Serialize> IntoResponse for Json<T> {
fn into_response(self) -> HttpResult<types::Response> {
let body = serde_json::to_vec(&self.0)
.map_err(|e| types::ErrorCode::InternalError(Some(e.to_string())))?;
let mut resp = http::Response::builder()
.status(http::StatusCode::OK)
.body(FullBody::new(bytes::Bytes::from(body)))
.unwrap();
resp.headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("application/json"),
);
resp.into_response()
}
}
#[cfg(feature = "json")]
impl<T: serde::Serialize> IntoResponse for (http::StatusCode, Json<T>) {
fn into_response(self) -> HttpResult<types::Response> {
let body = serde_json::to_vec(&self.1 .0)
.map_err(|e| types::ErrorCode::InternalError(Some(e.to_string())))?;
let mut resp = http::Response::builder()
.status(self.0)
.body(FullBody::new(bytes::Bytes::from(body)))
.unwrap();
resp.headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("application/json"),
);
resp.into_response()
}
}