1use derive_builder::Builder;
12use reqwest::Method;
13use std::borrow::Cow;
14
15use crate::api::projects::ProjectEssentials;
16use crate::api::{Endpoint, NoPagination, ReturnsJsonResponse};
17use serde::Serialize;
18
19#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
22pub struct VersionEssentials {
23 pub id: u64,
25 pub name: String,
27}
28
29impl From<Version> for VersionEssentials {
30 fn from(v: Version) -> Self {
31 VersionEssentials {
32 id: v.id,
33 name: v.name,
34 }
35 }
36}
37
38impl From<&Version> for VersionEssentials {
39 fn from(v: &Version) -> Self {
40 VersionEssentials {
41 id: v.id,
42 name: v.name.to_owned(),
43 }
44 }
45}
46
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51pub struct Version {
52 pub id: u64,
54 pub name: String,
56 pub project: ProjectEssentials,
58 pub description: String,
60 pub status: VersionStatus,
62 pub due_date: Option<time::Date>,
64 pub sharing: VersionSharing,
66 #[serde(
68 serialize_with = "crate::api::serialize_rfc3339",
69 deserialize_with = "crate::api::deserialize_rfc3339"
70 )]
71 pub created_on: time::OffsetDateTime,
72 #[serde(
74 serialize_with = "crate::api::serialize_rfc3339",
75 deserialize_with = "crate::api::deserialize_rfc3339"
76 )]
77 pub updated_on: time::OffsetDateTime,
78 #[serde(default)]
80 wiki_page_title: Option<String>,
81}
82
83#[derive(Debug, Clone, Builder)]
85#[builder(setter(strip_option))]
86pub struct ListVersions<'a> {
87 #[builder(setter(into))]
89 project_id_or_name: Cow<'a, str>,
90}
91
92impl ReturnsJsonResponse for ListVersions<'_> {}
93impl NoPagination for ListVersions<'_> {}
94
95impl<'a> ListVersions<'a> {
96 #[must_use]
98 pub fn builder() -> ListVersionsBuilder<'a> {
99 ListVersionsBuilder::default()
100 }
101}
102
103impl Endpoint for ListVersions<'_> {
104 fn method(&self) -> Method {
105 Method::GET
106 }
107
108 fn endpoint(&self) -> Cow<'static, str> {
109 format!("projects/{}/versions.json", self.project_id_or_name).into()
110 }
111}
112
113#[derive(Debug, Clone, Builder)]
115#[builder(setter(strip_option))]
116pub struct GetVersion {
117 id: u64,
119}
120
121impl ReturnsJsonResponse for GetVersion {}
122impl NoPagination for GetVersion {}
123
124impl GetVersion {
125 #[must_use]
127 pub fn builder() -> GetVersionBuilder {
128 GetVersionBuilder::default()
129 }
130}
131
132impl Endpoint for GetVersion {
133 fn method(&self) -> Method {
134 Method::GET
135 }
136
137 fn endpoint(&self) -> Cow<'static, str> {
138 format!("versions/{}.json", &self.id).into()
139 }
140}
141
142#[derive(Debug, Clone, serde::Deserialize, Serialize)]
145#[serde(rename_all = "snake_case")]
146pub enum VersionStatus {
147 Open,
149 Locked,
151 Closed,
153}
154
155#[derive(Debug, Clone, serde::Deserialize, Serialize)]
157#[serde(rename_all = "snake_case")]
158pub enum VersionSharing {
159 None,
161 Descendants,
163 Hierarchy,
165 Tree,
167 System,
169}
170
171#[serde_with::skip_serializing_none]
173#[derive(Debug, Clone, Builder, Serialize)]
174#[builder(setter(strip_option))]
175pub struct CreateVersion<'a> {
176 #[builder(setter(into))]
178 #[serde(skip_serializing)]
179 project_id_or_name: Cow<'a, str>,
180 #[builder(setter(into))]
182 name: Cow<'a, str>,
183 #[builder(default)]
185 status: Option<VersionStatus>,
186 #[builder(default)]
188 sharing: Option<VersionSharing>,
189 #[builder(default)]
191 due_date: Option<time::Date>,
192 #[builder(default)]
194 description: Option<Cow<'a, str>>,
195 #[builder(default)]
197 wiki_page_title: Option<Cow<'a, str>>,
198}
199
200impl ReturnsJsonResponse for CreateVersion<'_> {}
201impl NoPagination for CreateVersion<'_> {}
202
203impl<'a> CreateVersion<'a> {
204 #[must_use]
206 pub fn builder() -> CreateVersionBuilder<'a> {
207 CreateVersionBuilder::default()
208 }
209}
210
211impl Endpoint for CreateVersion<'_> {
212 fn method(&self) -> Method {
213 Method::POST
214 }
215
216 fn endpoint(&self) -> Cow<'static, str> {
217 format!("projects/{}/versions.json", self.project_id_or_name).into()
218 }
219
220 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
221 Ok(Some((
222 "application/json",
223 serde_json::to_vec(&VersionWrapper::<CreateVersion> {
224 version: (*self).to_owned(),
225 })?,
226 )))
227 }
228}
229
230#[serde_with::skip_serializing_none]
232#[derive(Debug, Clone, Builder, Serialize)]
233#[builder(setter(strip_option))]
234pub struct UpdateVersion<'a> {
235 #[serde(skip_serializing)]
237 id: u64,
238 #[builder(default, setter(into))]
240 name: Option<Cow<'a, str>>,
241 #[builder(default)]
243 status: Option<VersionStatus>,
244 #[builder(default)]
246 sharing: Option<VersionSharing>,
247 #[builder(default)]
249 due_date: Option<time::Date>,
250 #[builder(default)]
252 description: Option<Cow<'a, str>>,
253 #[builder(default)]
255 wiki_page_title: Option<Cow<'a, str>>,
256}
257
258impl<'a> UpdateVersion<'a> {
259 #[must_use]
261 pub fn builder() -> UpdateVersionBuilder<'a> {
262 UpdateVersionBuilder::default()
263 }
264}
265
266impl Endpoint for UpdateVersion<'_> {
267 fn method(&self) -> Method {
268 Method::PUT
269 }
270
271 fn endpoint(&self) -> Cow<'static, str> {
272 format!("versions/{}.json", self.id).into()
273 }
274
275 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
276 Ok(Some((
277 "application/json",
278 serde_json::to_vec(&VersionWrapper::<UpdateVersion> {
279 version: (*self).to_owned(),
280 })?,
281 )))
282 }
283}
284
285#[derive(Debug, Clone, Builder)]
287#[builder(setter(strip_option))]
288pub struct DeleteVersion {
289 id: u64,
291}
292
293impl DeleteVersion {
294 #[must_use]
296 pub fn builder() -> DeleteVersionBuilder {
297 DeleteVersionBuilder::default()
298 }
299}
300
301impl Endpoint for DeleteVersion {
302 fn method(&self) -> Method {
303 Method::DELETE
304 }
305
306 fn endpoint(&self) -> Cow<'static, str> {
307 format!("versions/{}.json", &self.id).into()
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
313pub struct VersionsWrapper<T> {
314 pub versions: Vec<T>,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
321pub struct VersionWrapper<T> {
322 pub version: T,
324}
325
326#[cfg(test)]
327mod test {
328 use super::*;
329 use crate::api::test_helpers::with_project;
330 use pretty_assertions::assert_eq;
331 use std::error::Error;
332 use tokio::sync::RwLock;
333 use tracing_test::traced_test;
334
335 static VERSION_LOCK: RwLock<()> = RwLock::const_new(());
338
339 #[traced_test]
340 #[test]
341 fn test_list_versions_no_pagination() -> Result<(), Box<dyn Error>> {
342 let _r_versions = VERSION_LOCK.read();
343 dotenvy::dotenv()?;
344 let redmine = crate::api::Redmine::from_env(
345 reqwest::blocking::Client::builder()
346 .use_rustls_tls()
347 .build()?,
348 )?;
349 let endpoint = ListVersions::builder().project_id_or_name("92").build()?;
350 redmine.json_response_body::<_, VersionsWrapper<Version>>(&endpoint)?;
351 Ok(())
352 }
353
354 #[traced_test]
355 #[test]
356 fn test_get_version() -> Result<(), Box<dyn Error>> {
357 let _r_versions = VERSION_LOCK.read();
358 dotenvy::dotenv()?;
359 let redmine = crate::api::Redmine::from_env(
360 reqwest::blocking::Client::builder()
361 .use_rustls_tls()
362 .build()?,
363 )?;
364 let endpoint = GetVersion::builder().id(1182).build()?;
365 redmine.json_response_body::<_, VersionWrapper<Version>>(&endpoint)?;
366 Ok(())
367 }
368
369 #[function_name::named]
370 #[traced_test]
371 #[test]
372 fn test_create_version() -> Result<(), Box<dyn Error>> {
373 let _w_versions = VERSION_LOCK.write();
374 let name = format!("unittest_{}", function_name!());
375 with_project(&name, |redmine, _, name| {
376 let create_endpoint = CreateVersion::builder()
377 .project_id_or_name(name)
378 .name("Test Version")
379 .build()?;
380 redmine.json_response_body::<_, VersionWrapper<Version>>(&create_endpoint)?;
381 Ok(())
382 })?;
383 Ok(())
384 }
385
386 #[function_name::named]
387 #[traced_test]
388 #[test]
389 fn test_update_version() -> Result<(), Box<dyn Error>> {
390 let _w_versions = VERSION_LOCK.write();
391 let name = format!("unittest_{}", function_name!());
392 with_project(&name, |redmine, _, name| {
393 let create_endpoint = CreateVersion::builder()
394 .project_id_or_name(name)
395 .name("Test Version")
396 .build()?;
397 let VersionWrapper { version } =
398 redmine.json_response_body::<_, VersionWrapper<Version>>(&create_endpoint)?;
399 let update_endpoint = super::UpdateVersion::builder()
400 .id(version.id)
401 .name("Neue Test-Version")
402 .build()?;
403 redmine.ignore_response_body::<_>(&update_endpoint)?;
404 Ok(())
405 })?;
406 Ok(())
407 }
408
409 #[traced_test]
414 #[test]
415 fn test_completeness_version_type() -> Result<(), Box<dyn Error>> {
416 let _r_versions = VERSION_LOCK.read();
417 dotenvy::dotenv()?;
418 let redmine = crate::api::Redmine::from_env(
419 reqwest::blocking::Client::builder()
420 .use_rustls_tls()
421 .build()?,
422 )?;
423 let endpoint = ListVersions::builder().project_id_or_name("92").build()?;
424 let VersionsWrapper { versions: values } =
425 redmine.json_response_body::<_, VersionsWrapper<serde_json::Value>>(&endpoint)?;
426 for value in values {
427 let o: Version = serde_json::from_value(value.clone())?;
428 let reserialized = serde_json::to_value(o)?;
429 assert_eq!(value, reserialized);
430 }
431 Ok(())
432 }
433}