use http::header::{HeaderName, HeaderValue};
use crate::proto::{Body, Headers, Method, Request, Response, StatusCode, Version};
#[derive(Debug)]
pub enum HttpConvertError {
Method(http::method::InvalidMethod),
Status(http::status::InvalidStatusCode),
HeaderName(http::header::InvalidHeaderName),
HeaderValue(http::header::InvalidHeaderValue),
Uri(http::uri::InvalidUri),
}
impl std::fmt::Display for HttpConvertError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HttpConvertError::Method(e) => write!(f, "invalid method: {e}"),
HttpConvertError::Status(e) => write!(f, "invalid status code: {e}"),
HttpConvertError::HeaderName(e) => write!(f, "invalid header name: {e}"),
HttpConvertError::HeaderValue(e) => write!(f, "invalid header value: {e}"),
HttpConvertError::Uri(e) => write!(f, "invalid uri: {e}"),
}
}
}
impl std::error::Error for HttpConvertError {}
impl From<http::method::InvalidMethod> for HttpConvertError {
fn from(e: http::method::InvalidMethod) -> Self {
HttpConvertError::Method(e)
}
}
impl From<http::status::InvalidStatusCode> for HttpConvertError {
fn from(e: http::status::InvalidStatusCode) -> Self {
HttpConvertError::Status(e)
}
}
impl From<http::header::InvalidHeaderName> for HttpConvertError {
fn from(e: http::header::InvalidHeaderName) -> Self {
HttpConvertError::HeaderName(e)
}
}
impl From<http::header::InvalidHeaderValue> for HttpConvertError {
fn from(e: http::header::InvalidHeaderValue) -> Self {
HttpConvertError::HeaderValue(e)
}
}
impl From<http::uri::InvalidUri> for HttpConvertError {
fn from(e: http::uri::InvalidUri) -> Self {
HttpConvertError::Uri(e)
}
}
impl TryFrom<&Method> for http::Method {
type Error = http::method::InvalidMethod;
fn try_from(m: &Method) -> Result<Self, Self::Error> {
http::Method::from_bytes(m.as_str().as_bytes())
}
}
impl From<&http::Method> for Method {
fn from(m: &http::Method) -> Self {
Method::parse(m.as_str())
}
}
impl From<Version> for http::Version {
fn from(v: Version) -> Self {
match v {
Version::Http10 => http::Version::HTTP_10,
Version::Http11 => http::Version::HTTP_11,
Version::Http2 => http::Version::HTTP_2,
Version::Http3 => http::Version::HTTP_3,
}
}
}
impl From<http::Version> for Version {
fn from(v: http::Version) -> Self {
match v {
http::Version::HTTP_2 => Version::Http2,
http::Version::HTTP_3 => Version::Http3,
http::Version::HTTP_10 => Version::Http10,
_ => Version::Http11,
}
}
}
impl TryFrom<StatusCode> for http::StatusCode {
type Error = http::status::InvalidStatusCode;
fn try_from(s: StatusCode) -> Result<Self, Self::Error> {
http::StatusCode::from_u16(s.code())
}
}
impl From<http::StatusCode> for StatusCode {
fn from(s: http::StatusCode) -> Self {
StatusCode::new(s.as_u16(), s.canonical_reason().unwrap_or(""))
}
}
impl TryFrom<&Headers> for http::HeaderMap {
type Error = HttpConvertError;
fn try_from(headers: &Headers) -> Result<Self, Self::Error> {
let mut map = http::HeaderMap::new();
for (name, value) in headers.iter() {
map.append(
HeaderName::from_bytes(name.as_bytes())?,
HeaderValue::from_str(value)?,
);
}
Ok(map)
}
}
impl From<&http::HeaderMap> for Headers {
fn from(map: &http::HeaderMap) -> Self {
let mut headers = Headers::new();
for (name, value) in map.iter() {
headers.append(
name.as_str(),
String::from_utf8_lossy(value.as_bytes()).into_owned(),
);
}
headers
}
}
impl TryFrom<&Request> for http::Request<Vec<u8>> {
type Error = HttpConvertError;
fn try_from(req: &Request) -> Result<Self, Self::Error> {
let mut out = http::Request::new(req.body().to_vec());
*out.method_mut() = http::Method::try_from(req.method())?;
*out.uri_mut() = req.target().parse::<http::Uri>()?;
*out.version_mut() = req.version().into();
*out.headers_mut() = http::HeaderMap::try_from(req.headers())?;
Ok(out)
}
}
impl From<http::Request<Vec<u8>>> for Request {
fn from(req: http::Request<Vec<u8>>) -> Self {
let (parts, body) = req.into_parts();
let target = parts
.uri
.path_and_query()
.map(|pq| pq.as_str().to_owned())
.unwrap_or_else(|| "/".to_owned());
Request::new(
Method::from(&parts.method),
target,
Version::from(parts.version),
Headers::from(&parts.headers),
body,
)
}
}
impl TryFrom<Response> for http::Response<Vec<u8>> {
type Error = HttpConvertError;
fn try_from(resp: Response) -> Result<Self, Self::Error> {
let (status, headers, body) = resp.into_parts();
let mut out = http::Response::new(body.into_bytes());
*out.status_mut() = http::StatusCode::try_from(status)?;
*out.headers_mut() = http::HeaderMap::try_from(&headers)?;
Ok(out)
}
}
impl From<http::Response<Vec<u8>>> for Response {
fn from(resp: http::Response<Vec<u8>>) -> Self {
let (parts, body) = resp.into_parts();
Response::from_parts(
StatusCode::from(parts.status),
Headers::from(&parts.headers),
Body::from(body),
)
}
}
#[cfg(feature = "router")]
impl crate::router::IntoResponse for http::Response<Vec<u8>> {
fn into_response(self) -> Response {
self.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn method_roundtrip() {
let h: http::Method = (&Method::Post).try_into().unwrap();
assert_eq!(h, http::Method::POST);
assert_eq!(Method::from(&http::Method::DELETE), Method::Delete);
}
#[test]
fn status_roundtrip() {
let h: http::StatusCode = StatusCode::NOT_FOUND.try_into().unwrap();
assert_eq!(h, http::StatusCode::NOT_FOUND);
assert_eq!(StatusCode::from(http::StatusCode::CREATED).code(), 201);
}
#[test]
fn response_from_http_keeps_status_headers_body() {
let h = http::Response::builder()
.status(http::StatusCode::ACCEPTED)
.header("x-test", "1")
.header("x-test", "2")
.body(b"body".to_vec())
.unwrap();
let resp: Response = h.into();
assert_eq!(resp.status_code().code(), 202);
assert_eq!(resp.body_ref().as_bytes(), b"body");
let vals: Vec<_> = resp.headers().get_all("x-test").collect();
assert_eq!(vals, ["1", "2"]);
}
#[test]
fn request_to_http_carries_target_and_headers() {
let mut headers = Headers::new();
headers.append("Host", "example.com");
let req = Request::new(
Method::Get,
"/path?q=1".to_owned(),
Version::Http11,
headers,
Vec::new(),
);
let out: http::Request<Vec<u8>> = (&req).try_into().unwrap();
assert_eq!(out.method(), http::Method::GET);
assert_eq!(out.uri().path(), "/path");
assert_eq!(out.uri().query(), Some("q=1"));
assert_eq!(out.headers().get("host").unwrap(), "example.com");
}
#[test]
fn response_to_http_and_back() {
let resp = Response::text("hello").with_status(StatusCode::OK);
let h: http::Response<Vec<u8>> = resp.try_into().unwrap();
assert_eq!(h.status(), http::StatusCode::OK);
let back: Response = h.into();
assert_eq!(back.body_ref().as_bytes(), b"hello");
}
}