use std::marker::PhantomData;
use serde::{self, Deserialize, Serialize};
use crate::helpers::Class;
use super::JobBuilder;
use crate::action::CommonAction;
use crate::build::{CommonBuild, ShortBuild};
use crate::client::{self, Result};
use crate::client_internals::{Name, Path};
use crate::queue::ShortQueueItem;
use crate::view::ViewName;
use crate::Jenkins;
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum BallColor {
Blue,
BlueAnime,
Yellow,
YellowAnime,
Red,
RedAnime,
Grey,
GreyAnime,
Disabled,
DisabledAnime,
Aborted,
AbortedAnime,
#[serde(rename = "notbuilt")]
NotBuilt,
#[serde(rename = "notbuilt_anime")]
NotBuiltAnime,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct HealthReport {
pub description: String,
pub icon_class_name: String,
pub icon_url: String,
pub score: u16,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ShortJob<T: Job = CommonJob> {
pub name: String,
pub url: String,
pub color: Option<BallColor>,
#[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)]
job_type: PhantomData<T>,
}
impl<T> ShortJob<T>
where
T: Job,
for<'de> T: Deserialize<'de>,
{
pub fn get_full_job(&self, jenkins_client: &Jenkins) -> Result<T> {
let path = jenkins_client.url_to_path(&self.url);
if let Path::Job { .. } = path {
return Ok(jenkins_client.get(&path)?.json()?);
} else if let Path::InFolder { path: sub_path, .. } = &path {
if let Path::Job { .. } = sub_path.as_ref() {
return Ok(jenkins_client.get(&path)?.json()?);
}
}
Err(client::Error::InvalidUrl {
url: self.url.clone(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
#[derive(Debug)]
pub struct JobName<'a>(pub &'a str);
impl<'a> From<&'a str> for JobName<'a> {
fn from(v: &'a str) -> JobName<'a> {
JobName(v)
}
}
impl<'a> From<&'a String> for JobName<'a> {
fn from(v: &'a String) -> JobName<'a> {
JobName(v)
}
}
impl<'a> From<&'a ShortJob> for JobName<'a> {
fn from(v: &'a ShortJob) -> JobName<'a> {
JobName(&v.name)
}
}
impl<'a, T: Job> From<&'a T> for JobName<'a> {
fn from(v: &'a T) -> JobName<'a> {
JobName(v.name())
}
}
pub trait Job {
fn url(&self) -> &str;
fn name(&self) -> &str;
fn enable(&self, jenkins_client: &Jenkins) -> Result<()> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job {
name,
configuration: None,
} = path
{
let _ = jenkins_client.post(&Path::JobEnable { name })?;
Ok(())
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
fn disable(&self, jenkins_client: &Jenkins) -> Result<()> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job {
name,
configuration: None,
} = path
{
let _ = jenkins_client.post(&Path::JobDisable { name })?;
Ok(())
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
fn add_to_view<'a, V>(&self, jenkins_client: &Jenkins, view_name: V) -> Result<()>
where
V: Into<ViewName<'a>>,
{
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job {
name,
configuration: None,
} = path
{
let _ = jenkins_client.post(&Path::AddJobToView {
job_name: name,
view_name: Name::Name(view_name.into().0),
})?;
Ok(())
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
fn remove_from_view<'a, V>(&self, jenkins_client: &Jenkins, view_name: V) -> Result<()>
where
V: Into<ViewName<'a>>,
{
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job {
name,
configuration: None,
} = path
{
let _ = jenkins_client.post(&Path::RemoveJobFromView {
job_name: name,
view_name: Name::Name(view_name.into().0),
})?;
Ok(())
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
fn get_config_xml(&self, jenkins_client: &Jenkins) -> Result<String> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job { name, .. } = path {
return Ok(jenkins_client
.get(&Path::ConfigXML {
job_name: name,
folder_name: None,
})?
.text()?);
} else if let Path::InFolder {
path: sub_path,
folder_name,
} = &path
{
if let Path::Job { name, .. } = sub_path.as_ref() {
return Ok(jenkins_client
.get(&Path::ConfigXML {
job_name: name.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! job_base_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
),* $(,)*
})*
}
) => {
job_base_with_common_fields_and_impl! {
$(#[$attr])*
pub struct $name<BuildType = CommonBuild> {
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$(private_fields {
$(
$(#[$private_field_attr])*
$private_field: $private_field_type
),*
})*
}
}
};
(
$(#[$attr:meta])*
pub struct $name:ident<BuildType = $build_type: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 name: String,
pub display_name: String,
pub full_display_name: Option<String>,
pub full_name: Option<String>,
pub display_name_or_null: Option<String>,
pub url: String,
pub actions: Vec<Option<CommonAction>>,
#[serde(default)]
pub buildable: bool,
#[serde(default)]
pub last_build: Option<ShortBuild<$build_type>>,
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$($(
$(#[$private_field_attr])*
$private_field: $private_field_type,
)*)*
}
impl Job for $name {
fn url(&self) -> &str {
&self.url
}
fn name(&self) -> &str {
&self.name
}
}
};
}
macro_rules! job_buildable_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
),* $(,)*
})*
}
) => {
job_buildable_with_common_fields_and_impl! {
$(#[$attr])*
pub struct $name<BuildType = CommonBuild> {
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$(private_fields {
$(
$(#[$private_field_attr])*
$private_field: $private_field_type
),*
})*
}
}
};
(
$(#[$attr:meta])*
pub struct $name:ident<BuildType = $build_type:ty> {
$(
$(#[$field_attr:meta])*
pub $field:ident: $field_type:ty,
)*
$(private_fields {
$(
$(#[$private_field_attr:meta])*
$private_field:ident: $private_field_type:ty
),* $(,)*
})*
}
) => {
job_base_with_common_fields_and_impl! {
$(#[$attr])*
pub struct $name<BuildType = $build_type> {
pub color: Option<BallColor>,
pub keep_dependencies: bool,
pub next_build_number: u32,
pub in_queue: bool,
pub first_build: Option<ShortBuild<$build_type>>,
pub last_stable_build: Option<ShortBuild<$build_type>>,
pub last_unstable_build: Option<ShortBuild<$build_type>>,
pub last_successful_build: Option<ShortBuild<$build_type>>,
pub last_unsuccessful_build: Option<ShortBuild<$build_type>>,
pub last_completed_build: Option<ShortBuild<$build_type>>,
pub last_failed_build: Option<ShortBuild<$build_type>>,
pub builds: Vec<ShortBuild>,
pub health_report: Vec<HealthReport>,
pub queue_item: Option<ShortQueueItem>,
$(
$(#[$field_attr])*
pub $field: $field_type,
)*
$($(
$(#[$private_field_attr])*
$private_field: $private_field_type,
)*)*
private_fields {
property: Vec<CommonProperty>,
}
}
}
};
}
job_base_with_common_fields_and_impl!(
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct 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!(CommonJob => Job);
impl CommonJob {}
pub trait BuildableJob: Job + Sized {
fn build(&self, jenkins_client: &Jenkins) -> Result<ShortQueueItem> {
self.builder(jenkins_client)?.send()
}
fn builder<'a, 'b, 'c, 'd>(
&'a self,
jenkins_client: &'b Jenkins,
) -> Result<JobBuilder<'a, 'b, 'c, 'd>> {
JobBuilder::new(self, jenkins_client)
}
}
pub trait SCMPollable: Job + Sized {
fn poll_scm(&self, jenkins_client: &Jenkins) -> Result<()> {
let path = jenkins_client.url_to_path(&self.url());
if let Path::Job {
name,
configuration: None,
} = path
{
let _ = jenkins_client.post(&Path::PollSCMJob { name })?;
Ok(())
} else {
Err(client::Error::InvalidUrl {
url: self.url().to_string(),
expected: client::error::ExpectedType::Job,
}
.into())
}
}
}