1pub struct ValidationResult {
2 pub is_valid: bool,
3 pub issues: Vec<String>,
4}
5
6impl Default for ValidationResult {
7 fn default() -> Self {
8 Self::new()
9 }
10}
11
12impl ValidationResult {
13 pub fn new() -> Self {
14 ValidationResult {
15 is_valid: true,
16 issues: Vec::new(),
17 }
18 }
19
20 pub fn add_issue(&mut self, issue: String) {
21 self.is_valid = false;
22 self.issues.push(issue);
23 }
24}
25
26pub mod gitlab {
28 use serde::{Deserialize, Deserializer, Serialize};
29 use std::collections::HashMap;
30
31 fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
32 where
33 D: Deserializer<'de>,
34 {
35 #[derive(Deserialize)]
36 #[serde(untagged)]
37 enum StringOrVec {
38 String(String),
39 Vec(Vec<String>),
40 }
41 let value = Option::<StringOrVec>::deserialize(deserializer)?;
42 match value {
43 Some(StringOrVec::String(s)) => Ok(Some(vec![s])),
44 Some(StringOrVec::Vec(v)) => Ok(Some(v)),
45 None => Ok(None),
46 }
47 }
48
49 fn deserialize_variables<'de, D>(
50 deserializer: D,
51 ) -> Result<Option<HashMap<String, String>>, D::Error>
52 where
53 D: Deserializer<'de>,
54 {
55 let opt: Option<HashMap<String, serde_yaml::Value>> = Option::deserialize(deserializer)?;
56 Ok(opt.map(|m| {
57 m.into_iter()
58 .map(|(k, v)| {
59 let s = match v {
60 serde_yaml::Value::String(s) => s,
61 serde_yaml::Value::Number(n) => n.to_string(),
62 serde_yaml::Value::Bool(b) => b.to_string(),
63 serde_yaml::Value::Null => String::new(),
64 other => serde_yaml::to_string(&other)
65 .map(|s| s.trim().to_string())
66 .unwrap_or_default(),
67 };
68 (k, s)
69 })
70 .collect()
71 }))
72 }
73
74 #[derive(Debug, Serialize, Deserialize, Clone)]
76 pub struct Pipeline {
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub image: Option<Image>,
80
81 #[serde(
83 default,
84 skip_serializing_if = "Option::is_none",
85 deserialize_with = "deserialize_variables"
86 )]
87 pub variables: Option<HashMap<String, String>>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub stages: Option<Vec<String>>,
92
93 #[serde(
95 default,
96 skip_serializing_if = "Option::is_none",
97 deserialize_with = "deserialize_string_or_vec"
98 )]
99 pub before_script: Option<Vec<String>>,
100
101 #[serde(
103 default,
104 skip_serializing_if = "Option::is_none",
105 deserialize_with = "deserialize_string_or_vec"
106 )]
107 pub after_script: Option<Vec<String>>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub default: Option<serde_yaml::Value>,
112
113 #[serde(flatten)]
115 pub jobs: HashMap<String, Job>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub workflow: Option<Workflow>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub include: Option<Vec<Include>>,
124 }
125
126 #[derive(Debug, Serialize, Deserialize, Clone)]
128 pub struct Job {
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub stage: Option<String>,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub image: Option<Image>,
136
137 #[serde(
139 default,
140 skip_serializing_if = "Option::is_none",
141 deserialize_with = "deserialize_string_or_vec"
142 )]
143 pub script: Option<Vec<String>>,
144
145 #[serde(
147 default,
148 skip_serializing_if = "Option::is_none",
149 deserialize_with = "deserialize_string_or_vec"
150 )]
151 pub before_script: Option<Vec<String>>,
152
153 #[serde(
155 default,
156 skip_serializing_if = "Option::is_none",
157 deserialize_with = "deserialize_string_or_vec"
158 )]
159 pub after_script: Option<Vec<String>>,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub when: Option<String>,
164
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub allow_failure: Option<bool>,
168
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub services: Option<Vec<Service>>,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub tags: Option<Vec<String>>,
176
177 #[serde(
179 default,
180 skip_serializing_if = "Option::is_none",
181 deserialize_with = "deserialize_variables"
182 )]
183 pub variables: Option<HashMap<String, String>>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub dependencies: Option<Vec<String>>,
188
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub artifacts: Option<Artifacts>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub cache: Option<Cache>,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub rules: Option<Vec<Rule>>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub only: Option<Only>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub except: Option<Except>,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub retry: Option<Retry>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub timeout: Option<String>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub parallel: Option<usize>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub template: Option<bool>,
224
225 #[serde(
227 default,
228 skip_serializing_if = "Option::is_none",
229 deserialize_with = "deserialize_string_or_vec"
230 )]
231 pub extends: Option<Vec<String>>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub needs: Option<serde_yaml::Value>,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub interruptible: Option<bool>,
240 }
241
242 #[derive(Debug, Serialize, Deserialize, Clone)]
244 #[serde(untagged)]
245 pub enum Image {
246 Simple(String),
248 Detailed {
250 name: String,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 entrypoint: Option<Vec<String>>,
255 },
256 }
257
258 #[derive(Debug, Serialize, Deserialize, Clone)]
260 #[serde(untagged)]
261 pub enum Service {
262 Simple(String),
264 Detailed {
266 name: String,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 command: Option<Vec<String>>,
271 #[serde(skip_serializing_if = "Option::is_none")]
273 entrypoint: Option<Vec<String>>,
274 },
275 }
276
277 #[derive(Debug, Serialize, Deserialize, Clone)]
279 pub struct Artifacts {
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub paths: Option<Vec<String>>,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub expire_in: Option<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub when: Option<String>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub reports: Option<serde_yaml::Value>,
292 }
293
294 #[derive(Debug, Serialize, Deserialize, Clone)]
296 #[serde(untagged)]
297 pub enum CacheKey {
298 Simple(String),
300 Structured {
302 files: Vec<String>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 prefix: Option<String>,
307 },
308 }
309
310 #[derive(Debug, Serialize, Deserialize, Clone)]
312 pub struct Cache {
313 #[serde(skip_serializing_if = "Option::is_none")]
315 pub key: Option<CacheKey>,
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub paths: Option<Vec<String>>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub when: Option<String>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub policy: Option<String>,
325 }
326
327 #[derive(Debug, Serialize, Deserialize, Clone)]
329 pub struct Rule {
330 #[serde(rename = "if", skip_serializing_if = "Option::is_none")]
332 pub if_: Option<String>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub when: Option<String>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub variables: Option<HashMap<String, String>>,
339 }
340
341 #[derive(Debug, Serialize, Deserialize, Clone)]
343 #[serde(untagged)]
344 pub enum Only {
345 Refs(Vec<String>),
347 Complex {
349 #[serde(skip_serializing_if = "Option::is_none")]
351 refs: Option<Vec<String>>,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 branches: Option<Vec<String>>,
355 #[serde(skip_serializing_if = "Option::is_none")]
357 tags: Option<Vec<String>>,
358 #[serde(skip_serializing_if = "Option::is_none")]
360 variables: Option<Vec<String>>,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 changes: Option<Vec<String>>,
364 },
365 }
366
367 #[derive(Debug, Serialize, Deserialize, Clone)]
369 #[serde(untagged)]
370 pub enum Except {
371 Refs(Vec<String>),
373 Complex {
375 #[serde(skip_serializing_if = "Option::is_none")]
377 refs: Option<Vec<String>>,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 branches: Option<Vec<String>>,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 tags: Option<Vec<String>>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 variables: Option<Vec<String>>,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 changes: Option<Vec<String>>,
390 },
391 }
392
393 #[derive(Debug, Serialize, Deserialize, Clone)]
395 pub struct Workflow {
396 pub rules: Vec<Rule>,
398 }
399
400 #[derive(Debug, Serialize, Deserialize, Clone)]
402 #[serde(untagged)]
403 pub enum Retry {
404 MaxAttempts(u32),
406 Detailed {
408 max: u32,
410 #[serde(skip_serializing_if = "Option::is_none")]
412 when: Option<Vec<String>>,
413 },
414 }
415
416 #[derive(Debug, Serialize, Deserialize, Clone)]
418 #[serde(untagged)]
419 pub enum Include {
420 Local(String),
422 Detailed {
424 #[serde(skip_serializing_if = "Option::is_none")]
426 local: Option<String>,
427 #[serde(skip_serializing_if = "Option::is_none")]
429 remote: Option<String>,
430 #[serde(skip_serializing_if = "Option::is_none")]
432 project: Option<String>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 file: Option<String>,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 template: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 ref_: Option<String>,
442 },
443 }
444}