1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3
4use fedora::url::Url;
5use serde::{Deserialize, Serialize};
6
7use super::dates::*;
8use super::enums::*;
9use super::release::FedoraRelease;
10
11#[derive(Debug, Deserialize, Serialize)]
13#[non_exhaustive]
14pub struct Bug {
15 pub bug_id: u32,
17 pub parent: bool,
19 pub security: bool,
21 pub title: Option<String>,
23
24 #[serde(flatten)]
26 pub extra: HashMap<String, serde_json::Value>,
27}
28
29impl Display for Bug {
30 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
31 let title = match &self.title {
32 Some(title) => title.as_str(),
33 None => "(None)",
34 };
35
36 writeln!(f, "Bug {}:", self.bug_id)?;
37 writeln!(f, "Title: {title}")?;
38 writeln!(f, "URL: {}", self.url())?;
39
40 Ok(())
41 }
42}
43
44impl Bug {
45 pub fn url(&self) -> Url {
47 Url::parse(&format!("https://bugzilla.redhat.com/show_bug.cgi?id={}", self.bug_id))
48 .expect("Failed to parse the hard-coded URL, this should not happen.")
49 }
50}
51
52
53#[derive(Debug, Deserialize, Serialize)]
55#[non_exhaustive]
56pub struct BugFeedback {
57 pub bug: Option<Bug>,
59 pub bug_id: u32,
61 pub comment_id: Option<u32>,
63 pub karma: Karma,
65
66 #[serde(flatten)]
68 pub extra: HashMap<String, serde_json::Value>,
69}
70
71impl Display for BugFeedback {
72 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
73 write!(f, "{bug_id}: {karma}", bug_id = self.bug_id, karma = self.karma)
74 }
75}
76
77
78#[derive(Debug, Deserialize, Serialize)]
80#[non_exhaustive]
81pub struct Build {
82 pub epoch: Option<u32>,
84 pub nvr: String,
86 pub release_id: Option<u32>,
88 pub signed: bool,
90 #[serde(rename = "type")]
92 pub build_type: ContentType,
93
94 #[serde(flatten)]
96 pub extra: HashMap<String, serde_json::Value>,
97}
98
99impl Display for Build {
100 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
101 writeln!(f, "Build {}", &self.nvr)?;
102 writeln!(f, "Type: {}", self.build_type)?;
103 writeln!(
104 f,
105 "Epoch: {}",
106 match self.epoch {
107 Some(epoch) => epoch.to_string(),
108 None => "(None)".to_string(),
109 }
110 )?;
111 Ok(())
112 }
113}
114
115
116#[derive(Debug, Deserialize, Serialize)]
118#[non_exhaustive]
119pub struct Comment {
120 #[deprecated(since = "2.0.0")]
122 author: Option<String>,
123 pub bug_feedback: Vec<BugFeedback>,
125 pub id: u32,
127 pub karma: Karma,
129 #[deprecated(since = "2.0.0")]
131 karma_critpath: Karma,
132 pub testcase_feedback: Vec<TestCaseFeedback>,
134 pub text: String,
136 #[serde(with = "bodhi_date_format")]
138 pub timestamp: BodhiDate,
139 pub update: Option<Update>,
141 pub update_id: u32,
143 #[deprecated(since = "2.0.0")]
146 update_alias: Option<String>,
147 pub user: User,
149 pub user_id: u32,
151
152 #[serde(flatten)]
154 pub extra: HashMap<String, serde_json::Value>,
155}
156
157impl Display for Comment {
158 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
159 writeln!(f, "Comment by {}", &self.user.name)?;
160 writeln!(f, "{}", &self.text)?;
161 writeln!(f, "Submitted: {}", &self.timestamp)?;
162 writeln!(f, "Karma: {}", self.karma)?;
163
164 Ok(())
165 }
166}
167
168
169#[derive(Debug, Deserialize, Serialize)]
171#[non_exhaustive]
172pub struct Compose {
173 pub checkpoints: String,
175 pub content_type: Option<ContentType>,
177 #[serde(with = "bodhi_date_format")]
179 pub date_created: BodhiDate,
180 pub error_message: Option<String>,
182 pub release: Option<Release>,
184 pub release_id: u32,
186 pub request: ComposeRequest,
191 pub security: bool,
193 pub state: ComposeState,
195 #[serde(with = "bodhi_date_format")]
197 pub state_date: BodhiDate,
198 pub update_summary: Vec<UpdateSummary>,
200
201 #[serde(flatten)]
203 pub extra: HashMap<String, serde_json::Value>,
204}
205
206impl Display for Compose {
207 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
208 writeln!(
209 f,
210 "Compose for {release} / {request} ({content_type})",
211 release = match &self.release {
212 Some(release) => release.name.to_string(),
213 None => "(None)".to_string(),
214 },
215 request = &self.request,
216 content_type = match &self.content_type {
217 Some(content_type) => content_type.to_string(),
218 None => "(None)".to_string(),
219 }
220 )?;
221
222 writeln!(f, "Created: {}", &self.date_created)?;
223 writeln!(f, "Status: {}", self.state)?;
224
225 Ok(())
226 }
227}
228
229
230#[derive(Debug, Deserialize, Serialize)]
232#[non_exhaustive]
233pub struct Group {
234 pub name: String,
236
237 #[serde(flatten)]
239 pub extra: HashMap<String, serde_json::Value>,
240}
241
242impl Display for Group {
243 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
244 write!(f, "{}", &self.name)
245 }
246}
247
248
249#[derive(Debug, Deserialize, Serialize)]
251#[non_exhaustive]
252pub struct Override {
253 pub build: Build,
255 pub build_id: u32,
257 #[serde(with = "bodhi_date_format")]
259 pub expiration_date: BodhiDate,
260 #[serde(with = "option_bodhi_date_format")]
262 pub expired_date: Option<BodhiDate>,
263 pub notes: String,
265 pub nvr: String,
268 #[serde(with = "bodhi_date_format")]
270 pub submission_date: BodhiDate,
271 pub submitter: User,
273 pub submitter_id: u32,
275
276 #[serde(flatten)]
278 pub extra: HashMap<String, serde_json::Value>,
279}
280
281impl Display for Override {
282 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
283 let expired_date = match &self.expired_date {
284 Some(date) => date.to_string(),
285 None => "no".to_string(),
286 };
287
288 writeln!(f, "Buildroot override for {}", &self.nvr)?;
289 writeln!(f, "{}", &self.notes)?;
290 writeln!(f, "Submitter: {}", &self.submitter.name)?;
291 writeln!(f, "Submission date: {}", &self.submission_date)?;
292 writeln!(f, "Expiration date: {}", &self.expiration_date)?;
293 writeln!(f, "Expired: {}", &expired_date)?;
294
295 Ok(())
296 }
297}
298
299
300#[derive(Debug, Deserialize, Serialize)]
302#[non_exhaustive]
303pub struct Package {
304 pub name: String,
306 #[serde(rename = "type")]
308 pub package_type: ContentType,
309 pub requirements: Option<String>,
311
312 #[serde(flatten)]
314 pub extra: HashMap<String, serde_json::Value>,
315}
316
317impl Display for Package {
318 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
319 write!(
320 f,
321 "{name} ({package_type})",
322 name = &self.name,
323 package_type = self.package_type
324 )
325 }
326}
327
328
329#[derive(Debug, Deserialize, Serialize)]
331#[non_exhaustive]
332pub struct Release {
333 pub branch: String,
335 pub candidate_tag: String,
337 pub composed_by_bodhi: bool,
339 #[deprecated(
341 since = "2.0.1",
342 note = "The `composes` field was dropped from serialized `Release` objects with bodhi server versions 6.0 and later. It is only kept for backwards compatibility, but will in the future always have a value of `None` when deserializing JSON server responses."
343 )]
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub composes: Option<Vec<Compose>>,
346 pub create_automatic_updates: Option<bool>,
348 pub dist_tag: String,
350 pub id_prefix: String,
352 pub long_name: String,
354 pub mail_template: String,
356 pub name: FedoraRelease,
358 pub package_manager: PackageManager,
360 pub override_tag: String,
362 pub pending_signing_tag: String,
364 pub pending_stable_tag: String,
366 pub pending_testing_tag: String,
368 pub stable_tag: String,
370 pub state: ReleaseState,
372 pub testing_repository: Option<String>,
374 pub testing_tag: String,
376 pub version: String,
378 pub eol: Option<String>,
380
381 #[serde(flatten)]
383 pub extra: HashMap<String, serde_json::Value>,
384}
385
386impl Display for Release {
387 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
388 writeln!(f, "Release {}:", &self.name)?;
389 writeln!(f, "State: {}", &self.state)?;
390 writeln!(f, "Branch: {}", &self.branch)?;
391 writeln!(f, "Candidate tag: {}", &self.candidate_tag)?;
392 writeln!(f, "Override tag: {}", &self.override_tag)?;
393 writeln!(f, "Pending signing tag: {}", &self.pending_signing_tag)?;
394 writeln!(f, "Pending stable tag: {}", &self.pending_stable_tag)?;
395 writeln!(f, "Pending testing tag: {}", &self.pending_testing_tag)?;
396 writeln!(f, "Stable tag: {}", &self.stable_tag)?;
397 writeln!(f, "Testing tag: {}", &self.testing_tag)?;
398
399 Ok(())
400 }
401}
402
403
404#[derive(Debug, Deserialize, Serialize)]
406#[non_exhaustive]
407pub struct TestCase {
408 pub name: String,
410 pub package: Option<Package>,
412
413 #[serde(flatten)]
415 pub extra: HashMap<String, serde_json::Value>,
416}
417
418impl Display for TestCase {
419 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
420 let package = match &self.package {
421 Some(package) => &package.name,
422 None => "(None)",
423 };
424
425 write!(
426 f,
427 "Test Case '{name}' for package {package}",
428 name = &self.name,
429 package = &package
430 )
431 }
432}
433
434impl TestCase {
435 pub fn url(&self) -> Url {
437 Url::parse(&format!(
438 "https://fedoraproject.org/wiki/{}",
439 self.name.replace(' ', "_")
440 ))
441 .expect("Failed to parse the hard-coded URL, this should not happen.")
442 }
443}
444
445
446#[derive(Debug, Deserialize, Serialize)]
448#[non_exhaustive]
449pub struct TestCaseFeedback {
450 pub comment_id: Option<u32>,
452 pub karma: Karma,
454 pub testcase: TestCase,
456 pub testcase_id: u32,
458
459 #[serde(flatten)]
461 pub extra: HashMap<String, serde_json::Value>,
462}
463
464impl Display for TestCaseFeedback {
465 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
466 write!(f, "{name}: {karma}", name = &self.testcase.name, karma = self.karma)
467 }
468}
469
470
471#[derive(Debug, Deserialize, Serialize)]
473#[non_exhaustive]
474pub struct Update {
475 pub alias: String,
477 pub autokarma: bool,
479 pub autotime: bool,
481 pub bugs: Vec<Bug>,
483 pub builds: Vec<Build>,
485 pub close_bugs: bool,
487 pub comments: Option<Vec<Comment>>,
489 pub compose: Option<Compose>,
491 pub content_type: Option<ContentType>,
493 pub critpath: bool,
495 pub critpath_groups: Option<String>,
498 #[deprecated(
500 since = "2.0.0",
501 note = "`date_approved` is an unused field: <https://github.com/fedora-infra/bodhi/issues/4171>"
502 )]
503 #[serde(with = "option_bodhi_date_format")]
504 pub date_approved: Option<BodhiDate>,
505 #[serde(with = "option_bodhi_date_format")]
507 pub date_modified: Option<BodhiDate>,
508 #[serde(with = "option_bodhi_date_format")]
510 pub date_pushed: Option<BodhiDate>,
511 #[serde(with = "option_bodhi_date_format")]
513 pub date_stable: Option<BodhiDate>,
514 #[serde(with = "option_bodhi_date_format")]
516 pub date_submitted: Option<BodhiDate>,
517 #[serde(with = "option_bodhi_date_format")]
519 pub date_testing: Option<BodhiDate>,
520 pub display_name: String,
522 pub from_tag: Option<String>,
524 pub karma: Option<i32>,
526 pub locked: bool,
528 pub meets_testing_requirements: bool,
530 pub notes: String,
532 pub pushed: bool,
534 pub release: Release,
536 pub request: Option<UpdateRequest>,
538 pub require_bugs: bool,
540 pub require_testcases: bool,
542 pub requirements: Option<String>,
544 pub severity: UpdateSeverity,
546 pub stable_days: Option<u32>,
548 pub stable_karma: Option<i32>,
550 pub status: UpdateStatus,
552 pub suggest: UpdateSuggestion,
554 pub test_cases: Option<Vec<TestCase>>,
556 pub test_gating_status: Option<TestGatingStatus>,
560 pub title: String,
562 pub unstable_karma: Option<i32>,
564 #[deprecated(since = "2.0.0")]
566 #[serde(rename = "updateid")]
567 update_id: Option<UpdateID>,
568 #[serde(rename = "type")]
570 pub update_type: UpdateType,
571 pub url: String,
573 pub user: User,
575 pub version_hash: String,
577
578 #[serde(flatten)]
580 pub extra: HashMap<String, serde_json::Value>,
581}
582
583impl Display for Update {
584 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
585 let builds = if !self.builds.is_empty() {
586 self.builds
587 .iter()
588 .map(|b| b.nvr.as_str())
589 .collect::<Vec<&str>>()
590 .join("\n")
591 } else {
592 String::from("(None)")
593 };
594
595 let bugs = if !self.bugs.is_empty() {
596 self.bugs
597 .iter()
598 .map(|b| b.bug_id.to_string())
599 .collect::<Vec<String>>()
600 .join(" ")
601 } else {
602 String::from("(None)")
603 };
604
605 let test_cases = match &self.test_cases {
606 Some(test_cases) => {
607 if !test_cases.is_empty() {
608 test_cases
609 .iter()
610 .map(|t| t.name.as_str())
611 .collect::<Vec<&str>>()
612 .join(" ")
613 } else {
614 "(None)".to_string()
615 }
616 },
617 None => "(None)".to_string(),
618 };
619
620 writeln!(f, "Update {}:", &self.alias)?;
621 writeln!(f, "{}", &self.notes)?;
622 writeln!(f)?;
623 writeln!(f, "State: {}", self.status)?;
624 writeln!(f, "Submitter: {}", &self.user.name)?;
625 writeln!(f)?;
626 writeln!(f, "Builds:")?;
627 writeln!(f, "{}", &builds)?;
628 writeln!(f)?;
629 writeln!(f, "Bugs: {}", &bugs)?;
630 writeln!(f, "Test Cases: {}", &test_cases)?;
631
632 Ok(())
633 }
634}
635
636
637#[derive(Debug, Deserialize, Serialize)]
639#[non_exhaustive]
640pub struct UpdateSummary {
641 pub alias: String,
643 pub title: String,
645}
646
647impl Display for UpdateSummary {
648 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
649 write!(f, "{}: {}", self.alias, self.title)
650 }
651}
652
653
654#[derive(Debug, Deserialize, Serialize)]
656#[non_exhaustive]
657pub struct User {
658 pub avatar: Option<String>,
660 pub email: Option<String>,
662 pub groups: Vec<Group>,
664 pub id: u32,
666 pub name: String,
668 pub openid: Option<String>,
670
671 #[serde(flatten)]
673 pub extra: HashMap<String, serde_json::Value>,
674}
675
676impl Display for User {
677 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
678 let email = match &self.email {
679 Some(email) => email.as_str(),
680 None => "(None)",
681 };
682
683 let groups: String = self
684 .groups
685 .iter()
686 .map(|g| g.name.as_str())
687 .collect::<Vec<&str>>()
688 .join(", ");
689
690 writeln!(f, "User {}:", &self.name)?;
691 writeln!(f, "E-Mail: {email}")?;
692 writeln!(f, "Groups: {}", &groups)?;
693
694 Ok(())
695 }
696}