use crate::enums::content_encoding::ContentEncoding;
use crate::enums::header::content_type::ContentType;
use crate::enums::http_error::{HttpError, HttpErrorKind};
use crate::enums::transfer_encoding::TransferEncoding;
use crate::utils::http_header_field_name;
use crate::utils::string_util::split_at_first;
use core::fmt;
use std::borrow::Cow;
use std::str::FromStr;
#[derive(Clone, PartialEq)]
pub enum HttpHeader {
ContentType(Vec<ContentType>),
TransferEncoding(Vec<TransferEncoding>),
ContentEncoding(Vec<ContentEncoding>),
ContentLength(usize),
Generic { key: String, value: String },
}
impl HttpHeader {
pub fn new<T1: ToString, T2: ToString>(key: T1, value: T2) -> HttpHeader {
HttpHeader::Generic {
key: key.to_string(),
value: value.to_string(),
}
}
pub fn field_name_lowercase(&self) -> Cow<'static, str> {
match self {
HttpHeader::ContentType(_) => Cow::Borrowed(http_header_field_name::CONTENT_TYPE_L),
HttpHeader::ContentEncoding(_) => {
Cow::Borrowed(http_header_field_name::CONTENT_ENCODING_L)
}
HttpHeader::ContentLength(_) => Cow::Borrowed(http_header_field_name::CONTENT_LENGTH_L),
HttpHeader::TransferEncoding(_) => {
Cow::Borrowed(http_header_field_name::TRANSFER_ENCODING_L)
}
HttpHeader::Generic { key, .. } => Cow::Owned(key.to_lowercase()),
}
}
}
impl FromStr for HttpHeader {
type Err = HttpError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let result = match split_at_first(s, ":") {
None => return Err(HttpErrorKind::Parse.into()),
Some((key, value)) => match &key.to_lowercase() as &str {
http_header_field_name::CONTENT_TYPE_L => {
let content_types = value
.split(";")
.map(|string| ContentType::from_str(string))
.collect::<Result<Vec<_>, HttpError>>()?;
HttpHeader::ContentType(content_types)
}
http_header_field_name::CONTENT_ENCODING_L => {
let content_encodings = value
.split(",")
.map(|string| ContentEncoding::from_str(string))
.collect::<Result<Vec<_>, HttpError>>()?;
HttpHeader::ContentEncoding(content_encodings)
}
http_header_field_name::CONTENT_LENGTH_L => {
HttpHeader::ContentLength(usize::from_str(value.trim())?)
}
http_header_field_name::TRANSFER_ENCODING_L => {
let transfer_encodings = value
.split(",")
.map(|string| TransferEncoding::from_str(string))
.collect::<Result<Vec<_>, HttpError>>()?;
HttpHeader::TransferEncoding(transfer_encodings)
}
_ => HttpHeader::new(key.to_lowercase().trim(), value.trim().to_string()),
},
};
Ok(result)
}
}
impl fmt::Display for HttpHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HttpHeader::ContentType(content_types) => {
write!(f, "{}: ", http_header_field_name::CONTENT_TYPE)?;
let mut first = true;
for item in content_types {
if !first {
f.write_str("; ")?;
}
write!(f, "{}", item)?;
first = false;
}
Ok(())
}
HttpHeader::ContentLength(length) => {
write!(f, "{}: {}", http_header_field_name::CONTENT_LENGTH, length)
}
HttpHeader::ContentEncoding(encodings) => {
write!(f, "{}: ", http_header_field_name::CONTENT_ENCODING)?;
let mut first = true;
for item in encodings {
if !first {
f.write_str(",")?;
}
write!(f, "{}", item)?;
first = false;
}
Ok(())
}
HttpHeader::TransferEncoding(encodings) => {
write!(f, "{}: ", http_header_field_name::TRANSFER_ENCODING)?;
let mut first = true;
for item in encodings {
if !first {
f.write_str(",")?;
}
write!(f, "{}", item)?;
first = false;
}
Ok(())
}
HttpHeader::Generic { key, value } => {
write!(f, "{}: {}", key, value)
}
}
}
}
impl fmt::Debug for HttpHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::enums::char_set::CharSet;
use crate::enums::media_type::media_application_subtype::MediaApplicationSubtype;
use crate::enums::media_type::media_type::MediaType;
#[test]
fn test_to_string_http_header() -> Result<(), HttpError> {
let header = HttpHeader::ContentType(vec![
ContentType::MediaType(MediaType::Application(MediaApplicationSubtype::Json)),
ContentType::Charset(CharSet::Iso88591),
]);
assert_eq!(
header.to_string(),
"Content-Type: application/json; charset=ISO-8859-1"
);
Ok(())
}
#[test]
fn test_from_str_http_header() -> Result<(), HttpError> {
let header = HttpHeader::ContentType(vec![
ContentType::MediaType(MediaType::Application(MediaApplicationSubtype::Json)),
ContentType::Charset(CharSet::Iso88591),
]);
dbg!("content-Type: application/json; charset=ISO-8859-1".parse::<HttpHeader>()?);
assert_eq!(
header,
"content-Type: application/json; charset=ISO-8859-1".parse()?
);
Ok(())
}
}