use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use fedora::url::Url;
use serde::{Deserialize, Serialize};
use super::dates::*;
use super::enums::*;
use super::release::FedoraRelease;
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Bug {
pub bug_id: u32,
pub parent: bool,
pub security: bool,
pub title: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Bug {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let title = match &self.title {
Some(title) => title.as_str(),
None => "(None)",
};
writeln!(f, "Bug {}:", self.bug_id)?;
writeln!(f, "Title: {title}")?;
writeln!(f, "URL: {}", self.url())?;
Ok(())
}
}
impl Bug {
pub fn url(&self) -> Url {
Url::parse(&format!("https://bugzilla.redhat.com/show_bug.cgi?id={}", self.bug_id))
.expect("Failed to parse the hard-coded URL, this should not happen.")
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct BugFeedback {
pub bug: Option<Bug>,
pub bug_id: u32,
pub comment_id: Option<u32>,
pub karma: Karma,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for BugFeedback {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{bug_id}: {karma}", bug_id = self.bug_id, karma = self.karma)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Build {
pub epoch: Option<u32>,
pub nvr: String,
pub release_id: Option<u32>,
pub signed: bool,
#[serde(rename = "type")]
pub build_type: ContentType,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Build {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
writeln!(f, "Build {}", &self.nvr)?;
writeln!(f, "Type: {}", self.build_type)?;
writeln!(
f,
"Epoch: {}",
match self.epoch {
Some(epoch) => epoch.to_string(),
None => "(None)".to_string(),
}
)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Comment {
#[deprecated(since = "2.0.0")]
author: Option<String>,
pub bug_feedback: Vec<BugFeedback>,
pub id: u32,
pub karma: Karma,
#[deprecated(since = "2.0.0")]
karma_critpath: Karma,
pub testcase_feedback: Vec<TestCaseFeedback>,
pub text: String,
#[serde(with = "bodhi_date_format")]
pub timestamp: BodhiDate,
pub update: Option<Update>,
pub update_id: u32,
#[deprecated(since = "2.0.0")]
update_alias: Option<String>,
pub user: User,
pub user_id: u32,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Comment {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
writeln!(f, "Comment by {}", &self.user.name)?;
writeln!(f, "{}", &self.text)?;
writeln!(f, "Submitted: {}", &self.timestamp)?;
writeln!(f, "Karma: {}", self.karma)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Compose {
pub checkpoints: String,
pub content_type: Option<ContentType>,
#[serde(with = "bodhi_date_format")]
pub date_created: BodhiDate,
pub error_message: Option<String>,
pub release: Option<Release>,
pub release_id: u32,
pub request: ComposeRequest,
pub security: bool,
pub state: ComposeState,
#[serde(with = "bodhi_date_format")]
pub state_date: BodhiDate,
pub update_summary: Vec<UpdateSummary>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Compose {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
writeln!(
f,
"Compose for {release} / {request} ({content_type})",
release = match &self.release {
Some(release) => release.name.to_string(),
None => "(None)".to_string(),
},
request = &self.request,
content_type = match &self.content_type {
Some(content_type) => content_type.to_string(),
None => "(None)".to_string(),
}
)?;
writeln!(f, "Created: {}", &self.date_created)?;
writeln!(f, "Status: {}", self.state)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Group {
pub name: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Group {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", &self.name)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Override {
pub build: Build,
pub build_id: u32,
#[serde(with = "bodhi_date_format")]
pub expiration_date: BodhiDate,
#[serde(with = "option_bodhi_date_format")]
pub expired_date: Option<BodhiDate>,
pub notes: String,
pub nvr: String,
#[serde(with = "bodhi_date_format")]
pub submission_date: BodhiDate,
pub submitter: User,
pub submitter_id: u32,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Override {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let expired_date = match &self.expired_date {
Some(date) => date.to_string(),
None => "no".to_string(),
};
writeln!(f, "Buildroot override for {}", &self.nvr)?;
writeln!(f, "{}", &self.notes)?;
writeln!(f, "Submitter: {}", &self.submitter.name)?;
writeln!(f, "Submission date: {}", &self.submission_date)?;
writeln!(f, "Expiration date: {}", &self.expiration_date)?;
writeln!(f, "Expired: {}", &expired_date)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Package {
pub name: String,
#[serde(rename = "type")]
pub package_type: ContentType,
pub requirements: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Package {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(
f,
"{name} ({package_type})",
name = &self.name,
package_type = self.package_type
)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Release {
pub branch: String,
pub candidate_tag: String,
pub composed_by_bodhi: bool,
#[deprecated(
since = "2.0.1",
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."
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub composes: Option<Vec<Compose>>,
pub create_automatic_updates: Option<bool>,
pub dist_tag: String,
pub id_prefix: String,
pub long_name: String,
pub mail_template: String,
pub name: FedoraRelease,
pub package_manager: PackageManager,
pub override_tag: String,
pub pending_signing_tag: String,
pub pending_stable_tag: String,
pub pending_testing_tag: String,
pub stable_tag: String,
pub state: ReleaseState,
pub testing_repository: Option<String>,
pub testing_tag: String,
pub version: String,
pub eol: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Release {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
writeln!(f, "Release {}:", &self.name)?;
writeln!(f, "State: {}", &self.state)?;
writeln!(f, "Branch: {}", &self.branch)?;
writeln!(f, "Candidate tag: {}", &self.candidate_tag)?;
writeln!(f, "Override tag: {}", &self.override_tag)?;
writeln!(f, "Pending signing tag: {}", &self.pending_signing_tag)?;
writeln!(f, "Pending stable tag: {}", &self.pending_stable_tag)?;
writeln!(f, "Pending testing tag: {}", &self.pending_testing_tag)?;
writeln!(f, "Stable tag: {}", &self.stable_tag)?;
writeln!(f, "Testing tag: {}", &self.testing_tag)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct TestCase {
pub name: String,
pub package: Option<Package>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for TestCase {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let package = match &self.package {
Some(package) => &package.name,
None => "(None)",
};
write!(
f,
"Test Case '{name}' for package {package}",
name = &self.name,
package = &package
)
}
}
impl TestCase {
pub fn url(&self) -> Url {
Url::parse(&format!(
"https://fedoraproject.org/wiki/{}",
self.name.replace(' ', "_")
))
.expect("Failed to parse the hard-coded URL, this should not happen.")
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct TestCaseFeedback {
pub comment_id: Option<u32>,
pub karma: Karma,
pub testcase: TestCase,
pub testcase_id: u32,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for TestCaseFeedback {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{name}: {karma}", name = &self.testcase.name, karma = self.karma)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Update {
pub alias: String,
pub autokarma: bool,
pub autotime: bool,
pub bugs: Vec<Bug>,
pub builds: Vec<Build>,
pub close_bugs: bool,
pub comments: Option<Vec<Comment>>,
pub compose: Option<Compose>,
pub content_type: Option<ContentType>,
pub critpath: bool,
pub critpath_groups: Option<String>,
#[deprecated(
since = "2.0.0",
note = "`date_approved` is an unused field: <https://github.com/fedora-infra/bodhi/issues/4171>"
)]
#[serde(with = "option_bodhi_date_format")]
pub date_approved: Option<BodhiDate>,
#[serde(with = "option_bodhi_date_format")]
pub date_modified: Option<BodhiDate>,
#[serde(with = "option_bodhi_date_format")]
pub date_pushed: Option<BodhiDate>,
#[serde(with = "option_bodhi_date_format")]
pub date_stable: Option<BodhiDate>,
#[serde(with = "option_bodhi_date_format")]
pub date_submitted: Option<BodhiDate>,
#[serde(with = "option_bodhi_date_format")]
pub date_testing: Option<BodhiDate>,
pub display_name: String,
pub from_tag: Option<String>,
pub karma: Option<i32>,
pub locked: bool,
pub meets_testing_requirements: bool,
pub notes: String,
pub pushed: bool,
pub release: Release,
pub request: Option<UpdateRequest>,
pub require_bugs: bool,
pub require_testcases: bool,
pub requirements: Option<String>,
pub severity: UpdateSeverity,
pub stable_days: Option<u32>,
pub stable_karma: Option<i32>,
pub status: UpdateStatus,
pub suggest: UpdateSuggestion,
pub test_cases: Option<Vec<TestCase>>,
pub test_gating_status: Option<TestGatingStatus>,
pub title: String,
pub unstable_karma: Option<i32>,
#[deprecated(since = "2.0.0")]
#[serde(rename = "updateid")]
update_id: Option<UpdateID>,
#[serde(rename = "type")]
pub update_type: UpdateType,
pub url: String,
pub user: User,
pub version_hash: String,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for Update {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let builds = if !self.builds.is_empty() {
self.builds
.iter()
.map(|b| b.nvr.as_str())
.collect::<Vec<&str>>()
.join("\n")
} else {
String::from("(None)")
};
let bugs = if !self.bugs.is_empty() {
self.bugs
.iter()
.map(|b| b.bug_id.to_string())
.collect::<Vec<String>>()
.join(" ")
} else {
String::from("(None)")
};
let test_cases = match &self.test_cases {
Some(test_cases) => {
if !test_cases.is_empty() {
test_cases
.iter()
.map(|t| t.name.as_str())
.collect::<Vec<&str>>()
.join(" ")
} else {
"(None)".to_string()
}
},
None => "(None)".to_string(),
};
writeln!(f, "Update {}:", &self.alias)?;
writeln!(f, "{}", &self.notes)?;
writeln!(f)?;
writeln!(f, "State: {}", self.status)?;
writeln!(f, "Submitter: {}", &self.user.name)?;
writeln!(f)?;
writeln!(f, "Builds:")?;
writeln!(f, "{}", &builds)?;
writeln!(f)?;
writeln!(f, "Bugs: {}", &bugs)?;
writeln!(f, "Test Cases: {}", &test_cases)?;
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct UpdateSummary {
pub alias: String,
pub title: String,
}
impl Display for UpdateSummary {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}: {}", self.alias, self.title)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct User {
pub avatar: Option<String>,
pub email: Option<String>,
pub groups: Vec<Group>,
pub id: u32,
pub name: String,
pub openid: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl Display for User {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let email = match &self.email {
Some(email) => email.as_str(),
None => "(None)",
};
let groups: String = self
.groups
.iter()
.map(|g| g.name.as_str())
.collect::<Vec<&str>>()
.join(", ");
writeln!(f, "User {}:", &self.name)?;
writeln!(f, "E-Mail: {email}")?;
writeln!(f, "Groups: {}", &groups)?;
Ok(())
}
}