use derive_builder::Builder;
use http::Method;
use std::borrow::Cow;
use crate::api::groups::GroupEssentials;
use crate::api::projects::ProjectEssentials;
use crate::api::roles::RoleEssentials;
use crate::api::users::UserEssentials;
use crate::api::{Endpoint, Pageable, ReturnsJsonResponse};
use serde::Serialize;
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize, Clone)]
pub struct UserProjectMembership {
pub id: u64,
pub project: ProjectEssentials,
pub roles: Vec<RoleEssentials>,
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct GroupProjectMembership {
pub id: u64,
pub project: ProjectEssentials,
pub roles: Vec<RoleEssentials>,
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct ProjectMembership {
pub id: u64,
pub project: ProjectEssentials,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user: Option<UserEssentials>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub group: Option<GroupEssentials>,
pub roles: Vec<RoleEssentials>,
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct ListProjectMemberships<'a> {
#[builder(setter(into))]
project_id_or_name: Cow<'a, str>,
}
impl<'a> ReturnsJsonResponse for ListProjectMemberships<'a> {}
impl<'a> Pageable for ListProjectMemberships<'a> {
fn response_wrapper_key(&self) -> String {
"memberships".to_string()
}
}
impl<'a> ListProjectMemberships<'a> {
#[must_use]
pub fn builder() -> ListProjectMembershipsBuilder<'a> {
ListProjectMembershipsBuilder::default()
}
}
impl<'a> Endpoint for ListProjectMemberships<'a> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/memberships.json", self.project_id_or_name).into()
}
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct GetProjectMembership {
id: u64,
}
impl ReturnsJsonResponse for GetProjectMembership {}
impl GetProjectMembership {
#[must_use]
pub fn builder() -> GetProjectMembershipBuilder {
GetProjectMembershipBuilder::default()
}
}
impl Endpoint for GetProjectMembership {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
format!("memberships/{}.json", &self.id).into()
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Builder, Serialize)]
#[builder(setter(strip_option))]
pub struct CreateProjectMembership<'a> {
#[builder(setter(into))]
#[serde(skip_serializing)]
project_id_or_name: Cow<'a, str>,
user_id: u64,
role_ids: Vec<u64>,
}
impl<'a> ReturnsJsonResponse for CreateProjectMembership<'a> {}
impl<'a> CreateProjectMembership<'a> {
#[must_use]
pub fn builder() -> CreateProjectMembershipBuilder<'a> {
CreateProjectMembershipBuilder::default()
}
}
impl<'a> Endpoint for CreateProjectMembership<'a> {
fn method(&self) -> Method {
Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/memberships.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(&MembershipWrapper::<CreateProjectMembership> {
membership: (*self).to_owned(),
})?,
)))
}
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, Builder, Serialize)]
#[builder(setter(strip_option))]
pub struct UpdateProjectMembership {
#[serde(skip_serializing)]
id: u64,
role_ids: Vec<u64>,
}
impl UpdateProjectMembership {
#[must_use]
pub fn builder() -> UpdateProjectMembershipBuilder {
UpdateProjectMembershipBuilder::default()
}
}
impl Endpoint for UpdateProjectMembership {
fn method(&self) -> Method {
Method::PUT
}
fn endpoint(&self) -> Cow<'static, str> {
format!("memberships/{}.json", self.id).into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
Ok(Some((
"application/json",
serde_json::to_vec(&MembershipWrapper::<UpdateProjectMembership> {
membership: (*self).to_owned(),
})?,
)))
}
}
#[derive(Debug, Builder)]
#[builder(setter(strip_option))]
pub struct DeleteProjectMembership {
id: u64,
}
impl DeleteProjectMembership {
#[must_use]
pub fn builder() -> DeleteProjectMembershipBuilder {
DeleteProjectMembershipBuilder::default()
}
}
impl Endpoint for DeleteProjectMembership {
fn method(&self) -> Method {
Method::DELETE
}
fn endpoint(&self) -> Cow<'static, str> {
format!("memberships/{}.json", &self.id).into()
}
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct MembershipsWrapper<T> {
pub memberships: Vec<T>,
}
#[derive(Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct MembershipWrapper<T> {
pub membership: 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 PROJECT_MEMBERSHIP_LOCK: RwLock<()> = const_rwlock(());
#[traced_test]
#[test]
fn test_list_project_memberships_no_pagination() -> Result<(), Box<dyn Error>> {
let _r_project_memberships = PROJECT_MEMBERSHIP_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListProjectMemberships::builder()
.project_id_or_name("sandbox")
.build()?;
redmine.json_response_body::<_, MembershipsWrapper<ProjectMembership>>(&endpoint)?;
Ok(())
}
#[traced_test]
#[test]
fn test_list_project_memberships_first_page() -> Result<(), Box<dyn Error>> {
let _r_project_memberships = PROJECT_MEMBERSHIP_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListProjectMemberships::builder()
.project_id_or_name("sandbox")
.build()?;
redmine.json_response_body_page::<_, ProjectMembership>(&endpoint, 0, 25)?;
Ok(())
}
#[traced_test]
#[test]
fn test_list_project_memberships_all_pages() -> Result<(), Box<dyn Error>> {
let _r_project_memberships = PROJECT_MEMBERSHIP_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListProjectMemberships::builder()
.project_id_or_name("sandbox")
.build()?;
redmine.json_response_body_all_pages::<_, ProjectMembership>(&endpoint)?;
Ok(())
}
#[traced_test]
#[test]
fn test_get_project_membership() -> Result<(), Box<dyn Error>> {
let _r_project_memberships = PROJECT_MEMBERSHIP_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = GetProjectMembership::builder().id(238).build()?;
redmine.json_response_body::<_, MembershipWrapper<ProjectMembership>>(&endpoint)?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_create_project_membership() -> Result<(), Box<dyn Error>> {
let _w_project_memberships = PROJECT_MEMBERSHIP_LOCK.write();
let name = format!("unittest_{}", function_name!());
with_project(&name, |redmine, project_id, _| {
let create_endpoint = super::CreateProjectMembership::builder()
.project_id_or_name(project_id.to_string())
.user_id(1)
.role_ids(vec![8])
.build()?;
redmine
.json_response_body::<_, MembershipWrapper<ProjectMembership>>(&create_endpoint)?;
Ok(())
})?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_update_project_membership() -> Result<(), Box<dyn Error>> {
let _w_project_memberships = PROJECT_MEMBERSHIP_LOCK.write();
let name = format!("unittest_{}", function_name!());
with_project(&name, |redmine, project_id, _name| {
let create_endpoint = super::CreateProjectMembership::builder()
.project_id_or_name(project_id.to_string())
.user_id(1)
.role_ids(vec![8])
.build()?;
let MembershipWrapper { membership } = redmine
.json_response_body::<_, MembershipWrapper<ProjectMembership>>(&create_endpoint)?;
let update_endpoint = super::UpdateProjectMembership::builder()
.id(membership.id)
.role_ids(vec![9])
.build()?;
redmine.ignore_response_body::<_>(&update_endpoint)?;
Ok(())
})?;
Ok(())
}
#[traced_test]
#[test]
fn test_completeness_project_membership_type() -> Result<(), Box<dyn Error>> {
let _r_project_memberships = PROJECT_MEMBERSHIP_LOCK.read();
dotenvy::dotenv()?;
let redmine = crate::api::Redmine::from_env()?;
let endpoint = ListProjectMemberships::builder()
.project_id_or_name("sandbox")
.build()?;
let MembershipsWrapper {
memberships: values,
} = redmine.json_response_body::<_, MembershipsWrapper<serde_json::Value>>(&endpoint)?;
for value in values {
let o: ProjectMembership = serde_json::from_value(value.clone())?;
let reserialized = serde_json::to_value(o)?;
assert_eq!(value, reserialized);
}
Ok(())
}
}