1#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8
9use serde::Deserialize;
10use serde::Serialize;
11use serde::Serializer;
12use std::collections::BTreeMap;
13
14mod none {
15 use serde::Deserialize;
16 use serde::Deserializer;
17 use serde::Serializer;
18
19 pub fn serialize<S>(_: &(), ser: S) -> Result<S::Ok, S::Error>
20 where
21 S: Serializer,
22 {
23 ser.serialize_str("none")
24 }
25
26 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<(), D::Error> {
27 let s: &str = Deserialize::deserialize(d)?;
28 if s != "none" {
29 return Err(serde::de::Error::custom("field must be 'none'"));
30 }
31 Ok(())
32 }
33}
34
35fn validate_name<S>(s: &str, ser: S) -> Result<S::Ok, S::Error>
38where
39 S: Serializer,
40{
41 if s.is_empty() {
42 return Err(serde::ser::Error::custom("name cannot be empty"));
43 }
44
45 if s.chars().next().unwrap().is_ascii_digit() {
46 return Err(serde::ser::Error::custom("name cannot start with a number"));
47 }
48
49 if !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
50 return Err(serde::ser::Error::custom(
51 "name must be ascii alphanumeric + '_'",
52 ));
53 }
54
55 ser.serialize_str(s)
56}
57
58#[derive(Debug, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct TriggerBranches {
61 #[serde(skip_serializing_if = "Vec::is_empty")]
62 pub include: Vec<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub exclude: Option<Vec<String>>,
66}
67
68#[derive(Debug, Serialize, Deserialize)]
69#[serde(untagged)]
70#[serde(rename_all = "camelCase")]
71pub enum PrTrigger {
72 None(#[serde(with = "none")] ()),
73 #[serde(rename_all = "camelCase")]
74 Some {
75 auto_cancel: bool,
76 drafts: bool,
77 branches: TriggerBranches,
78 },
79 NoneWorkaround(String),
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84#[serde(untagged)]
85#[serde(rename_all = "camelCase")]
86pub enum CiTrigger {
87 None(#[serde(with = "none")] ()),
88 #[serde(rename_all = "camelCase")]
89 Some {
90 batch: bool,
91 branches: TriggerBranches,
92 },
93 NoneWorkaround(String),
95}
96
97#[derive(Debug, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Schedule {
100 pub cron: String,
102 pub display_name: String,
103 pub branches: TriggerBranches,
104 #[serde(skip_serializing_if = "std::ops::Not::not")]
105 #[serde(default)]
106 pub batch: bool,
107}
108
109#[derive(Debug, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct Variable {
112 pub name: String,
113 pub value: String,
114}
115
116#[derive(Debug, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct Pipeline {
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub name: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub trigger: Option<CiTrigger>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub pr: Option<PrTrigger>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub schedules: Option<Vec<Schedule>>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub variables: Option<Vec<Variable>>,
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub parameters: Option<Vec<Parameter>>,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub resources: Option<Resources>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub stages: Option<Vec<Stage>>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub jobs: Option<Vec<Job>>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub extends: Option<Extends>,
139}
140
141#[derive(Debug, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct Extends {
144 pub template: String,
145 pub parameters: BTreeMap<String, serde_yaml::Value>,
146}
147
148#[derive(Debug, Default, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct Resources {
151 #[serde(skip_serializing_if = "Vec::is_empty")]
152 pub repositories: Vec<ResourcesRepository>,
153}
154
155#[derive(Debug, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct ResourcesRepository {
158 pub repository: String,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub endpoint: Option<String>,
165 pub name: String,
167 #[serde(rename = "ref")]
169 pub r#ref: String,
170 #[serde(rename = "type")]
171 pub r#type: ResourcesRepositoryType,
172}
173
174#[derive(Debug, Serialize, Deserialize)]
175#[serde(rename_all = "lowercase")]
176pub enum ResourcesRepositoryType {
177 Git,
178 GitHub,
179 GitHubEnterprise,
180 Bitbucket,
181}
182
183#[derive(Debug, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct Parameter {
186 pub name: String,
187 pub display_name: String,
188 #[serde(flatten)]
189 pub ty: ParameterType,
190}
191
192#[derive(Debug, Serialize, Deserialize)]
195#[serde(tag = "type", rename_all = "camelCase")]
196pub enum ParameterType {
197 Boolean {
198 #[serde(skip_serializing_if = "Option::is_none")]
199 default: Option<bool>,
200 },
201 String {
202 #[serde(skip_serializing_if = "Option::is_none")]
203 default: Option<String>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 values: Option<Vec<String>>,
206 },
207 Number {
208 #[serde(skip_serializing_if = "Option::is_none")]
209 default: Option<i64>,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 values: Option<Vec<i64>>,
212 },
213 Object {
214 #[serde(skip_serializing_if = "Option::is_none")]
215 default: Option<serde_yaml::Value>,
216 },
217}
218
219#[derive(Debug, Serialize, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct Stage {
222 #[serde(serialize_with = "validate_name")]
225 pub stage: String,
226 pub display_name: String,
227 pub depends_on: Vec<String>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub condition: Option<String>,
230 pub jobs: Vec<Job>,
231}
232
233#[derive(Debug, Serialize, Deserialize)]
234#[serde(untagged)]
235#[serde(rename_all = "camelCase")]
236pub enum Pool {
237 Pool(String),
238 PoolWithMetadata(BTreeMap<String, serde_yaml::Value>),
239}
240
241#[derive(Debug, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct Job {
244 #[serde(serialize_with = "validate_name")]
245 pub job: String,
246 pub display_name: String,
247 pub pool: Pool,
248 pub depends_on: Vec<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub condition: Option<String>,
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub variables: Option<Vec<Variable>>,
253 pub steps: Vec<serde_yaml::Value>,
259}