rujira 0.4.1

This module provides an API for working with Jira
Documentation
//! This is module documentation for api
//!
//! A model for connecting to basic Jira's REST API.
//! A description of all available entry points can be
//! found on the [page](https://docs.atlassian.com/software/jira/docs/api/REST/latest/)
use std::env;

use reqwest::{
    header::{self, HeaderMap, HeaderName, HeaderValue},
    Method, StatusCode,
};
use tracing::error;

const REST_BASE: &str = "/rest/api/2";

use crate::{error::Error, Rujira};

pub mod issue;
pub mod myself;
pub mod project;

static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

/// This struct represents a request to Jira
#[derive(Default)]
pub struct Rq {
    bot: Rujira,
    uri: String,
    payload: serde_json::Value,
    method: Method,
    headers: HeaderMap,
    params: Vec<(String, String)>,
}

/// This struct represents a response from Jira
#[derive(Debug, Default, Clone)]
pub struct Rs {
    pub data: serde_json::Value,
    pub raw: String,
    pub status: StatusCode,
}

impl Rq {
    pub fn new(bot: Rujira) -> Self {
        Self {
            bot,
            method: Method::GET,
            ..Default::default()
        }
    }

    pub fn uri(self, uri: &str) -> Self {
        let uri = uri.to_owned();
        Self { uri, ..self }
    }

    pub fn method(self, method: Method) -> Self {
        Self { method, ..self }
    }

    pub fn add_header(mut self, header: &[u8], value: &str) -> Self {
        let Ok(hdr) = HeaderName::from_bytes(header) else {
            tracing::error!(
                header = ?String::from_utf8_lossy(header),
                "Invalid header name"
            );
            return self;
        };
        let val = HeaderValue::from_str(value).unwrap();
        self.headers.insert(hdr, val);
        self
    }

    pub fn add_params(mut self, params: Vec<(&str, &str)>) -> Self {
        let params = params
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect();
        self.params = params;
        self
    }

    pub fn load_payload(mut self, value: serde_json::Value) -> Self {
        for (key, value) in value.as_object().unwrap() {
            self.payload[key] = value.clone();
        }
        self
    }

    pub fn add_payload(mut self, key: &str, value: serde_json::Value) -> Self {
        self.payload[key] = value;
        self
    }

    pub fn apply_if<T, F>(self, val: Option<T>, fun: F) -> Self
    where
        Self: Sized,
        F: FnOnce(Self, T) -> Self,
    {
        if let Some(val) = val {
            fun(self, val)
        } else {
            self
        }
    }

    pub async fn apply(self) -> Result<Rs, Error> {
        let mut headers = header::HeaderMap::new();
        let hdr = format!("Bearer {}", self.bot.token);
        let mut auth_value = header::HeaderValue::from_str(&hdr).map_err(Error::InvalidHeader)?;
        auth_value.set_sensitive(true);
        headers.insert(header::AUTHORIZATION, auth_value);
        let client = reqwest::Client::builder()
            .user_agent(APP_USER_AGENT)
            .default_headers(headers)
            .build()
            .map_err(Error::AnyReqwestError)?;
        let url = format!("{}{}", self.bot.url.as_ref(), self.uri);
        let req = match self.method {
            Method::GET => client.get(url),
            Method::POST => client.post(url),
            Method::DELETE => client.delete(url),
            Method::PUT => client.put(url),
            _ => return Err(Error::UnsupportedMethod),
        };
        let req = req
            .headers(self.headers)
            .query(&self.params)
            .json(&self.payload);
        let res = req.send().await.map_err(Error::AnyReqwestError)?;
        if !res.status().is_success() {
            error!(?res, code = ?res.status());
            let raw = res.text().await.map_err(Error::AnyReqwestError)?;
            return Ok(Rs {
                raw,
                ..Default::default()
            });
        }
        let data = res.text().await.map_err(Error::AnyReqwestError)?;
        let Ok(data) = serde_json::from_str(&data) else {
            return Ok(Rs {
                raw: data,
                ..Default::default()
            });
        };
        Ok(Rs {
            data,
            ..Default::default()
        })
    }
}