use std::marker::PhantomData;
use failure::Error;
use serde;
use serde_json;
use helpers::Class;
use action::CommonAction;
use client;
use client_internals::path::Path;
use job::{CommonJob, Job};
use Jenkins;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ShortBuild<T: Build = CommonBuild> {
pub url: String,
pub number: u32,
#[serde(flatten)]
pub(crate) other_fields: Option<serde_json::Value>,
#[serde(skip)]
build_type: PhantomData<T>,
}
impl<T> ShortBuild<T>
where
T: Build,
for<'de> T: serde::Deserialize<'de>,
{
pub fn get_full_build(&self, jenkins_client: &Jenkins) -> Result<T, Error> {
let path = jenkins_client.url_to_path(&self.url);
if let Path::Build { .. } = path {
Ok(jenkins_client.get(&path)?.json()?)
} else {
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)]
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, Error>
where
for<'de> Self::ParentJob: serde::Deserialize<'de>,
{
let path = jenkins_client.url_to_path(&self.url());
if let Path::Build {
job_name,
configuration,
..
} = path
{
Ok(jenkins_client
.get(&Path::Job {
name: job_name,
configuration,
})?
.json()?)
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Build,
}.into())
}
}
fn get_console(&self, jenkins_client: &Jenkins) -> Result<String, Error> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Build {
job_name,
number,
configuration,
} = path
{
Ok(jenkins_client
.get(&Path::ConsoleText {
job_name,
number,
configuration,
})?
.text()?)
} else {
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 {
/// URL for the build
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: 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>,
private_fields {
#[serde(flatten)]
other_fields: serde_json::Value,
}
}
);
specialize!(CommonBuild => Build);
impl CommonBuild {}