#![expect(missing_docs)]
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use std::collections::BTreeMap;
mod none {
use serde::Deserialize;
use serde::Deserializer;
use serde::Serializer;
pub fn serialize<S>(_: &(), ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ser.serialize_str("none")
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<(), D::Error> {
let s: &str = Deserialize::deserialize(d)?;
if s != "none" {
return Err(serde::de::Error::custom("field must be 'none'"));
}
Ok(())
}
}
fn validate_name<S>(s: &str, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if s.is_empty() {
return Err(serde::ser::Error::custom("name cannot be empty"));
}
if s.chars().next().unwrap().is_ascii_digit() {
return Err(serde::ser::Error::custom("name cannot start with a number"));
}
if !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(serde::ser::Error::custom(
"name must be ascii alphanumeric + '_'",
));
}
ser.serialize_str(s)
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TriggerBranches {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub include: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum PrTrigger {
None(#[serde(with = "none")] ()),
#[serde(rename_all = "camelCase")]
Some {
auto_cancel: bool,
drafts: bool,
branches: TriggerBranches,
},
NoneWorkaround(String),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum CiTrigger {
None(#[serde(with = "none")] ()),
#[serde(rename_all = "camelCase")]
Some {
batch: bool,
branches: TriggerBranches,
},
NoneWorkaround(String),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Schedule {
pub cron: String,
pub display_name: String,
pub branches: TriggerBranches,
#[serde(skip_serializing_if = "std::ops::Not::not")]
#[serde(default)]
pub batch: bool,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Variable {
pub name: String,
pub value: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Pipeline {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger: Option<CiTrigger>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pr: Option<PrTrigger>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schedules: Option<Vec<Schedule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<Vec<Variable>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<Vec<Parameter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<Resources>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stages: Option<Vec<Stage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jobs: Option<Vec<Job>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extends: Option<Extends>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Extends {
pub template: String,
pub parameters: BTreeMap<String, serde_yaml::Value>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Resources {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub repositories: Vec<ResourcesRepository>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesRepository {
pub repository: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
pub name: String,
#[serde(rename = "ref")]
pub r#ref: String,
#[serde(rename = "type")]
pub r#type: ResourcesRepositoryType,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ResourcesRepositoryType {
Git,
GitHub,
GitHubEnterprise,
Bitbucket,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Parameter {
pub name: String,
pub display_name: String,
#[serde(flatten)]
pub ty: ParameterType,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ParameterType {
Boolean {
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<bool>,
},
String {
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
values: Option<Vec<String>>,
},
Number {
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
values: Option<Vec<i64>>,
},
Object {
#[serde(skip_serializing_if = "Option::is_none")]
default: Option<serde_yaml::Value>,
},
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stage {
#[serde(serialize_with = "validate_name")]
pub stage: String,
pub display_name: String,
pub depends_on: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
pub jobs: Vec<Job>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum Pool {
Pool(String),
PoolWithMetadata(BTreeMap<String, serde_yaml::Value>),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Job {
#[serde(serialize_with = "validate_name")]
pub job: String,
pub display_name: String,
pub pool: Pool,
pub depends_on: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<Vec<Variable>>,
pub steps: Vec<serde_yaml::Value>,
}