redmine_api/api/
issue_statuses.rs

1//! Issue Statuses Rest API Endpoint definitions
2//!
3//! [Redmine Documentation](https://www.redmine.org/projects/redmine/wiki/Rest_IssueStatuses)
4//!
5//! - [x] all issue statuses endpoint
6
7use derive_builder::Builder;
8use reqwest::Method;
9use std::borrow::Cow;
10
11use crate::api::{Endpoint, ReturnsJsonResponse};
12
13/// a minimal type for Redmine issue status used in
14/// other Redmine objects (e.g. issue)
15#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16pub struct IssueStatusEssentials {
17    /// numeric id
18    pub id: u64,
19    /// is this status consided closed, only included in recent Redmine versions
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub is_closed: Option<bool>,
22    /// display name
23    pub name: String,
24}
25
26impl From<IssueStatus> for IssueStatusEssentials {
27    fn from(v: IssueStatus) -> Self {
28        IssueStatusEssentials {
29            id: v.id,
30            is_closed: Some(v.is_closed),
31            name: v.name,
32        }
33    }
34}
35
36impl From<&IssueStatus> for IssueStatusEssentials {
37    fn from(v: &IssueStatus) -> Self {
38        IssueStatusEssentials {
39            id: v.id,
40            is_closed: Some(v.is_closed),
41            name: v.name.to_owned(),
42        }
43    }
44}
45
46/// a type for issue status to use as an API return type
47///
48/// alternatively you can use your own type limited to the fields you need
49#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50pub struct IssueStatus {
51    /// numeric id
52    pub id: u64,
53    /// display name
54    pub name: String,
55    /// description
56    pub description: Option<String>,
57    /// is this status considered closed
58    pub is_closed: bool,
59}
60
61/// The endpoint for all issue statuses
62#[derive(Debug, Clone, Builder)]
63#[builder(setter(strip_option))]
64pub struct ListIssueStatuses {}
65
66impl ReturnsJsonResponse for ListIssueStatuses {}
67
68impl ListIssueStatuses {
69    /// Create a builder for the endpoint.
70    #[must_use]
71    pub fn builder() -> ListIssueStatusesBuilder {
72        ListIssueStatusesBuilder::default()
73    }
74}
75
76impl Endpoint for ListIssueStatuses {
77    fn method(&self) -> Method {
78        Method::GET
79    }
80
81    fn endpoint(&self) -> Cow<'static, str> {
82        "issue_statuses.json".into()
83    }
84}
85
86/// helper struct for outer layers with a issue_statuses field holding the inner data
87#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
88pub struct IssueStatusesWrapper<T> {
89    /// to parse JSON with issue_statuses key
90    pub issue_statuses: Vec<T>,
91}
92
93/// A lot of APIs in Redmine wrap their data in an extra layer, this is a
94/// helper struct for outer layers with a issue_status field holding the inner data
95#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
96pub struct IssueStatusWrapper<T> {
97    /// to parse JSON with an issue_status key
98    pub issue_status: T,
99}
100
101#[cfg(test)]
102mod test {
103    use super::*;
104    use pretty_assertions::assert_eq;
105    use std::error::Error;
106    use tracing_test::traced_test;
107
108    #[traced_test]
109    #[test]
110    fn test_list_issue_statuses_no_pagination() -> Result<(), Box<dyn Error>> {
111        dotenvy::dotenv()?;
112        let redmine = crate::api::Redmine::from_env()?;
113        let endpoint = ListIssueStatuses::builder().build()?;
114        redmine.json_response_body::<_, IssueStatusesWrapper<IssueStatus>>(&endpoint)?;
115        Ok(())
116    }
117
118    /// this tests if any of the results contain a field we are not deserializing
119    ///
120    /// this will only catch fields we missed if they are part of the response but
121    /// it is better than nothing
122    #[traced_test]
123    #[test]
124    fn test_completeness_issue_status_type() -> Result<(), Box<dyn Error>> {
125        dotenvy::dotenv()?;
126        let redmine = crate::api::Redmine::from_env()?;
127        let endpoint = ListIssueStatuses::builder().build()?;
128        let IssueStatusesWrapper {
129            issue_statuses: values,
130        } = redmine.json_response_body::<_, IssueStatusesWrapper<serde_json::Value>>(&endpoint)?;
131        for value in values {
132            let o: IssueStatus = serde_json::from_value(value.clone())?;
133            let reserialized = serde_json::to_value(o)?;
134            assert_eq!(value, reserialized);
135        }
136        Ok(())
137    }
138}