use std::marker::PhantomData;
use serde::{self, Deserialize, Serialize};
use crate::helpers::Class;
use crate::action::CommonAction;
use crate::client::{self, Result};
use crate::client_internals::path::Path;
use crate::job::{CommonJob, Job};
use crate::Jenkins;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ShortBuild<T: Build = CommonBuild> {
pub url: String,
pub number: u32,
pub display_name: Option<String>,
pub timestamp: Option<u64>,
#[cfg(not(feature = "extra-fields-visibility"))]
#[serde(flatten)]
pub(crate) extra_fields: Option<serde_json::Value>,
#[cfg(feature = "extra-fields-visibility")]
#[serde(flatten)]
pub extra_fields: Option<serde_json::Value>,
#[serde(skip)]
build_type: PhantomData<T>,
}
impl<T> ShortBuild<T>
where
T: Build,
for<'de> T: Deserialize<'de>,
{
pub fn get_full_build(&self, jenkins_client: &Jenkins) -> Result<T> {
let path = jenkins_client.url_to_path(&self.url);
if let Path::Build { .. } = path {
return Ok(jenkins_client.get(&path)?.json()?);
} else if let Path::InFolder { path: sub_path, .. } = &path {
if let Path::Build { .. } = sub_path.as_ref() {
return Ok(jenkins_client.get(&path)?.json()?);
}
}
Err(client::Error::InvalidUrl {
url: self.url.clone(),
expected: client::error::ExpectedType::Build,
}
.into())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BuildStatus {
Success,
Unstable,
Failure,
NotBuilt,
Aborted,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
pub display_path: Option<String>,
pub file_name: String,
pub relative_path: String,
}
#[derive(Debug, PartialEq, Clone)]
pub enum BuildNumber {
LastBuild,
LastSuccessfulBuild,
LastStableBuild,
LastCompletedBuild,
LastFailedBuild,
LastUnsuccessfulBuild,
Number(u32),
UnknwonAlias(String),
}
impl ToString for BuildNumber {
fn to_string(&self) -> String {
match self {
BuildNumber::LastBuild => "lastBuild".to_string(),
BuildNumber::LastSuccessfulBuild => "lastSuccessfulBuild".to_string(),
BuildNumber::LastStableBuild => "lastStableBuild".to_string(),
BuildNumber::LastCompletedBuild => "lastCompletedBuild".to_string(),
BuildNumber::LastFailedBuild => "lastFailedBuild".to_string(),
BuildNumber::LastUnsuccessfulBuild => "lastUnsuccessfulBuild".to_string(),
BuildNumber::Number(n) => n.to_string(),
BuildNumber::UnknwonAlias(s) => s.to_string(),
}
}
}
impl<'a> From<&'a str> for BuildNumber {
fn from(v: &'a str) -> BuildNumber {
match v {
"lastBuild" => BuildNumber::LastBuild,
"lastSuccessfulBuild" => BuildNumber::LastSuccessfulBuild,
"lastStableBuild" => BuildNumber::LastStableBuild,
"lastCompletedBuild" => BuildNumber::LastCompletedBuild,
"lastFailedBuild" => BuildNumber::LastFailedBuild,
"lastUnsuccessfulBuild" => BuildNumber::LastUnsuccessfulBuild,
_ => BuildNumber::UnknwonAlias(v.to_string()),
}
}
}
impl From<u32> for BuildNumber {
fn from(v: u32) -> BuildNumber {
BuildNumber::Number(v)
}
}
macro_rules! safe_into_buildnumber {
($type_from:ty) => {
impl From<$type_from> for BuildNumber {
fn from(v: $type_from) -> BuildNumber {
BuildNumber::Number(u32::from(v))
}
}
};
}
macro_rules! into_buildnumber {
($type_from:ty) => {
impl From<$type_from> for BuildNumber {
fn from(v: $type_from) -> BuildNumber {
BuildNumber::Number(v as u32)
}
}
};
}
safe_into_buildnumber!(u8);
safe_into_buildnumber!(u16);
into_buildnumber!(u64);
into_buildnumber!(i8);
into_buildnumber!(i16);
into_buildnumber!(i32);
into_buildnumber!(i64);
pub trait Build {
type ParentJob: Job;
fn url(&self) -> &str;
fn get_job(&self, jenkins_client: &Jenkins) -> Result<Self::ParentJob>
where
for<'de> Self::ParentJob: Deserialize<'de>,
{
let path = jenkins_client.url_to_path(&self.url());
if let Path::Build {
job_name,
configuration,
..
} = path
{
return Ok(jenkins_client
.get(&Path::Job {
name: job_name,
configuration,
})?
.json()?);
} else if let Path::InFolder {
path: sub_path,
folder_name,
} = &path
{
if let Path::Build {
job_name,
configuration,
..
} = sub_path.as_ref()
{
return Ok(jenkins_client
.get(&Path::InFolder {
folder_name: folder_name.clone(),
path: Box::new(Path::Job {
name: job_name.clone(),
configuration: configuration.clone(),
}),
})?
.json()?);
}
}
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Build,
}
.into())
}
fn get_console(&self, jenkins_client: &Jenkins) -> Result<String> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Build {
job_name,
number,
configuration,
} = path
{
return Ok(jenkins_client
.get(&Path::ConsoleText {
job_name,
number,
configuration,
folder_name: None,
})?
.text()?);
} else if let Path::InFolder {
path: sub_path,
folder_name,
} = &path
{
if let Path::Build {
job_name,
number,
configuration,
} = sub_path.as_ref()
{
return Ok(jenkins_client
.get(&Path::ConsoleText {
job_name: job_name.clone(),
number: number.clone(),
configuration: configuration.clone(),
folder_name: Some(folder_name.clone()),
})?
.text()?);
}
}
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Build,
}
.into())
}
}
macro_rules! build_with_common_fields_and_impl {
(
$(#[$attr:meta])*
pub struct $name:ident {
$(
$(#[$field_attr:meta])*
pub $field:ident: $field_type:ty,
)*
$(private_fields {
$(
$(#[$private_field_attr:meta])*
$private_field:ident: $private_field_type:ty
),* $(,)*
})*
}
) => {
build_with_common_fields_and_impl!{
$(#[$attr])*
pub struct $name<ParentJob = CommonJob> {
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$(private_fields {
$(
$(#[$private_field_attr])*
$private_field: $private_field_type
),*
})*
}
}
};
(
$(#[$attr:meta])*
pub struct $name:ident<ParentJob = $parent_job:ty> {
$(
$(#[$field_attr:meta])*
pub $field:ident: $field_type:ty,
)*
$(private_fields {
$(
$(#[$private_field_attr:meta])*
$private_field:ident: $private_field_type:ty
),* $(,)*
})*
}
) => {
$(#[$attr])*
pub struct $name {
pub url: String,
pub number: u32,
pub duration: i64,
pub estimated_duration: i64,
pub timestamp: u64,
pub keep_log: bool,
pub result: Option<BuildStatus>,
pub display_name: String,
pub full_display_name: Option<String>,
pub description: Option<String>,
pub building: bool,
pub id: String,
pub queue_id: i32,
pub actions: Vec<CommonAction>,
pub artifacts: Vec<Artifact>,
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$($(
$(#[$private_field_attr])*
$private_field: $private_field_type,
)*)*
}
impl Build for $name {
type ParentJob = $parent_job;
fn url(&self) -> &str {
&self.url
}
}
};
}
build_with_common_fields_and_impl!(
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CommonBuild<ParentJob = CommonJob> {
#[serde(rename = "_class")]
pub class: Option<String>,
#[cfg(feature = "extra-fields-visibility")]
#[serde(flatten)]
pub extra_fields: serde_json::Value,
private_fields {
#[cfg(not(feature = "extra-fields-visibility"))]
#[serde(flatten)]
extra_fields: serde_json::Value,
}
}
);
specialize!(CommonBuild => Build);
impl CommonBuild {}