httpio 0.2.4

A transport-agnostic, async HTTP/1.1 client library for any runtime.
Documentation
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(())
    }
}