pub mod conversions;
use std::collections::HashMap;
#[doc(inline)]
pub use conversions::IntoResponse;
#[doc(inline)]
pub use types::{
Error, Fields, Headers, IncomingRequest, IncomingResponse, Method, OutgoingBody,
OutgoingRequest, OutgoingResponse, Scheme, StatusCode, Trailers,
};
use self::conversions::{TryFromIncomingResponse, TryIntoOutgoingRequest};
use super::wit::wasi::http::types;
use crate::wit::wasi::io::streams;
use futures::SinkExt;
pub struct Request {
method: Method,
uri: (Option<hyperium::Uri>, String),
headers: HashMap<String, HeaderValue>,
body: Vec<u8>,
}
impl Request {
pub fn new(method: Method, uri: impl Into<String>) -> Self {
Self {
method,
uri: Self::parse_uri(uri.into()),
headers: HashMap::new(),
body: Vec::new(),
}
}
pub fn builder() -> RequestBuilder {
RequestBuilder::new(Method::Get, "/")
}
pub fn get(uri: impl Into<String>) -> RequestBuilder {
RequestBuilder::new(Method::Get, uri)
}
pub fn post(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
let mut builder = RequestBuilder::new(Method::Post, uri);
builder.body(body);
builder
}
pub fn put(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
let mut builder = RequestBuilder::new(Method::Put, uri);
builder.body(body);
builder
}
pub fn patch(uri: impl Into<String>, body: impl conversions::IntoBody) -> RequestBuilder {
let mut builder = RequestBuilder::new(Method::Patch, uri);
builder.body(body);
builder
}
pub fn delete(uri: impl Into<String>) -> RequestBuilder {
RequestBuilder::new(Method::Delete, uri)
}
pub fn method(&self) -> &Method {
&self.method
}
pub fn uri(&self) -> &str {
&self.uri.1
}
pub fn path(&self) -> &str {
self.uri.0.as_ref().map(|u| u.path()).unwrap_or_default()
}
pub fn query(&self) -> &str {
self.uri
.0
.as_ref()
.and_then(|u| u.query())
.unwrap_or_default()
}
pub fn headers(&self) -> impl Iterator<Item = (&str, &HeaderValue)> {
self.headers.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn header(&self, name: &str) -> Option<&HeaderValue> {
self.headers.get(&name.to_lowercase())
}
pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.headers.insert(
name.into(),
HeaderValue {
inner: HeaderValueRep::String(value.into()),
},
);
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn body_mut(&mut self) -> &mut Vec<u8> {
&mut self.body
}
pub fn into_body(self) -> Vec<u8> {
self.body
}
fn parse_uri(uri: String) -> (Option<hyperium::Uri>, String) {
(
hyperium::Uri::try_from(&uri)
.or_else(|_| hyperium::Uri::try_from(&format!("http://{uri}")))
.ok(),
uri,
)
}
fn is_https(&self) -> bool {
self.uri
.0
.as_ref()
.and_then(|u| u.scheme())
.map(|s| s == &hyperium::uri::Scheme::HTTPS)
.unwrap_or(true)
}
fn authority(&self) -> Option<&str> {
self.uri
.0
.as_ref()
.and_then(|u| u.authority())
.map(|a| a.as_str())
}
pub fn path_and_query(&self) -> Option<&str> {
self.uri
.0
.as_ref()
.and_then(|u| u.path_and_query())
.map(|s| s.as_str())
}
}
pub struct RequestBuilder {
request: Request,
}
impl RequestBuilder {
pub fn new(method: Method, uri: impl Into<String>) -> Self {
Self {
request: Request::new(method, uri.into()),
}
}
pub fn method(&mut self, method: Method) -> &mut Self {
self.request.method = method;
self
}
pub fn uri(&mut self, uri: impl Into<String>) -> &mut Self {
self.request.uri = Request::parse_uri(uri.into());
self
}
pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
self.request.headers = into_header_rep(headers);
self
}
pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.request
.headers
.insert(key.into().to_lowercase(), HeaderValue::string(value.into()));
self
}
pub fn body(&mut self, body: impl conversions::IntoBody) -> &mut Self {
self.request.body = body.into_body();
self
}
pub fn build(&mut self) -> Request {
std::mem::replace(&mut self.request, Request::new(Method::Get, "/"))
}
}
pub struct Response {
status: StatusCode,
headers: HashMap<String, HeaderValue>,
body: Vec<u8>,
}
impl Response {
pub fn new(status: impl conversions::IntoStatusCode, body: impl conversions::IntoBody) -> Self {
Self {
status: status.into_status_code(),
headers: HashMap::new(),
body: body.into_body(),
}
}
pub fn status(&self) -> &StatusCode {
&self.status
}
pub fn headers(&self) -> impl Iterator<Item = (&str, &HeaderValue)> {
self.headers.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn header(&self, name: &str) -> Option<&HeaderValue> {
self.headers.get(&name.to_lowercase())
}
pub fn set_header(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.headers.insert(
name.into(),
HeaderValue {
inner: HeaderValueRep::String(value.into()),
},
);
}
pub fn body(&self) -> &[u8] {
&self.body
}
pub fn body_mut(&mut self) -> &mut Vec<u8> {
&mut self.body
}
pub fn into_body(self) -> Vec<u8> {
self.body
}
pub fn into_builder(self) -> ResponseBuilder {
ResponseBuilder { response: self }
}
pub fn builder() -> ResponseBuilder {
ResponseBuilder::new(200)
}
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("status", &self.status)
.field("headers", &self.headers)
.field("body.len()", &self.body.len())
.finish()
}
}
pub struct ResponseBuilder {
response: Response,
}
impl ResponseBuilder {
pub fn new(status: impl conversions::IntoStatusCode) -> Self {
ResponseBuilder {
response: Response::new(status, Vec::new()),
}
}
pub fn status(&mut self, status: impl conversions::IntoStatusCode) -> &mut Self {
self.response.status = status.into_status_code();
self
}
pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
self.response.headers = into_header_rep(headers.into_headers());
self
}
pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.response
.headers
.insert(key.into().to_lowercase(), HeaderValue::string(value.into()));
self
}
pub fn body(&mut self, body: impl conversions::IntoBody) -> &mut Self {
self.response.body = body.into_body();
self
}
pub fn build(&mut self) -> Response {
std::mem::replace(&mut self.response, Response::new(200, Vec::new()))
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct HeaderValue {
inner: HeaderValueRep,
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum HeaderValueRep {
String(String),
Bytes(Vec<u8>),
}
impl HeaderValue {
pub fn string(str: String) -> HeaderValue {
HeaderValue {
inner: HeaderValueRep::String(str),
}
}
pub fn bytes(bytes: Vec<u8>) -> HeaderValue {
HeaderValue {
inner: String::from_utf8(bytes)
.map(HeaderValueRep::String)
.unwrap_or_else(|e| HeaderValueRep::Bytes(e.into_bytes())),
}
}
pub fn as_str(&self) -> Option<&str> {
match &self.inner {
HeaderValueRep::String(s) => Some(s),
HeaderValueRep::Bytes(b) => std::str::from_utf8(b).ok(),
}
}
pub fn as_bytes(&self) -> &[u8] {
self.as_ref()
}
pub fn into_utf8_lossy(self) -> String {
match self.inner {
HeaderValueRep::String(s) => s,
HeaderValueRep::Bytes(b) => String::from_utf8_lossy(&b).into_owned(),
}
}
pub fn into_bytes(self) -> Vec<u8> {
match self.inner {
HeaderValueRep::String(s) => s.into_bytes(),
HeaderValueRep::Bytes(b) => b,
}
}
}
impl AsRef<[u8]> for HeaderValue {
fn as_ref(&self) -> &[u8] {
match &self.inner {
HeaderValueRep::String(s) => s.as_bytes(),
HeaderValueRep::Bytes(b) => b,
}
}
}
fn into_header_rep(headers: impl conversions::IntoHeaders) -> HashMap<String, HeaderValue> {
headers
.into_headers()
.into_iter()
.map(|(k, v)| {
let v = String::from_utf8(v)
.map(HeaderValueRep::String)
.unwrap_or_else(|e| HeaderValueRep::Bytes(e.into_bytes()));
(k.to_lowercase(), HeaderValue { inner: v })
})
.collect()
}
impl std::hash::Hash for Method {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
}
}
impl Eq for Method {}
impl PartialEq for Method {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Other(l), Self::Other(r)) => l == r,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl std::fmt::Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Patch => "PATCH",
Method::Head => "HEAD",
Method::Options => "OPTIONS",
Method::Connect => "CONNECT",
Method::Trace => "TRACE",
Method::Other(o) => o,
})
}
}
impl IncomingRequest {
pub fn uri(&self) -> String {
let scheme_and_authority =
if let (Some(scheme), Some(authority)) = (self.scheme(), self.authority()) {
let scheme = match &scheme {
Scheme::Http => "http://",
Scheme::Https => "https://",
Scheme::Other(s) => s.as_str(),
};
format!("{scheme}{authority}")
} else {
String::new()
};
let path_and_query = self.path_with_query().unwrap_or_default();
format!("{scheme_and_authority}{path_and_query}")
}
pub fn into_body_stream(self) -> impl futures::Stream<Item = Result<Vec<u8>, streams::Error>> {
executor::incoming_body(self.consume().expect("request body was already consumed"))
}
pub async fn into_body(self) -> Result<Vec<u8>, streams::Error> {
use futures::TryStreamExt;
let mut stream = self.into_body_stream();
let mut body = Vec::new();
while let Some(chunk) = stream.try_next().await? {
body.extend(chunk);
}
Ok(body)
}
}
impl IncomingResponse {
pub fn take_body_stream(&self) -> impl futures::Stream<Item = Result<Vec<u8>, streams::Error>> {
executor::incoming_body(self.consume().expect("response body was already consumed"))
}
pub async fn into_body(self) -> Result<Vec<u8>, streams::Error> {
use futures::TryStreamExt;
let mut stream = self.take_body_stream();
let mut body = Vec::new();
while let Some(chunk) = stream.try_next().await? {
body.extend(chunk);
}
Ok(body)
}
}
impl OutgoingResponse {
pub fn take_body(&self) -> impl futures::Sink<Vec<u8>, Error = Error> {
executor::outgoing_body(self.write().expect("response body was already taken"))
}
}
impl OutgoingRequest {
pub fn take_body(&self) -> impl futures::Sink<Vec<u8>, Error = Error> {
executor::outgoing_body(self.write().expect("request body was already taken"))
}
}
pub struct ResponseOutparam(types::ResponseOutparam);
impl ResponseOutparam {
#[doc(hidden)]
pub unsafe fn from_handle(handle: u32) -> Self {
Self(types::ResponseOutparam::from_handle(handle))
}
pub fn set(self, response: OutgoingResponse) {
types::ResponseOutparam::set(self.0, Ok(response));
}
pub async fn set_with_body(
self,
response: OutgoingResponse,
buffer: Vec<u8>,
) -> Result<(), Error> {
let mut body = response.take_body();
self.set(response);
body.send(buffer).await
}
pub fn into_inner(self) -> types::ResponseOutparam {
self.0
}
}
pub async fn send<I, O>(request: I) -> Result<O, SendError>
where
I: TryIntoOutgoingRequest,
I::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
O: TryFromIncomingResponse,
O::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
{
let (request, body_buffer) = I::try_into_outgoing_request(request)
.map_err(|e| SendError::RequestConversion(e.into()))?;
let response = if let Some(body_buffer) = body_buffer {
let mut body_sink = request.take_body();
let response = executor::outgoing_request_send(request);
body_sink
.send(body_buffer)
.await
.map_err(|e| SendError::Http(Error::UnexpectedError(e.to_string())))?;
drop(body_sink);
response.await.map_err(SendError::Http)?
} else {
executor::outgoing_request_send(request)
.await
.map_err(SendError::Http)?
};
TryFromIncomingResponse::try_from_incoming_response(response)
.await
.map_err(|e: O::Error| SendError::ResponseConversion(e.into()))
}
#[derive(thiserror::Error, Debug)]
pub enum SendError {
#[error(transparent)]
RequestConversion(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
ResponseConversion(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
Http(Error),
}
#[doc(hidden)]
mod executor;
#[doc(hidden)]
pub use executor::run;
#[cfg(feature = "json")]
#[derive(Debug)]
pub struct JsonBodyError(serde_json::Error);
#[cfg(feature = "json")]
impl std::error::Error for JsonBodyError {}
#[cfg(feature = "json")]
impl std::fmt::Display for JsonBodyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("could not convert body to json")
}
}
#[derive(Debug)]
pub struct NonUtf8BodyError;
impl std::error::Error for NonUtf8BodyError {}
impl std::fmt::Display for NonUtf8BodyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("body was expected to be utf8 but was not")
}
}
mod router;
pub use router::*;
#[derive(Debug)]
pub struct Body<T>(pub T);
impl<T> std::ops::Deref for Body<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct Json<T>(pub T);
impl<T> std::ops::Deref for Json<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub mod responses {
use super::Response;
pub fn not_found() -> Response {
Response::new(404, "Not Found")
}
pub fn internal_server_error() -> Response {
Response::new(500, "Internal Server Error")
}
pub fn method_not_allowed() -> Response {
Response::new(405, "Method Not Allowed")
}
pub(crate) fn bad_request(msg: Option<String>) -> Response {
Response::new(400, msg.map(|m| m.into_bytes()))
}
}
impl std::fmt::Display for streams::Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_debug_string())
}
}
impl std::error::Error for streams::Error {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_uri_parses() {
let uri = "/hello?world=1";
let req = Request::new(Method::Get, uri);
assert_eq!(req.uri(), uri);
assert_eq!(req.path(), "/hello");
assert_eq!(req.query(), "world=1");
let uri = "http://localhost:3000/hello?world=1";
let req = Request::new(Method::Get, uri);
assert_eq!(req.uri(), uri);
assert_eq!(req.path(), "/hello");
assert_eq!(req.query(), "world=1");
let uri = "localhost:3000/hello?world=1";
let req = Request::new(Method::Get, uri);
assert_eq!(req.uri(), uri);
assert_eq!(req.path(), "/hello");
assert_eq!(req.query(), "world=1");
}
}