pub struct ValidationResult {
pub is_valid: bool,
pub issues: Vec<String>,
}
impl Default for ValidationResult {
fn default() -> Self {
Self::new()
}
}
impl ValidationResult {
pub fn new() -> Self {
ValidationResult {
is_valid: true,
issues: Vec::new(),
}
}
pub fn add_issue(&mut self, issue: String) {
self.is_valid = false;
self.issues.push(issue);
}
}
pub mod gitlab {
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVec {
String(String),
Vec(Vec<String>),
}
let value = Option::<StringOrVec>::deserialize(deserializer)?;
match value {
Some(StringOrVec::String(s)) => Ok(Some(vec![s])),
Some(StringOrVec::Vec(v)) => Ok(Some(v)),
None => Ok(None),
}
}
fn deserialize_variables<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<String, String>>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<HashMap<String, serde_yaml::Value>> = Option::deserialize(deserializer)?;
Ok(opt.map(|m| {
m.into_iter()
.map(|(k, v)| {
let s = match v {
serde_yaml::Value::String(s) => s,
serde_yaml::Value::Number(n) => n.to_string(),
serde_yaml::Value::Bool(b) => b.to_string(),
serde_yaml::Value::Null => String::new(),
other => serde_yaml::to_string(&other)
.map(|s| s.trim().to_string())
.unwrap_or_default(),
};
(k, s)
})
.collect()
}))
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Pipeline {
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<Image>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_variables"
)]
pub variables: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stages: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub before_script: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub after_script: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_yaml::Value>,
#[serde(flatten)]
pub jobs: HashMap<String, Job>,
#[serde(skip_serializing_if = "Option::is_none")]
pub workflow: Option<Workflow>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<Include>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Job {
#[serde(skip_serializing_if = "Option::is_none")]
pub stage: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<Image>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub script: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub before_script: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub after_script: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_failure: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub services: Option<Vec<Service>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_variables"
)]
pub variables: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dependencies: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub artifacts: Option<Artifacts>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache: Option<Cache>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rules: Option<Vec<Rule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub only: Option<Only>,
#[serde(skip_serializing_if = "Option::is_none")]
pub except: Option<Except>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<Retry>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_string_or_vec"
)]
pub extends: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub needs: Option<serde_yaml::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interruptible: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Image {
Simple(String),
Detailed {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
entrypoint: Option<Vec<String>>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Service {
Simple(String),
Detailed {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
command: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
entrypoint: Option<Vec<String>>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Artifacts {
#[serde(skip_serializing_if = "Option::is_none")]
pub paths: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_in: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reports: Option<serde_yaml::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum CacheKey {
Simple(String),
Structured {
files: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
prefix: Option<String>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Cache {
#[serde(skip_serializing_if = "Option::is_none")]
pub key: Option<CacheKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pub paths: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Rule {
#[serde(rename = "if", skip_serializing_if = "Option::is_none")]
pub if_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub when: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<HashMap<String, String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Only {
Refs(Vec<String>),
Complex {
#[serde(skip_serializing_if = "Option::is_none")]
refs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
branches: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
variables: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
changes: Option<Vec<String>>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Except {
Refs(Vec<String>),
Complex {
#[serde(skip_serializing_if = "Option::is_none")]
refs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
branches: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
variables: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
changes: Option<Vec<String>>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Workflow {
pub rules: Vec<Rule>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Retry {
MaxAttempts(u32),
Detailed {
max: u32,
#[serde(skip_serializing_if = "Option::is_none")]
when: Option<Vec<String>>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Include {
Local(String),
Detailed {
#[serde(skip_serializing_if = "Option::is_none")]
local: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
remote: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
project: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
file: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
ref_: Option<String>,
},
}
}