async-fetch 0.4.0

Asynchronous HTTP client.
Documentation
use std::fmt;
use std::pin::Pin;
use std::collections::HashMap;
use std::collections::hash_map::RandomState;
use std::io::{Error, ErrorKind};
use std::str::FromStr;
use async_std::io::{Read};
use async_httplib::{Status, Version, read_exact, read_chunks};
use crate::{read_content_length, read_transfer_encoding};

pub struct Response<'a> {
    status: Status,
    version: Version,
    headers: HashMap<String, String>,
    reader: Pin<Box<dyn Read + Send + Unpin + 'a>>,
    chunkline_limit: Option<usize>,
    body_limit: Option<usize>,
}

impl<'a> Response<'a> {

    pub fn default() -> Self {
        Self {
            status: Status::Ok,
            version: Version::Http1_1,
            headers: HashMap::with_hasher(RandomState::new()),
            reader: Box::pin("".as_bytes()),
            chunkline_limit: None,
            body_limit: None,
        }
    }

    pub fn with_reader<R>(reader: R) -> Self
        where
        R: Read + Send + Unpin + 'a,
    {
        let mut res = Self::default();
        res.set_reader(reader);
        res
    }

    pub fn status(&self) -> &Status {
        &self.status
    }

    pub fn version(&self) -> &Version {
        &self.version
    }

    pub fn headers(&self) -> &HashMap<String, String> {
        &self.headers
    }

    pub fn header<N: Into<String>>(&self, name: N) -> Option<&String> {
        self.headers.get(&name.into())
    }

    pub fn reader(&self) -> &Pin<Box<dyn Read + Send + Unpin + 'a>> {
        &self.reader
    }

    pub fn chunkline_limit(&self) -> &Option<usize> {
        &self.chunkline_limit
    }

    pub fn body_limit(&self) -> &Option<usize> {
        &self.body_limit
    }

    pub fn has_status(&self, value: Status) -> bool {
        self.status == value
    }

    pub fn has_version(&self, value: Version) -> bool {
        self.version == value
    }

    pub fn has_headers(&self) -> bool {
        !self.headers.is_empty()
    }

    pub fn has_header<N: Into<String>>(&self, name: N) -> bool {
        self.headers.contains_key(&name.into())
    }

    pub fn has_chunkline_limit(&self) -> bool {
        self.chunkline_limit.is_some()
    }

    pub fn has_body_limit(&self) -> bool {
        self.body_limit.is_some()
    }

    pub fn set_status(&mut self, value: Status) {
        self.status = value;
    }

    pub fn set_status_str(&mut self, value: &str) -> Result<(), Error> {
        self.status = Status::from_str(value)?;
        Ok(())
    }

    pub fn set_version(&mut self, value: Version) {
        self.version = value;
    }

    pub fn set_version_str(&mut self, value: &str) -> Result<(), Error> {
        self.version = Version::from_str(value)?;
        Ok(())
    }

    pub fn set_header<N: Into<String>, V: Into<String>>(&mut self, name: N, value: V) {
        self.headers.insert(name.into(), value.into());
    }

    pub fn set_reader<R>(&mut self, reader: R)
        where
        R: Read + Send + Unpin + 'a,
    {
        self.reader = Box::pin(reader);
    }

    pub fn set_chunkline_limit(&mut self, length: usize) {
        self.chunkline_limit = Some(length);
    }

    pub fn set_body_limit(&mut self, length: usize) {
        self.body_limit = Some(length);
    }

    pub fn remove_header<N: Into<String>>(&mut self, name: N) {
        self.headers.remove(&name.into());
    }

    pub fn clear_headers(&mut self) {
        self.headers.clear();
    }

    pub fn to_proto_string(&self) -> String {
        let mut output = String::new();
        if !self.has_version(Version::Http0_9) {
            output.push_str(&format!("{} {} {}\r\n", self.version, self.status, self.status.reason()));

            for (name, value) in self.headers.iter() {
                output.push_str(&format!("{}: {}\r\n", name, value));
            }

            output.push_str("\r\n");
        }
        output
    }

    pub async fn recv(&mut self) -> Result<Vec<u8>, Error> {
        let mut data = Vec::new();

        if read_transfer_encoding(&self.headers) == "chunked" {
            read_chunks(&mut self.reader, &mut data, (self.chunkline_limit, self.body_limit)).await?;
        } else if self.has_header("Content-Length") {
            let length = read_content_length(&self.headers, self.body_limit)?;
            read_exact(&mut self.reader, &mut data, length).await?;
        }

        Ok(data)
    }

    pub async fn recv_string(&mut self) -> Result<String, Error> {
        let data = self.recv().await?;
        let txt = match String::from_utf8(data) {
            Ok(txt) => txt,
            Err(e) => return Err(Error::new(ErrorKind::InvalidData, e.to_string())),
        };
        Ok(txt)
    }

    #[cfg(feature = "json")]
    pub async fn recv_json(&mut self) -> Result<serde_json::Value, Error> {
        let mut data = self.recv().await?;
        if data.is_empty() {
            data = "{}".as_bytes().to_vec();
        }
        let json: serde_json::Value = match serde_json::from_slice(&data) {
            Ok(json) => json,
            Err(e) => return Err(Error::new(ErrorKind::InvalidData, e.to_string())),
        };
        Ok(json)
    }
}

impl fmt::Display for Response<'_> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", self.to_proto_string())
    }
}

impl From<Response<'_>> for String {
    fn from(item: Response) -> String {
        item.to_proto_string()
    }
}