155 lines
4 KiB
Rust
155 lines
4 KiB
Rust
pub mod commands;
|
|
|
|
use http::header::CONTENT_TYPE;
|
|
use http::{HeaderMap, HeaderValue};
|
|
use serde::de::DeserializeOwned;
|
|
use serde::Serialize;
|
|
use std::fmt::Debug;
|
|
use thiserror::Error;
|
|
use url::{ParseError, Url};
|
|
|
|
pub struct JiraClient {
|
|
pub url: String,
|
|
pub email: String,
|
|
pub api_token: String,
|
|
}
|
|
|
|
pub trait JiraCommand {
|
|
type TResponse: DeserializeOwned + Clone;
|
|
type TPayload: Serialize;
|
|
const REQUEST_TYPE: JiraRequestType;
|
|
fn endpoint(&self) -> String;
|
|
fn request_body(&self) -> Option<&Self::TPayload>;
|
|
fn query_params(&self) -> Option<Vec<(String, String)>>;
|
|
}
|
|
|
|
pub enum JiraRequestType {
|
|
Read,
|
|
Create,
|
|
Update,
|
|
Delete,
|
|
}
|
|
|
|
impl From<JiraRequestType> for http::Method {
|
|
fn from(value: JiraRequestType) -> Self {
|
|
match value {
|
|
JiraRequestType::Read => Self::GET,
|
|
JiraRequestType::Create => Self::POST,
|
|
JiraRequestType::Update => Self::PATCH,
|
|
JiraRequestType::Delete => Self::DELETE,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum JiraClientError {
|
|
#[error(transparent)]
|
|
UrlParseError(#[from] ParseError),
|
|
#[error(transparent)]
|
|
ReqwestError(#[from] reqwest::Error),
|
|
#[error(transparent)]
|
|
SerdeError(#[from] serde_json::Error),
|
|
#[error("API Error: {0}")]
|
|
ApiError(String),
|
|
}
|
|
|
|
impl JiraClient {
|
|
pub fn new(url: &str, email: &str, api_token: &str) -> Self {
|
|
Self {
|
|
url: url.to_string(),
|
|
email: email.to_string(),
|
|
api_token: api_token.to_string(),
|
|
}
|
|
}
|
|
|
|
pub async fn exec<TCommand>(
|
|
&self,
|
|
cmd: TCommand,
|
|
) -> Result<TCommand::TResponse, JiraClientError>
|
|
where
|
|
TCommand: JiraCommand + Debug,
|
|
{
|
|
let client = reqwest::Client::new();
|
|
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
|
|
let url = self.construct_url(&cmd.endpoint())?;
|
|
|
|
let mut request_builder = client
|
|
.request(TCommand::REQUEST_TYPE.into(), url)
|
|
.basic_auth(&self.email, Some(&self.api_token))
|
|
.headers(headers);
|
|
|
|
if let Some(params) = cmd.query_params() {
|
|
request_builder = request_builder.query(¶ms);
|
|
}
|
|
|
|
if let Some(body) = cmd.request_body() {
|
|
request_builder = request_builder.json(body);
|
|
}
|
|
|
|
let response = match request_builder.send().await {
|
|
Ok(x) => x,
|
|
Err(reason) => return Err(JiraClientError::ReqwestError(reason)),
|
|
};
|
|
|
|
if !response.status().is_success() {
|
|
let error_text = response.text().await?;
|
|
return Err(JiraClientError::ApiError(error_text));
|
|
}
|
|
|
|
let plain = match response.bytes().await {
|
|
Ok(x) => x,
|
|
Err(reason) => return Err(JiraClientError::ReqwestError(reason)),
|
|
};
|
|
|
|
let value = match is_unit::<TCommand::TResponse>() {
|
|
true => b"null",
|
|
false => plain.as_ref(),
|
|
};
|
|
|
|
match serde_json::from_slice::<TCommand::TResponse>(value) {
|
|
Ok(x) => Ok(x),
|
|
Err(reason) => Err(JiraClientError::SerdeError(reason)),
|
|
}
|
|
}
|
|
|
|
fn construct_url(&self, endpoint: &str) -> Result<String, JiraClientError> {
|
|
let mut url = match Url::parse(&self.url) {
|
|
Ok(x) => x,
|
|
Err(reason) => {
|
|
return Err(JiraClientError::UrlParseError(reason));
|
|
}
|
|
};
|
|
|
|
url.set_path(endpoint);
|
|
|
|
Ok(url.to_string())
|
|
}
|
|
}
|
|
|
|
const fn is_unit<T>() -> bool {
|
|
size_of::<T>() == 0 && align_of::<T>() == 1
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_is_unit_type() {
|
|
assert!(is_unit::<()>());
|
|
assert!(!is_unit::<String>());
|
|
}
|
|
|
|
#[test]
|
|
fn deserialize_unit_type() {
|
|
let value = match is_unit::<()>() {
|
|
true => b"null".as_ref(),
|
|
false => b"{}".as_ref(),
|
|
};
|
|
|
|
serde_json::from_slice::<()>(value).unwrap();
|
|
}
|
|
}
|