use derive_builder::Builder;
use reqwest::Method;
use std::borrow::Cow;
use crate::api::issues::AssigneeEssentials;
use crate::api::projects::ProjectEssentials;
use crate::api::{Endpoint, ReturnsJsonResponse};
use serde::Serialize;
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct IssueCategoryEssentials {
pub id: u64,
pub name: String,
}
impl From<IssueCategory> for IssueCategoryEssentials {
fn from(v: IssueCategory) -> Self {
IssueCategoryEssentials {
id: v.id,
name: v.name,
}
}
}
impl From<&IssueCategory> for IssueCategoryEssentials {
fn from(v: &IssueCategory) -> Self {
IssueCategoryEssentials {
id: v.id,
name: v.name.to_owned(),
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct IssueCategory {
pub id: u64,
pub name: String,
pub project: ProjectEssentials,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_to: Option<AssigneeEssentials>,
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct ListIssueCategories<'a> {
#[builder(setter(into))]
project_id_or_name: Cow<'a, str>,
}
impl<'a> ReturnsJsonResponse for ListIssueCategories<'a> {}
impl<'a> ListIssueCategories<'a> {
#[must_use]
pub fn builder() -> ListIssueCategoriesBuilder<'a> {
ListIssueCategoriesBuilder::default()
}
}
impl<'a> Endpoint for ListIssueCategories<'a> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/issue_categories.json", self.project_id_or_name).into()
}
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct GetIssueCategory {
id: u64,
}
impl ReturnsJsonResponse for GetIssueCategory {}
impl GetIssueCategory {
#[must_use]
pub fn builder() -> GetIssueCategoryBuilder {
GetIssueCategoryBuilder::default()
}
}
impl Endpoint for GetIssueCategory {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
format!("issue_categories/{}.json", &self.id).into()
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Builder, Serialize)]
#[builder(setter(strip_option))]
pub struct CreateIssueCategory<'a> {
#[serde(skip_serializing)]
#[builder(setter(into))]
project_id_or_name: Cow<'a, str>,
#[builder(setter(into))]
name: Cow<'a, str>,
#[builder(default)]
assigned_to_id: Option<u64>,
}
impl<'a> ReturnsJsonResponse for CreateIssueCategory<'a> {}
impl<'a> CreateIssueCategory<'a> {
#[must_use]
pub fn builder() -> CreateIssueCategoryBuilder<'a> {
CreateIssueCategoryBuilder::default()
}
}
impl<'a> Endpoint for CreateIssueCategory<'a> {
fn method(&self) -> Method {
Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/issue_categories.json", self.project_id_or_name).into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
Ok(Some((
"application/json",
serde_json::to_vec(&IssueCategoryWrapper::<CreateIssueCategory> {
issue_category: (*self).to_owned(),
})?,
)))
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Builder, Serialize)]
#[builder(setter(strip_option))]
pub struct UpdateIssueCategory<'a> {
#[serde(skip_serializing)]
id: u64,
#[builder(setter(into), default)]
name: Option<Cow<'a, str>>,
#[builder(default)]
assigned_to_id: Option<u64>,
}
impl<'a> UpdateIssueCategory<'a> {
#[must_use]
pub fn builder() -> UpdateIssueCategoryBuilder<'a> {
UpdateIssueCategoryBuilder::default()
}
}
impl<'a> Endpoint for UpdateIssueCategory<'a> {
fn method(&self) -> Method {
Method::PUT
}
fn endpoint(&self) -> Cow<'static, str> {
format!("issue_categories/{}.json", self.id).into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
Ok(Some((
"application/json",
serde_json::to_vec(&IssueCategoryWrapper::<UpdateIssueCategory> {
issue_category: (*self).to_owned(),
})?,
)))
}
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct DeleteIssueCategory {
id: u64,
}
impl DeleteIssueCategory {
#[must_use]
pub fn builder() -> DeleteIssueCategoryBuilder {
DeleteIssueCategoryBuilder::default()
}
}
impl Endpoint for DeleteIssueCategory {
fn method(&self) -> Method {
Method::DELETE
}
fn endpoint(&self) -> Cow<'static, str> {
format!("issue_categories/{}.json", &self.id).into()
}
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct IssueCategoriesWrapper<T> {
pub issue_categories: Vec<T>,
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct IssueCategoryWrapper<T> {
pub issue_category: T,
}
#[cfg(test)]
mod test {
use super::*;
use crate::api::test_helpers::with_project;
use parking_lot::{const_rwlock, RwLock};
use pretty_assertions::assert_eq;
use std::error::Error;
use tracing_test::traced_test;
static ISSUE_CATEGORY_LOCK: RwLock<()> = const_rwlock(());
#[traced_test]
#[test]
fn test_list_issue_categories_no_pagination() -> Result<(), Box<dyn Error>> {
let _r_issue_category = ISSUE_CATEGORY_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListIssueCategories::builder()
.project_id_or_name("336")
.build()?;
redmine.json_response_body::<_, IssueCategoriesWrapper<IssueCategory>>(&endpoint)?;
Ok(())
}
#[traced_test]
#[test]
fn test_get_issue_category() -> Result<(), Box<dyn Error>> {
let _r_issue_category = ISSUE_CATEGORY_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = GetIssueCategory::builder().id(10).build()?;
redmine.json_response_body::<_, IssueCategoryWrapper<IssueCategory>>(&endpoint)?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_create_issue_category() -> Result<(), Box<dyn Error>> {
let _w_issue_category = ISSUE_CATEGORY_LOCK.write();
let name = format!("unittest_{}", function_name!());
with_project(&name, |redmine, _id, name| {
let create_endpoint = super::CreateIssueCategory::builder()
.project_id_or_name(name)
.name("Unittest Issue Category")
.build()?;
redmine.ignore_response_body::<_>(&create_endpoint)?;
Ok(())
})?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_update_issue_category() -> Result<(), Box<dyn Error>> {
let _w_issue_category = ISSUE_CATEGORY_LOCK.write();
let name = format!("unittest_{}", function_name!());
with_project(&name, |redmine, _id, name| {
let create_endpoint = super::CreateIssueCategory::builder()
.project_id_or_name(name)
.name("Unittest Issue Category")
.build()?;
let IssueCategoryWrapper { issue_category }: IssueCategoryWrapper<IssueCategory> =
redmine.json_response_body::<_, _>(&create_endpoint)?;
let id = issue_category.id;
let update_endpoint = super::UpdateIssueCategory::builder()
.id(id)
.name("Renamed Unit-Test name")
.build()?;
redmine.ignore_response_body::<_>(&update_endpoint)?;
Ok(())
})?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_delete_issue_category() -> Result<(), Box<dyn Error>> {
let _w_issue_category = ISSUE_CATEGORY_LOCK.write();
let name = format!("unittest_{}", function_name!());
with_project(&name, |redmine, _id, name| {
let create_endpoint = super::CreateIssueCategory::builder()
.project_id_or_name(name)
.name("Unittest Issue Category")
.build()?;
let IssueCategoryWrapper { issue_category }: IssueCategoryWrapper<IssueCategory> =
redmine.json_response_body::<_, _>(&create_endpoint)?;
let id = issue_category.id;
let delete_endpoint = super::DeleteIssueCategory::builder().id(id).build()?;
redmine.ignore_response_body::<_>(&delete_endpoint)?;
Ok(())
})?;
Ok(())
}
#[traced_test]
#[test]
fn test_completeness_issue_category_type() -> Result<(), Box<dyn Error>> {
let _r_issue_category = ISSUE_CATEGORY_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListIssueCategories::builder()
.project_id_or_name("336")
.build()?;
let IssueCategoriesWrapper {
issue_categories: values,
} = redmine
.json_response_body::<_, IssueCategoriesWrapper<serde_json::Value>>(&endpoint)?;
for value in values {
let o: IssueCategory = serde_json::from_value(value.clone())?;
let reserialized = serde_json::to_value(o)?;
assert_eq!(value, reserialized);
}
Ok(())
}
}