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, NoPagination, 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 {}
67impl NoPagination for ListIssueStatuses {}
68
69impl ListIssueStatuses {
70    /// Create a builder for the endpoint.
71    #[must_use]
72    pub fn builder() -> ListIssueStatusesBuilder {
73        ListIssueStatusesBuilder::default()
74    }
75}
76
77impl Endpoint for ListIssueStatuses {
78    fn method(&self) -> Method {
79        Method::GET
80    }
81
82    fn endpoint(&self) -> Cow<'static, str> {
83        "issue_statuses.json".into()
84    }
85}
86
87/// helper struct for outer layers with a issue_statuses field holding the inner data
88#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
89pub struct IssueStatusesWrapper<T> {
90    /// to parse JSON with issue_statuses key
91    pub issue_statuses: Vec<T>,
92}
93
94/// A lot of APIs in Redmine wrap their data in an extra layer, this is a
95/// helper struct for outer layers with a issue_status field holding the inner data
96#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
97pub struct IssueStatusWrapper<T> {
98    /// to parse JSON with an issue_status key
99    pub issue_status: T,
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105    use pretty_assertions::assert_eq;
106    use std::error::Error;
107    use tracing_test::traced_test;
108
109    #[traced_test]
110    #[test]
111    fn test_list_issue_statuses_no_pagination() -> Result<(), Box<dyn Error>> {
112        dotenvy::dotenv()?;
113        let redmine = crate::api::Redmine::from_env()?;
114        let endpoint = ListIssueStatuses::builder().build()?;
115        redmine.json_response_body::<_, IssueStatusesWrapper<IssueStatus>>(&endpoint)?;
116        Ok(())
117    }
118
119    /// this tests if any of the results contain a field we are not deserializing
120    ///
121    /// this will only catch fields we missed if they are part of the response but
122    /// it is better than nothing
123    #[traced_test]
124    #[test]
125    fn test_completeness_issue_status_type() -> Result<(), Box<dyn Error>> {
126        dotenvy::dotenv()?;
127        let redmine = crate::api::Redmine::from_env()?;
128        let endpoint = ListIssueStatuses::builder().build()?;
129        let IssueStatusesWrapper {
130            issue_statuses: values,
131        } = redmine.json_response_body::<_, IssueStatusesWrapper<serde_json::Value>>(&endpoint)?;
132        for value in values {
133            let o: IssueStatus = serde_json::from_value(value.clone())?;
134            let reserialized = serde_json::to_value(o)?;
135            assert_eq!(value, reserialized);
136        }
137        Ok(())
138    }
139}