disma 0.16.4

Discord server management has never been easier!
Documentation
#![allow(dead_code)]

use std::{error::Error, fmt::Display};

use reqwest::{
    header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
    Method, StatusCode,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;

#[derive(Clone, Default)]
pub struct Client {
    base_url: String,
    base_headers: HeaderMap,
}

impl Client {
    pub fn new() -> Self {
        Self {
            base_url: String::new(),
            base_headers: HeaderMap::new(),
        }
    }

    pub fn base_url(mut self, url: &str) -> Self {
        self.base_url = url.to_string();
        self
    }

    pub fn header(mut self, key: HeaderName, value: &str) -> Self {
        self.base_headers
            .insert(key, HeaderValue::from_str(value).unwrap());
        self
    }

    pub fn get(self, url: &str) -> Request {
        self.request(Method::GET, url)
    }

    pub fn post(self, url: &str) -> Request {
        self.request(Method::POST, url)
    }

    pub fn patch(self, url: &str) -> Request {
        self.request(Method::PATCH, url)
    }

    pub fn put(self, url: &str) -> Request {
        self.request(Method::PUT, url)
    }

    pub fn delete(self, url: &str) -> Request {
        self.request(Method::DELETE, url)
    }

    pub fn request(self, method: Method, url: &str) -> Request {
        Request::new(method, &self.full_url(url)).headers(&self.base_headers)
    }

    fn full_url(&self, url_suffix: &str) -> String {
        if self.base_url.is_empty() {
            url_suffix.to_string()
        } else {
            format!("{}{}", &self.base_url, &url_suffix)
        }
    }
}

#[derive(Debug)]
pub enum HttpError {
    SendingRequest(String),
    ParsingTextResponse(String),
    SendingJsonPayload(String),
}

impl Display for HttpError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::SendingRequest(description) => {
                f.write_str(&format!("Could not send request. Error: {description}"))
            }
            Self::SendingJsonPayload(description) => f.write_str(&format!(
                "Error occured while serializing json body. Error: {description}"
            )),
            Self::ParsingTextResponse(description) => f.write_str(&format!(
                "Could not fetch response text content. Error: {description}"
            )),
        }
    }
}

impl Error for HttpError {}

#[derive(Debug)]
pub struct Request {
    method: Method,
    url: String,
    headers: HeaderMap,
    body: Option<String>,
}

impl Request {
    pub fn new(method: Method, url: &str) -> Self {
        Self {
            method,
            url: url.to_string(),
            headers: HeaderMap::new(),
            body: None,
        }
    }

    pub fn get(url: &str) -> Self {
        Self::new(Method::GET, url)
    }

    pub fn post(url: &str) -> Self {
        Self::new(Method::POST, url)
    }

    pub fn patch(url: &str) -> Self {
        Self::new(Method::PATCH, url)
    }

    pub fn put(url: &str) -> Self {
        Self::new(Method::PUT, url)
    }

    pub fn delete(url: &str) -> Self {
        Self::new(Method::DELETE, url)
    }

    pub fn header(mut self, key: HeaderName, value: &str) -> Self {
        self.headers
            .insert(key, HeaderValue::from_str(value).unwrap());
        self
    }

    pub fn headers(mut self, headers: &HeaderMap) -> Self {
        self.headers = headers.clone();
        self
    }

    pub fn json_body<T: Serialize>(mut self, body: T) -> Result<Self, HttpError> {
        let json_body = serde_json::to_string(&body)
            .map_err(|error| HttpError::SendingJsonPayload(error.to_string()))?;

        self.body = Some(json_body);
        Ok(self.header(CONTENT_TYPE, "application/json"))
    }

    pub fn send(self) -> Result<Response, HttpError> {
        let client = reqwest::blocking::Client::new();
        let mut request = client
            .request(self.method.clone(), self.url.clone())
            .headers(self.headers.clone());

        request = match &self.body {
            Some(body) => request.body(body.clone()),
            None => request,
        };

        let http_response = request
            .send()
            .map_err(|error| HttpError::SendingRequest(error.to_string()))?;

        let status = http_response.status();
        let text_content = http_response
            .text()
            .map_err(|error| HttpError::ParsingTextResponse(error.to_string()))?;

        let response = Response::new(self, status, &text_content);
        Ok(response)
    }
}

#[derive(Debug)]
pub struct Response {
    pub request: Request,
    pub status: StatusCode,
    pub content: String,
}

impl Response {
    pub fn new(request: Request, status: StatusCode, content: &str) -> Self {
        Self {
            request,
            status,
            content: content.to_string(),
        }
    }

    pub fn text_body(&self) -> &str {
        &self.content
    }

    pub fn json_body(&self) -> Result<Value, String> {
        serde_json::from_str(&self.content).map_err(|error| {
            format!(
                "Could not parse response body to json.\nBody: {}\nError: {}",
                &self.content, error
            )
        })
    }

    pub fn parsed_body<T: DeserializeOwned>(&self) -> Result<T, String> {
        serde_json::from_str(&self.content).map_err(|error| {
            format!(
                "Could not parse response body to specified type.\nBody: {}\nError: {}",
                &self.content, error
            )
        })
    }
}