use std::collections::HashMap;
use crate::status::{HttpStatus, ServerErrorResponse};
pub trait Response {
fn to_response(&self) -> HttpResponse;
}
impl Response for &str {
fn to_response(&self) -> HttpResponse {
HttpResponse::new_body((*self).to_string(), HttpStatus::default())
}
}
impl Response for String {
fn to_response(&self) -> HttpResponse {
HttpResponse::new_body(self.clone(), HttpStatus::default())
}
}
impl<S: Response> Response for Option<S> {
fn to_response(&self) -> HttpResponse {
match self {
Some(e) => e.to_response(),
None => HttpResponse::new().set_status(HttpStatus::ServerError(
ServerErrorResponse::InternalServerError,
)),
}
}
}
impl<S: Response, E: Response> Response for Result<S, E> {
fn to_response(&self) -> HttpResponse {
match self {
Ok(s) => s.to_response().set_status(HttpStatus::default()),
Err(e) => e
.to_response()
.set_status(ServerErrorResponse::InternalServerError.into()),
}
}
}
impl Response for HttpResponse {
fn to_response(&self) -> HttpResponse {
self.clone()
}
}
#[derive(Eq, PartialEq, Clone, Debug, Default)]
pub struct HttpResponse {
pub headers: HashMap<String, String>,
pub status: HttpStatus,
pub body: Option<String>,
}
impl HttpResponse {
#[must_use]
pub fn new_body(body: String, status: HttpStatus) -> Self {
let mut headers: HashMap<String, String> = HashMap::new();
headers.insert("Content-Length".into(), body.chars().count().to_string());
Self {
headers,
status,
body: Some(body),
}
}
#[must_use]
pub fn insert_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.insert(key.into(), value.into());
self
}
#[must_use]
pub fn set_body(mut self, body: impl Into<String>) -> Self {
let body = body.into();
let body_len = body.chars().count();
self.body.replace(body);
self.headers
.insert("Content-Length".into(), body_len.to_string());
self
}
#[must_use]
pub fn set_status(mut self, status: HttpStatus) -> Self {
self.status = status;
self
}
#[must_use]
pub fn new() -> Self {
Self {
headers: HashMap::new(),
status: HttpStatus::default(),
body: None,
}
}
pub(crate) fn into_bytes(self) -> Vec<u8> {
self.into_string().into_bytes()
}
pub(crate) fn into_string(self) -> String {
use std::fmt::Write;
let headers = self.headers.iter().fold(String::new(), |mut acc, (k, v)| {
acc.write_fmt(core::format_args!("{k}: {v}\r\n"))
.expect("if this fails fuck clippy");
acc
});
format!(
"HTTP/1.1 {}\r\n{headers}\r\n{}",
self.status,
self.body.unwrap_or_default()
)
}
}