1use derive_builder::Builder;
9use reqwest::Method;
10use std::borrow::Cow;
11
12use crate::api::projects::ProjectEssentials;
13use crate::api::users::UserEssentials;
14use crate::api::{Endpoint, Pageable, ReturnsJsonResponse};
15
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20pub struct News {
21 pub id: u64,
23 pub project: ProjectEssentials,
25 pub author: UserEssentials,
27 pub title: String,
29 pub summary: String,
31 pub description: String,
33 #[serde(
35 serialize_with = "crate::api::serialize_rfc3339",
36 deserialize_with = "crate::api::deserialize_rfc3339"
37 )]
38 pub created_on: time::OffsetDateTime,
39}
40#[derive(Debug, Clone, Builder)]
42#[builder(setter(strip_option))]
43pub struct ListNews {}
44
45impl ReturnsJsonResponse for ListNews {}
46impl Pageable for ListNews {
47 fn response_wrapper_key(&self) -> String {
48 "news".to_string()
49 }
50}
51
52impl ListNews {
53 #[must_use]
55 pub fn builder() -> ListNewsBuilder {
56 ListNewsBuilder::default()
57 }
58}
59
60impl Endpoint for ListNews {
61 fn method(&self) -> Method {
62 Method::GET
63 }
64
65 fn endpoint(&self) -> Cow<'static, str> {
66 "news.json".into()
67 }
68}
69
70#[derive(Debug, Clone, Builder)]
72#[builder(setter(strip_option))]
73pub struct ListProjectNews<'a> {
74 #[builder(setter(into))]
76 project_id_or_name: Cow<'a, str>,
77}
78
79impl ReturnsJsonResponse for ListProjectNews<'_> {}
80impl Pageable for ListProjectNews<'_> {
81 fn response_wrapper_key(&self) -> String {
82 "news".to_string()
83 }
84}
85
86impl<'a> ListProjectNews<'a> {
87 #[must_use]
89 pub fn builder() -> ListProjectNewsBuilder<'a> {
90 ListProjectNewsBuilder::default()
91 }
92}
93
94impl Endpoint for ListProjectNews<'_> {
95 fn method(&self) -> Method {
96 Method::GET
97 }
98
99 fn endpoint(&self) -> Cow<'static, str> {
100 format!("projects/{}/news.json", self.project_id_or_name).into()
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
106pub struct NewsWrapper<T> {
107 pub news: Vec<T>,
109}
110
111#[cfg(test)]
112mod test {
113 use super::*;
114 use pretty_assertions::assert_eq;
115 use std::error::Error;
116 use tracing_test::traced_test;
117
118 #[traced_test]
119 #[test]
120 fn test_list_news_no_pagination() -> Result<(), Box<dyn Error>> {
121 dotenvy::dotenv()?;
122 let redmine = crate::api::Redmine::from_env()?;
123 let endpoint = ListNews::builder().build()?;
124 redmine.json_response_body::<_, NewsWrapper<News>>(&endpoint)?;
125 Ok(())
126 }
127
128 #[traced_test]
129 #[test]
130 fn test_list_news_first_page() -> Result<(), Box<dyn Error>> {
131 dotenvy::dotenv()?;
132 let redmine = crate::api::Redmine::from_env()?;
133 let endpoint = ListNews::builder().build()?;
134 redmine.json_response_body_page::<_, News>(&endpoint, 0, 25)?;
135 Ok(())
136 }
137
138 #[traced_test]
139 #[test]
140 fn test_list_news_all_pages() -> Result<(), Box<dyn Error>> {
141 dotenvy::dotenv()?;
142 let redmine = crate::api::Redmine::from_env()?;
143 let endpoint = ListNews::builder().build()?;
144 redmine.json_response_body_all_pages::<_, News>(&endpoint)?;
145 Ok(())
146 }
147
148 #[traced_test]
153 #[test]
154 fn test_completeness_news_type() -> Result<(), Box<dyn Error>> {
155 dotenvy::dotenv()?;
156 let redmine = crate::api::Redmine::from_env()?;
157 let endpoint = ListNews::builder().build()?;
158 let NewsWrapper { news: values } =
159 redmine.json_response_body::<_, NewsWrapper<serde_json::Value>>(&endpoint)?;
160 for value in values {
161 let o: News = serde_json::from_value(value.clone())?;
162 let reserialized = serde_json::to_value(o)?;
163 assert_eq!(value, reserialized);
164 }
165 Ok(())
166 }
167}