1use crate::api::common::NameOrId;
8use crate::api::endpoint_prelude::*;
9use chrono::{DateTime, Utc};
10use derive_builder::Builder;
11use serde_json::json;
12
13use crate::api::projects::releases::links::LinkType;
14
15#[derive(Debug, Builder, Clone)]
19#[builder(setter(strip_option))]
20pub struct CreateReleaseAssetLinks<'a> {
21 #[builder(setter(into))]
25 name: Cow<'a, str>,
26
27 #[builder(setter(into))]
31 url: Cow<'a, str>,
32
33 #[builder(setter(into), default)]
35 direct_asset_path: Option<Cow<'a, str>>,
36
37 #[builder(default)]
41 link_type: Option<LinkType>,
42}
43
44impl<'a> CreateReleaseAssetLinks<'a> {
45 pub fn builder() -> CreateReleaseAssetLinksBuilder<'a> {
47 CreateReleaseAssetLinksBuilder::default()
48 }
49
50 fn as_json(&self) -> serde_json::Value {
51 JsonParams::clean(json!({
52 "name": self.name,
53 "url": self.url,
54 "direct_asset_path": self.direct_asset_path,
55 "link_type": self.link_type.map(|lt| lt.as_str()),
56 }))
57 }
58}
59
60#[derive(Debug, Builder, Clone)]
64#[builder(setter(strip_option))]
65pub struct CreateRelease<'a> {
66 #[builder(setter(into))]
68 project: NameOrId<'a>,
69
70 #[builder(setter(into), default)]
74 name: Option<Cow<'a, str>>,
75
76 #[builder(setter(into))]
78 tag_name: Cow<'a, str>,
79
80 #[builder(setter(into), default)]
82 tag_message: Option<Cow<'a, str>>,
83
84 #[builder(setter(into), default)]
88 description: Option<Cow<'a, str>>,
89
90 #[builder(setter(into), default)]
95 ref_sha: Option<Cow<'a, str>>,
96
97 #[builder(setter(name = "_milestones"), default, private)]
99 milestones: Vec<Cow<'a, str>>,
100
101 #[builder(setter(name = "_assets"), default, private)]
103 assets: Vec<CreateReleaseAssetLinks<'a>>,
104
105 #[builder(default)]
111 released_at: Option<DateTime<Utc>>,
112}
113
114impl<'a> CreateRelease<'a> {
115 pub fn builder() -> CreateReleaseBuilder<'a> {
117 CreateReleaseBuilder::default()
118 }
119
120 fn as_json(&self) -> serde_json::Value {
122 JsonParams::clean(json!({
123 "name": self.name,
124 "tag_name": self.tag_name,
125 "tag_message": self.tag_message,
126 "description": self.description,
127 "ref": self.ref_sha,
128 "milestones": self.milestones,
129 "released_at": self.released_at,
130 "assets": JsonParams::clean(json!({
131 "links": self.assets
132 .iter()
133 .map(|a| a.as_json())
134 .collect::<Vec<_>>()
135 })),
136 }))
137 }
138}
139
140impl<'a> CreateReleaseBuilder<'a> {
141 pub fn milestone<M>(&mut self, milestone: M) -> &mut Self
143 where
144 M: Into<Cow<'a, str>>,
145 {
146 self.milestones
147 .get_or_insert_with(Vec::new)
148 .push(milestone.into());
149 self
150 }
151
152 pub fn milestones<I, M>(&mut self, milestones: I) -> &mut Self
154 where
155 I: Iterator<Item = M>,
156 M: Into<Cow<'a, str>>,
157 {
158 self.milestones
159 .get_or_insert_with(Vec::new)
160 .extend(milestones.map(Into::into));
161 self
162 }
163
164 pub fn asset(&mut self, asset: CreateReleaseAssetLinks<'a>) -> &mut Self {
166 self.assets.get_or_insert_with(Vec::new).push(asset);
167 self
168 }
169
170 pub fn assets<I>(&mut self, assets: I) -> &mut Self
172 where
173 I: Iterator<Item = CreateReleaseAssetLinks<'a>>,
174 {
175 self.assets.get_or_insert_with(Vec::new).extend(assets);
176 self
177 }
178}
179
180impl Endpoint for CreateRelease<'_> {
181 fn method(&self) -> Method {
182 Method::POST
183 }
184
185 fn endpoint(&self) -> Cow<'static, str> {
186 format!("projects/{}/releases", self.project).into()
187 }
188
189 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
190 JsonParams::into_body(&self.as_json())
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use chrono::DateTime;
197 use http::Method;
198
199 use crate::{
200 api::{
201 self,
202 projects::releases::{CreateReleaseAssetLinks, CreateReleaseAssetLinksBuilderError},
203 Query,
204 },
205 test::client::{ExpectedUrl, SingleTestClient},
206 };
207
208 use super::{CreateRelease, CreateReleaseBuilderError};
209
210 #[test]
211 fn assets_url_is_needed() {
212 let err = CreateReleaseAssetLinks::builder()
213 .name("test")
214 .build()
215 .unwrap_err();
216
217 crate::test::assert_missing_field!(err, CreateReleaseAssetLinksBuilderError, "url");
218 }
219
220 #[test]
221 fn assets_name_is_needed() {
222 let err = CreateReleaseAssetLinks::builder()
223 .url("https://img.com/img.png")
224 .build()
225 .unwrap_err();
226
227 crate::test::assert_missing_field!(err, CreateReleaseAssetLinksBuilderError, "name");
228 }
229
230 #[test]
231 fn assets_required_parameter_are_sufficient() {
232 CreateReleaseAssetLinks::builder()
233 .name("test")
234 .url("https://test.com/")
235 .build()
236 .unwrap();
237 }
238
239 #[test]
240 fn project_is_needed() {
241 let err = CreateRelease::builder()
242 .tag_name("1.2.3")
243 .build()
244 .unwrap_err();
245
246 crate::test::assert_missing_field!(err, CreateReleaseBuilderError, "project");
247 }
248
249 #[test]
250 fn tag_name_is_needed() {
251 let err = CreateRelease::builder().project(123).build().unwrap_err();
252
253 crate::test::assert_missing_field!(err, CreateReleaseBuilderError, "tag_name");
254 }
255
256 #[test]
257 fn project_and_tag_name_are_sufficient() {
258 CreateRelease::builder()
259 .project(1)
260 .tag_name("1.2.3")
261 .build()
262 .unwrap();
263 }
264
265 #[test]
266 fn endpoint() {
267 let endpoint = ExpectedUrl::builder()
268 .method(Method::POST)
269 .endpoint("projects/1337/releases")
270 .content_type("application/json")
271 .body_str("{\"tag_name\":\"1.2.3\"}")
272 .build()
273 .unwrap();
274 let client = SingleTestClient::new_raw(endpoint, "");
275
276 let endpoint = CreateRelease::builder()
277 .project(1337)
278 .tag_name("1.2.3")
279 .build()
280 .unwrap();
281 api::ignore(endpoint).query(&client).unwrap();
282 }
283
284 #[test]
285 fn endpoint_name() {
286 let endpoint = ExpectedUrl::builder()
287 .method(Method::POST)
288 .endpoint("projects/1337/releases")
289 .content_type("application/json")
290 .body_str(concat!(
291 "{",
292 "\"name\":\"Test\",",
293 "\"tag_name\":\"1.2.3\"",
294 "}",
295 ))
296 .build()
297 .unwrap();
298 let client = SingleTestClient::new_raw(endpoint, "");
299
300 let endpoint = CreateRelease::builder()
301 .project(1337)
302 .tag_name("1.2.3")
303 .name("Test")
304 .build()
305 .unwrap();
306 api::ignore(endpoint).query(&client).unwrap();
307 }
308
309 #[test]
310 fn endpoint_tag_message() {
311 let endpoint = ExpectedUrl::builder()
312 .method(Method::POST)
313 .endpoint("projects/1337/releases")
314 .content_type("application/json")
315 .body_str(concat!(
316 "{",
317 "\"tag_message\":\"Test message\",",
318 "\"tag_name\":\"1.2.3\"",
319 "}",
320 ))
321 .build()
322 .unwrap();
323 let client = SingleTestClient::new_raw(endpoint, "");
324
325 let endpoint = CreateRelease::builder()
326 .project(1337)
327 .tag_name("1.2.3")
328 .tag_message("Test message")
329 .build()
330 .unwrap();
331 api::ignore(endpoint).query(&client).unwrap();
332 }
333
334 #[test]
335 fn endpoint_description() {
336 let endpoint = ExpectedUrl::builder()
337 .method(Method::POST)
338 .endpoint("projects/1337/releases")
339 .content_type("application/json")
340 .body_str(concat!(
341 "{",
342 "\"description\":\"Test description\",",
343 "\"tag_name\":\"1.2.3\"",
344 "}",
345 ))
346 .build()
347 .unwrap();
348 let client = SingleTestClient::new_raw(endpoint, "");
349
350 let endpoint = CreateRelease::builder()
351 .project(1337)
352 .tag_name("1.2.3")
353 .description("Test description")
354 .build()
355 .unwrap();
356 api::ignore(endpoint).query(&client).unwrap();
357 }
358
359 #[test]
360 fn endpoint_ref() {
361 let endpoint = ExpectedUrl::builder()
362 .method(Method::POST)
363 .endpoint("projects/1337/releases")
364 .content_type("application/json")
365 .body_str(concat!(
366 "{",
367 "\"ref\":\"abfc1234\",",
368 "\"tag_name\":\"1.2.3\"",
369 "}",
370 ))
371 .build()
372 .unwrap();
373 let client = SingleTestClient::new_raw(endpoint, "");
374
375 let endpoint = CreateRelease::builder()
376 .project(1337)
377 .tag_name("1.2.3")
378 .ref_sha("abfc1234")
379 .build()
380 .unwrap();
381 api::ignore(endpoint).query(&client).unwrap();
382 }
383
384 #[test]
385 fn endpoint_milestones() {
386 let endpoint = ExpectedUrl::builder()
387 .method(Method::POST)
388 .endpoint("projects/1337/releases")
389 .content_type("application/json")
390 .body_str(concat!(
391 "{",
392 "\"milestones\":[",
393 "\"milestone_1\",",
394 "\"milestone_2\",",
395 "\"milestone_3\"",
396 "],",
397 "\"tag_name\":\"1.2.3\"",
398 "}",
399 ))
400 .build()
401 .unwrap();
402 let client = SingleTestClient::new_raw(endpoint, "");
403
404 let endpoint = CreateRelease::builder()
405 .project(1337)
406 .tag_name("1.2.3")
407 .milestones(["milestone_1", "milestone_2"].iter().copied())
408 .milestone("milestone_3")
409 .build()
410 .unwrap();
411 api::ignore(endpoint).query(&client).unwrap();
412 }
413
414 #[test]
415 fn endpoint_released_at() {
416 let endpoint = ExpectedUrl::builder()
417 .method(Method::POST)
418 .endpoint("projects/1337/releases")
419 .content_type("application/json")
420 .body_str(concat!(
421 "{",
422 "\"released_at\":\"2023-12-16T12:00:00Z\",",
423 "\"tag_name\":\"1.2.3\"",
424 "}",
425 ))
426 .build()
427 .unwrap();
428 let client = SingleTestClient::new_raw(endpoint, "");
429
430 let endpoint = CreateRelease::builder()
431 .project(1337)
432 .tag_name("1.2.3")
433 .released_at(
434 DateTime::parse_from_rfc3339("2023-12-16T12:00:00Z")
435 .unwrap()
436 .into(),
437 )
438 .build()
439 .unwrap();
440 api::ignore(endpoint).query(&client).unwrap();
441 }
442
443 #[test]
444 fn endpoint_assets() {
445 let endpoint = ExpectedUrl::builder()
446 .method(Method::POST)
447 .endpoint("projects/1337/releases")
448 .content_type("application/json")
449 .body_str(concat!(
450 "{",
451 "\"assets\":",
452 "{",
453 "\"links\":",
454 "[",
455 "{",
456 "\"name\":\"Test url 1\",",
457 "\"url\":\"https://test.com/test-1.zip\"",
458 "},",
459 "{",
460 "\"name\":\"Test url 2\",",
461 "\"url\":\"https://test.com/test-2.zip\"",
462 "},",
463 "{",
464 "\"name\":\"Test url 3\",",
465 "\"url\":\"https://test.com/test-3.zip\"",
466 "}",
467 "]",
468 "},",
469 "\"tag_name\":\"1.2.3\"",
470 "}",
471 ))
472 .build()
473 .unwrap();
474 let client = SingleTestClient::new_raw(endpoint, "");
475
476 let endpoint = CreateRelease::builder()
477 .project(1337)
478 .tag_name("1.2.3")
479 .asset(
480 CreateReleaseAssetLinks::builder()
481 .name("Test url 1")
482 .url("https://test.com/test-1.zip")
483 .build()
484 .unwrap(),
485 )
486 .assets(
487 [
488 CreateReleaseAssetLinks::builder()
489 .name("Test url 2")
490 .url("https://test.com/test-2.zip")
491 .build()
492 .unwrap(),
493 CreateReleaseAssetLinks::builder()
494 .name("Test url 3")
495 .url("https://test.com/test-3.zip")
496 .build()
497 .unwrap(),
498 ]
499 .into_iter(),
500 )
501 .build()
502 .unwrap();
503 api::ignore(endpoint).query(&client).unwrap();
504 }
505
506 #[test]
507 fn endpoint_assets_direct_asset_path() {
508 let endpoint = ExpectedUrl::builder()
509 .method(Method::POST)
510 .endpoint("projects/1337/releases")
511 .content_type("application/json")
512 .body_str(concat!(
513 "{",
514 "\"assets\":",
515 "{",
516 "\"links\":",
517 "[",
518 "{",
519 "\"direct_asset_path\":\"bin/test.zip\",",
520 "\"name\":\"Test url\",",
521 "\"url\":\"https://test.com/test.zip\"",
522 "}",
523 "]",
524 "},",
525 "\"tag_name\":\"1.2.3\"",
526 "}",
527 ))
528 .build()
529 .unwrap();
530 let client = SingleTestClient::new_raw(endpoint, "");
531
532 let endpoint = CreateRelease::builder()
533 .project(1337)
534 .tag_name("1.2.3")
535 .asset(
536 CreateReleaseAssetLinks::builder()
537 .name("Test url")
538 .url("https://test.com/test.zip")
539 .direct_asset_path("bin/test.zip")
540 .build()
541 .unwrap(),
542 )
543 .build()
544 .unwrap();
545 api::ignore(endpoint).query(&client).unwrap();
546 }
547
548 #[test]
549 fn endpoint_assets_link_type() {
550 let endpoint = ExpectedUrl::builder()
551 .method(Method::POST)
552 .endpoint("projects/1337/releases")
553 .content_type("application/json")
554 .body_str(concat!(
555 "{",
556 "\"assets\":",
557 "{",
558 "\"links\":",
559 "[",
560 "{",
561 "\"link_type\":\"other\",",
562 "\"name\":\"Test url\",",
563 "\"url\":\"https://test.com/test.zip\"",
564 "}",
565 "]",
566 "},",
567 "\"tag_name\":\"1.2.3\"",
568 "}",
569 ))
570 .build()
571 .unwrap();
572 let client = SingleTestClient::new_raw(endpoint, "");
573
574 let endpoint = CreateRelease::builder()
575 .project(1337)
576 .tag_name("1.2.3")
577 .asset(
578 CreateReleaseAssetLinks::builder()
579 .name("Test url")
580 .url("https://test.com/test.zip")
581 .link_type(api::projects::releases::links::LinkType::Other)
582 .build()
583 .unwrap(),
584 )
585 .build()
586 .unwrap();
587 api::ignore(endpoint).query(&client).unwrap();
588 }
589}