httpio 0.2.4

A transport-agnostic, async HTTP/1.1 client library for any runtime.
Documentation
use crate::compression::{brotli_decode, gzip_decode, zlib_decode, zstd_decode};
use crate::enums::char_set::CharSet;
use crate::enums::content_encoding::ContentEncoding;
use crate::enums::http_error::HttpError;
use crate::enums::transfer_encoding::TransferEncoding;
use crate::structures::header::header_list::HttpHeaderList;
use crate::structures::http_multipart_body::HttpMultipartBody;
use crate::utils::parse_util;
use futures::AsyncBufRead;
use std::borrow::Cow;

use super::http_error::HttpErrorKind;

#[derive(Clone)]
pub enum HttpBody {
    None,
    Content(Vec<u8>),
    Multipart(HttpMultipartBody),
}

impl HttpBody {
    pub fn as_bytes(&self) -> Cow<'_, [u8]> {
        match self {
            HttpBody::Content(content) => Cow::Borrowed(content),
            HttpBody::Multipart(multipart) => Cow::Owned(multipart.as_bytes()),
            HttpBody::None => Cow::Owned(vec![]),
        }
    }

    pub fn get_multipart(&self) -> Option<&HttpMultipartBody> {
        match self {
            HttpBody::Content(_) => None,
            HttpBody::Multipart(multipart) => Some(multipart),
            HttpBody::None => None,
        }
    }

    pub fn get_content(&self) -> &[u8] {
        if let HttpBody::Content(content) = self {
            return content;
        }

        return &[];
    }

    pub async fn read<R: AsyncBufRead + Unpin>(
        mut reader: R,
        headers: &mut HttpHeaderList,
    ) -> Result<HttpBody, HttpError> {
        let mut data: Vec<u8> = vec![];
        let transfer_encodings = headers.get_transfer_encodings().to_vec();
        for encoding in transfer_encodings.into_iter().rev() {
            data = if data.is_empty() {
                Self::read_transferred_content(&mut reader, encoding, headers, None).await?
            } else {
                Self::read_transferred_content(
                    &mut (&data as &[u8]),
                    encoding,
                    headers,
                    Some(data.len() * 3),
                )
                .await?
            }
        }

        match headers.get_content_length() {
            Some(content_length) => {
                data = if data.is_empty() {
                    parse_util::read_content_with_known_length(&mut reader, content_length).await?
                } else {
                    parse_util::read_content_with_known_length(
                        &mut (&data as &[u8]),
                        content_length,
                    )
                    .await?
                };
            }
            _ => (),
        }

        let content_encodings = headers.get_content_encoding();
        for encoding in content_encodings.iter().rev().copied() {
            data = if data.is_empty() {
                Self::read_compressed_content(&mut reader, encoding, None).await?
            } else {
                Self::read_compressed_content(&mut (&data as &[u8]), encoding, Some(data.len() * 3))
                    .await?
            }
        }

        let (is_multipart, subtype, boundary) = headers.get_multipart_data();

        if is_multipart {
            let multipart_body = if data.is_empty() {
                HttpMultipartBody::read(reader, &boundary, &subtype).await?
            } else {
                HttpMultipartBody::read(&mut (&data as &[u8]), &boundary, &subtype).await?
            };
            return Ok(HttpBody::Multipart(multipart_body));
        } else {
            Ok(HttpBody::Content(data))
        }
    }

    async fn read_transferred_content(
        mut reader: impl AsyncBufRead + Unpin,
        encoding: TransferEncoding,
        headers: &mut HttpHeaderList,
        size_hint: Option<usize>,
    ) -> Result<Vec<u8>, HttpError> {
        let data = match encoding {
            TransferEncoding::Chunked => parse_util::read_chunked(&mut reader, headers).await?,
            TransferEncoding::Deflate => zlib_decode(&mut reader, size_hint).await?,
            TransferEncoding::Gzip => {
                let (_, result) = gzip_decode(reader).await?;
                result
            }
            _ => return Err(HttpErrorKind::Unimplemented.into()),
        };
        Ok(data)
    }

    async fn read_compressed_content<R: AsyncBufRead + Unpin>(
        mut reader: R,
        encoding: ContentEncoding,
        size_hint: Option<usize>,
    ) -> Result<Vec<u8>, HttpError> {
        let data = match encoding {
            ContentEncoding::Deflate => zlib_decode(&mut reader, size_hint).await?,
            ContentEncoding::Gzip => {
                let (_, result) = gzip_decode(reader).await?;
                result
            }
            ContentEncoding::Br => brotli_decode(&mut reader).await?,
            ContentEncoding::ZStandard => zstd_decode(&mut reader).await?,
            _ => return Err(HttpErrorKind::Unimplemented.into()),
        };
        Ok(data)
    }

    pub fn to_string(&self, charset: CharSet) -> Result<String, HttpError> {
        let bytes = self.as_bytes();
        let str = parse_util::bytes_to_string(&bytes, charset)?;
        Ok(str.to_string())
    }
}