awtrix3 0.0.2

Awtrix3 types and API (mqtt/http), from https://blueforcer.github.io/awtrix3/#/api
Documentation
use base64::prelude::*;
use reqwest::{header, Client};

use super::Awtrix3Error;

/// HTTP client for Awtrix3.
pub struct Awtrix3HttpClient {
    host: String,
    port: u16,
    username: Option<String>,
    password: Option<String>,
    client: Client,
}

impl Awtrix3HttpClient {
    /// Create a new HTTP client for Awtrix3.
    ///
    /// - `host` is the hostname or IP address of the Awtrix3 server.
    /// - `port` is the port of the Awtrix3 server.
    ///
    /// Returns the created client.
    pub fn new<S: Into<String>>(host: S, port: u16) -> Awtrix3HttpClient {
        Awtrix3HttpClient {
            host: host.into(),
            port,
            username: None,
            password: None,
            client: reqwest::Client::new(),
        }
    }

    /// Add basic authentication credentials to the client.
    ///
    /// - `username` is the username to use for authentication.
    /// - `password` is the password to use for authentication.
    ///
    /// Returns the client with the credentials added.
    pub fn with_credentials<S: Into<String>>(self, username: S, password: S) -> Self {
        let mut headers = header::HeaderMap::new();
        let username: String = username.into();
        let password: String = password.into();
        let mut auth_header_value = header::HeaderValue::from_str(&format!(
            "Basic {}",
            BASE64_STANDARD.encode(format!("{}:{}", username, password))
        ))
        .unwrap();
        auth_header_value.set_sensitive(true);
        headers.insert(header::AUTHORIZATION, auth_header_value);

        Self {
            client: reqwest::Client::builder()
                .default_headers(headers)
                .build()
                .unwrap(),
            username: Some(username),
            password: Some(password),
            ..self
        }
    }

    /// Perform a GET request to the given path.
    pub async fn get_request<T>(&self, path: &str) -> Result<T, Awtrix3Error>
    where
        T: serde::de::DeserializeOwned,
    {
        if !path.starts_with("/") {
            return Err(Awtrix3Error::InvalidPath(path.to_string()));
        }
        let resp = self.client.get(format!("{}{}", self.base_url(), path));
        let resp = resp.send().await?;
        if !resp.status().is_success() {
            return Err(Awtrix3Error::HttpError(resp.status()));
        }
        let resp = resp.json::<T>().await?;
        Ok(resp)
    }

    /// Perform a POST request to the given path with the given body, and return the response.
    pub async fn post_request_response<T, R>(&self, path: &str, body: T) -> Result<R, Awtrix3Error>
    where
        T: serde::Serialize,
        R: serde::de::DeserializeOwned,
    {
        if !path.starts_with("/") {
            return Err(Awtrix3Error::InvalidPath(path.to_string()));
        }
        let resp = self.client.post(format!("{}{}", self.base_url(), path));
        let resp = resp.json(&body);
        let resp = resp.send().await?;
        if !resp.status().is_success() {
            return Err(Awtrix3Error::HttpError(resp.status()));
        }
        if resp.content_length().unwrap_or(0) == 0 {
            return Ok(serde_json::from_str("null")?);
        }
        let resp = resp.json::<R>().await?;
        Ok(resp)
    }

    /// Perform a POST request to the given path with the given body.
    pub async fn post_request<T>(&self, path: &str, body: T) -> Result<(), Awtrix3Error>
    where
        T: serde::Serialize,
    {
        if !path.starts_with("/") {
            return Err(Awtrix3Error::InvalidPath(path.to_string()));
        }
        let resp = self.client.post(format!("{}{}", self.base_url(), path));
        let resp = resp.json(&body);
        let resp = resp.send().await?;
        if !resp.status().is_success() {
            return Err(Awtrix3Error::HttpError(resp.status()));
        }
        Ok(())
    }

    /// Perform a POST request to the given path with an empty body.
    pub async fn post_empty_request(&self, path: &str) -> Result<(), Awtrix3Error> {
        if !path.starts_with("/") {
            return Err(Awtrix3Error::InvalidPath(path.to_string()));
        }
        let resp = self.client.post(format!("{}{}", self.base_url(), path));
        let resp = resp.send().await?;
        if !resp.status().is_success() {
            return Err(Awtrix3Error::HttpError(resp.status()));
        }
        Ok(())
    }

    /// Is client authenticated?
    pub fn is_authenticated(&self) -> bool {
        self.username.is_some() && self.password.is_some()
    }

    /// Get the base URL of the client.
    pub fn base_url(&self) -> String {
        format!("http://{}:{}", self.host, self.port)
    }
}