use std::{ collections::HashMap, convert::{TryFrom, TryInto}, }; use serde::{Deserialize, Serialize}; use thiserror::Error; use tokio::sync::mpsc; // Hyper doesn't seem to make it easy to de/ser requests // and responses and stuff like that, so I do it by hand here. #[derive (Debug, Error)] pub enum Error { #[error ("Unsupported method")] UnsupportedMethod, } #[derive (Debug, Deserialize, Serialize)] pub enum Method { Get, Head, Post, Put, } impl TryFrom for Method { type Error = Error; fn try_from (x: hyper::Method) -> Result { match x { hyper::Method::GET => Ok (Self::Get), hyper::Method::HEAD => Ok (Self::Head), _ => Err (Error::UnsupportedMethod), } } } #[derive (Deserialize, Serialize)] pub struct RequestParts { pub method: Method, // Technically URIs are subtle and complex, but I don't care pub uri: String, // Technically Hyper has headers in a multi-map // but I don't feel like doing that right now. // Headers are _kinda_ ASCII but they can also be binary. // HTTP is nutty. pub headers: HashMap >, } impl RequestParts { /// # Errors /// /// `UnsupportedMethod` if PTTH doesn't support the method pub fn from_hyper ( method: hyper::Method, uri: String, headers: hyper::HeaderMap ) -> Result { let method = Method::try_from (method)?; let headers = headers.into_iter () .filter_map (|(k, v)| { let (k, v) = k.map (|k| (k, v))?; Some ((String::from (k.as_str ()), v.as_bytes ().to_vec ())) }) .collect (); Ok (Self { method, uri, headers, }) } } #[derive (Deserialize, Serialize)] pub struct WrappedRequest { pub id: String, pub req: RequestParts, } #[derive (Debug, Deserialize, Serialize, PartialEq)] pub enum StatusCode { Ok, // 200 NoContent, // 204 PartialContent, // 206 NotModified, // 304 TemporaryRedirect, // 307 BadRequest, // 400 Forbidden, // 403 NotFound, // 404 MethodNotAllowed, // 405 RangeNotSatisfiable, // 416 InternalServerError, // 500 } impl Default for StatusCode { fn default () -> Self { Self::Ok } } impl From for hyper::StatusCode { fn from (x: StatusCode) -> Self { match x { StatusCode::Ok => Self::OK, StatusCode::NoContent => Self::NO_CONTENT, StatusCode::PartialContent => Self::PARTIAL_CONTENT, StatusCode::NotModified => Self::NOT_MODIFIED, StatusCode::TemporaryRedirect => Self::TEMPORARY_REDIRECT, StatusCode::BadRequest => Self::BAD_REQUEST, StatusCode::Forbidden => Self::FORBIDDEN, StatusCode::NotFound => Self::NOT_FOUND, StatusCode::MethodNotAllowed => Self::METHOD_NOT_ALLOWED, StatusCode::RangeNotSatisfiable => Self::RANGE_NOT_SATISFIABLE, StatusCode::InternalServerError => Self::INTERNAL_SERVER_ERROR, } } } #[derive (Default, Deserialize, Serialize)] pub struct ResponseParts { pub status_code: StatusCode, // Technically Hyper has headers in a multi-map // but I don't feel like doing that right now. // We promise not to need this feature. pub headers: HashMap >, } // reqwest and hyper have different Body types for _some reason_ // so I have to do everything myself type Body = mpsc::Receiver , std::convert::Infallible>>; #[derive (Default)] pub struct Response { pub parts: ResponseParts, pub body: Option , // Needed to make Nginx happy pub content_length: Option , } impl Response { pub fn status_code (&mut self, c: StatusCode) -> &mut Self { self.parts.status_code = c; self } pub fn header (&mut self, k: String, v: Vec ) -> &mut Self { self.parts.headers.insert (k, v); self } pub fn body (&mut self, b: Body) -> &mut Self { self.body = Some (b); self } pub fn body_bytes (&mut self, b: Vec ) -> &mut Self { self.content_length = b.len ().try_into ().ok (); self.header ("content-length".to_string (), b.len ().to_string ().into_bytes ()); let (tx, rx) = tokio::sync::mpsc::channel (1); tokio::spawn (async move { tx.send (Ok (b)).await.ok (); }); self.body = Some (rx); self } }