ptth/crates/ptth_core/src/http_serde.rs

186 lines
4.2 KiB
Rust

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 <hyper::Method> for Method {
type Error = Error;
fn try_from (x: hyper::Method) -> Result <Self, Error> {
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 <String, Vec <u8>>,
}
impl RequestParts {
/// # Errors
///
/// `UnsupportedMethod` if PTTH doesn't support the method
pub fn from_hyper (
method: hyper::Method,
uri: String,
headers: hyper::HeaderMap <hyper::header::HeaderValue>
) -> Result <Self, Error>
{
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 <StatusCode> 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 <String, Vec <u8>>,
}
// reqwest and hyper have different Body types for _some reason_
// so I have to do everything myself
type Body = mpsc::Receiver <Result <Vec <u8>, std::convert::Infallible>>;
#[derive (Default)]
pub struct Response {
pub parts: ResponseParts,
pub body: Option <Body>,
// Needed to make Nginx happy
pub content_length: Option <u64>,
}
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 <u8>) -> &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 <u8>) -> &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
}
}