wards 0.1.10

Библиотека для камеры Beward B2530RZQ-LP
Documentation
use std::{
    io::{Read, Write},
    net::{TcpStream, ToSocketAddrs},
    time::Duration,
};

use crate::errors::{
    WardError, ERROR_HTTP_CODE_MISSING, ERROR_HTTP_VERSION_MISSING, ERROR_IP_LOOKUP,
    ERROR_PARSING_HTTP_CODE, ERROR_SPLIT_HEADERS, ERROR_SPLIT_RAW,
};

const CONNECITON_TIMEOUT: u64 = 3000; // millis

pub struct Response {
    pub status_code: u16,
    pub body: String,
}

impl<'a> Response {
    /// Создает объект типа Response из строки ответа от сервера
    ///
    /// # Examples
    ///
    /// ```rust
    /// let data = "HTTP/1.0 200 OK\r\nContent-Type:text/plain\r\n\r\nResponse Body Here";
    ///
    /// let response: Response = Response::from_str(data).unwrap();
    ///
    /// assert_eq!(response.code, 200);
    /// assert_eq!(response.status, "OK");
    /// assert_eq!(response.content_type, "text/plain");
    /// assert_eq!(response.body, "Response Body Here");
    ///
    /// ```
    pub fn from_str(data: &'a str) -> Result<Self, WardError> {
        let (headers, body) = match data.split_once("\r\n\r\n") {
            Some((h, b)) => (h, b),
            None => return Err(WardError::HTTPError(ERROR_SPLIT_RAW.into())),
        };

        let (http_version_code_status, _content_type_line) = match headers.split_once("\r\n") {
            Some((s, ct)) => (s, ct),
            None => return Err(WardError::HTTPError(ERROR_SPLIT_HEADERS.to_string())),
        };

        let mut splitted_headers = http_version_code_status.splitn(3, " ");

        if let None = splitted_headers.next() {
            return Err(WardError::HTTPError(ERROR_HTTP_VERSION_MISSING.to_string()));
        };

        let status_code = match splitted_headers.next() {
            Some(c) => match c.parse::<u16>() {
                Ok(code) => Ok(code),
                Err(err) => Err(WardError::HTTPError(format!(
                    "{ERROR_PARSING_HTTP_CODE}: {err}"
                ))),
            }?,
            None => return Err(WardError::HTTPError(ERROR_HTTP_CODE_MISSING.to_string())),
        };

        let response = Response {
            status_code,
            body: body.to_owned(),
        };

        Ok(response)
    }

    /// Проверка статуса ответа, если статус не 200 возвращается ошибка
    pub(crate) fn check_status_code(&self) -> Result<(), WardError> {
        match self.status_code {
            200 => Ok(()),
            _ => return Err(WardError::HTTPError(self.body.trim().to_string())),
        }
    }
}

/// Открывает соединение с хостом. Возвращает ответ от сервера в виде строки.
///
/// Пример передаваемых параметров:
///
/// host: 127.0.0.1:8000
///
/// headers:
///
/// GET /status HTTP/1.1
///
/// Host: 127.0.0.1:8000
///
/// Authorization: Basic base64encode=
///
/// User-Agent: curl/7.88.1
pub(crate) fn send_request(host: &str, headers: &[u8]) -> Result<String, WardError> {
    let ip_lookup = host
        .to_socket_addrs()
        .map_err(|err| WardError::NetworkError(err.to_string()))?
        .next();

    if ip_lookup.is_none() {
        return Err(WardError::NetworkError(ERROR_IP_LOOKUP.to_string()));
    }

    let mut socket = TcpStream::connect_timeout(
        &ip_lookup.unwrap(),
        Duration::from_millis(CONNECITON_TIMEOUT),
    )
    .map_err(|err| WardError::NetworkError(err.to_string()))?;

    socket.write(headers).unwrap();
    socket.flush().unwrap();

    let mut buf = String::new();

    socket
        .read_to_string(&mut buf)
        .map_err(|err| WardError::NetworkError(err.to_string()))?;

    Ok(buf)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn response_from_str() {
        let data = "HTTP/1.0 200 OK\r\nContent-Type:text/plain\r\n\r\nResponse Body Here";

        let response = Response::from_str(data).unwrap();

        assert_eq!(response.status_code, 200);
        assert_eq!(response.body, "Response Body Here");
    }
}