rujira/
api.rs

1//! This is module documentation for api
2//!
3//! A model for connecting to basic Jira's REST API.
4//! A description of all available entry points can be
5//! found on the [page](https://docs.atlassian.com/software/jira/docs/api/REST/latest/)
6use std::env;
7
8use reqwest::{
9    header::{self, HeaderMap, HeaderName, HeaderValue},
10    Method, StatusCode,
11};
12use tracing::error;
13
14const REST_BASE: &str = "/rest/api/2";
15
16use crate::{error::Error, Rujira};
17
18pub mod issue;
19pub mod myself;
20pub mod project;
21
22static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
23
24/// This struct represents a request to Jira
25#[derive(Default)]
26pub struct Rq {
27    bot: Rujira,
28    uri: String,
29    payload: serde_json::Value,
30    method: Method,
31    headers: HeaderMap,
32    params: Vec<(String, String)>,
33}
34
35/// This struct represents a response from Jira
36#[derive(Debug, Default, Clone)]
37pub struct Rs {
38    pub data: serde_json::Value,
39    pub raw: String,
40    pub status: StatusCode,
41}
42
43impl Rq {
44    pub fn new(bot: Rujira) -> Self {
45        Self {
46            bot,
47            method: Method::GET,
48            ..Default::default()
49        }
50    }
51
52    pub fn uri(self, uri: &str) -> Self {
53        let uri = uri.to_owned();
54        Self { uri, ..self }
55    }
56
57    pub fn method(self, method: Method) -> Self {
58        Self { method, ..self }
59    }
60
61    pub fn add_header(mut self, header: &[u8], value: &str) -> Self {
62        let Ok(hdr) = HeaderName::from_bytes(header) else {
63            tracing::error!(
64                header = ?String::from_utf8_lossy(header),
65                "Invalid header name"
66            );
67            return self;
68        };
69        let val = HeaderValue::from_str(value).unwrap();
70        self.headers.insert(hdr, val);
71        self
72    }
73
74    pub fn add_params(mut self, params: Vec<(&str, &str)>) -> Self {
75        let params = params
76            .iter()
77            .map(|(k, v)| (k.to_string(), v.to_string()))
78            .collect();
79        self.params = params;
80        self
81    }
82
83    pub fn load_payload(mut self, value: serde_json::Value) -> Self {
84        for (key, value) in value.as_object().unwrap() {
85            self.payload[key] = value.clone();
86        }
87        self
88    }
89
90    pub fn add_payload(mut self, key: &str, value: serde_json::Value) -> Self {
91        self.payload[key] = value;
92        self
93    }
94
95    pub fn apply_if<T, F>(self, val: Option<T>, fun: F) -> Self
96    where
97        Self: Sized,
98        F: FnOnce(Self, T) -> Self,
99    {
100        if let Some(val) = val {
101            fun(self, val)
102        } else {
103            self
104        }
105    }
106
107    pub async fn apply(self) -> Result<Rs, Error> {
108        let mut headers = header::HeaderMap::new();
109        let hdr = format!("Bearer {}", self.bot.token);
110        let mut auth_value = header::HeaderValue::from_str(&hdr).map_err(Error::InvalidHeader)?;
111        auth_value.set_sensitive(true);
112        headers.insert(header::AUTHORIZATION, auth_value);
113        let client = reqwest::Client::builder()
114            .user_agent(APP_USER_AGENT)
115            .default_headers(headers)
116            .build()
117            .map_err(Error::AnyReqwestError)?;
118        let url = format!("{}{}", self.bot.url.as_ref(), self.uri);
119        let req = match self.method {
120            Method::GET => client.get(url),
121            Method::POST => client.post(url),
122            Method::DELETE => client.delete(url),
123            Method::PUT => client.put(url),
124            _ => return Err(Error::UnsupportedMethod),
125        };
126        let req = req
127            .headers(self.headers)
128            .query(&self.params)
129            .json(&self.payload);
130        let res = req.send().await.map_err(Error::AnyReqwestError)?;
131        if !res.status().is_success() {
132            error!(?res, code = ?res.status());
133            let raw = res.text().await.map_err(Error::AnyReqwestError)?;
134            return Ok(Rs {
135                raw,
136                ..Default::default()
137            });
138        }
139        let data = res.text().await.map_err(Error::AnyReqwestError)?;
140        let Ok(data) = serde_json::from_str(&data) else {
141            return Ok(Rs {
142                raw: data,
143                ..Default::default()
144            });
145        };
146        Ok(Rs {
147            data,
148            ..Default::default()
149        })
150    }
151}