1use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::time::Duration;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(untagged)]
13pub enum BoolOrExpression {
14 Bool(bool),
15 Expression(String),
16}
17
18impl Default for BoolOrExpression {
19 fn default() -> Self {
20 BoolOrExpression::Bool(false)
21 }
22}
23
24impl BoolOrExpression {
25 pub fn as_bool(&self) -> bool {
28 match self {
29 BoolOrExpression::Bool(b) => *b,
30 BoolOrExpression::Expression(_) => false,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
37#[serde(rename_all = "camelCase")]
38pub struct Pipeline {
39 pub name: Option<String>,
41
42 pub trigger: Option<Trigger>,
44
45 pub pr: Option<PrTrigger>,
47
48 pub schedules: Option<Vec<Schedule>>,
50
51 pub resources: Option<Resources>,
53
54 #[serde(default, deserialize_with = "deserialize_variables")]
56 pub variables: Vec<Variable>,
57
58 #[serde(default)]
60 pub parameters: Vec<Parameter>,
61
62 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
64 pub stages: Vec<Stage>,
65
66 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
68 pub jobs: Vec<Job>,
69
70 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
72 pub steps: Vec<Step>,
73
74 pub pool: Option<Pool>,
76
77 pub extends: Option<Extends>,
79
80 #[serde(rename = "lockBehavior")]
82 pub lock_behavior: Option<LockBehavior>,
83
84 #[serde(skip)]
87 pub has_template_directives: bool,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(untagged)]
97pub enum Trigger {
98 None,
100 Branches(Vec<String>),
102 Full(TriggerConfig),
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, Default)]
107pub struct TriggerConfig {
108 pub batch: Option<bool>,
109 pub branches: Option<BranchFilter>,
110 pub paths: Option<PathFilter>,
111 pub tags: Option<TagFilter>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, Default)]
115pub struct BranchFilter {
116 #[serde(default)]
117 pub include: Vec<String>,
118 #[serde(default)]
119 pub exclude: Vec<String>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123pub struct PathFilter {
124 #[serde(default)]
125 pub include: Vec<String>,
126 #[serde(default)]
127 pub exclude: Vec<String>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct TagFilter {
132 #[serde(default)]
133 pub include: Vec<String>,
134 #[serde(default)]
135 pub exclude: Vec<String>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(untagged)]
141pub enum PrTrigger {
142 None,
144 Branches(Vec<String>),
146 Full(PrTriggerConfig),
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, Default)]
151#[serde(rename_all = "camelCase")]
152pub struct PrTriggerConfig {
153 pub auto_cancel: Option<bool>,
154 pub branches: Option<BranchFilter>,
155 pub paths: Option<PathFilter>,
156 pub drafts: Option<bool>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct Schedule {
163 pub cron: String,
164 pub display_name: Option<String>,
165 pub branches: Option<BranchFilter>,
166 #[serde(default = "default_true")]
167 pub always: bool,
168 #[serde(default)]
169 pub batch: bool,
170}
171
172fn default_true() -> bool {
173 true
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, Default)]
181pub struct Resources {
182 #[serde(default)]
183 pub repositories: Vec<RepositoryResource>,
184 #[serde(default)]
185 pub containers: Vec<ContainerResource>,
186 #[serde(default)]
187 pub pipelines: Vec<PipelineResource>,
188 #[serde(default)]
189 pub packages: Vec<PackageResource>,
190 #[serde(default)]
191 pub webhooks: Vec<WebhookResource>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct RepositoryResource {
196 pub repository: String,
197 #[serde(rename = "type")]
198 pub repo_type: Option<String>,
199 pub name: Option<String>,
200 #[serde(rename = "ref")]
201 pub git_ref: Option<String>,
202 pub endpoint: Option<String>,
203 pub trigger: Option<Trigger>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ContainerResource {
208 pub container: String,
209 pub image: String,
210 pub endpoint: Option<String>,
211 #[serde(default)]
212 pub env: HashMap<String, String>,
213 #[serde(default)]
214 pub ports: Vec<String>,
215 #[serde(default)]
216 pub volumes: Vec<String>,
217 pub options: Option<String>,
218 #[serde(rename = "mapDockerSocket")]
219 pub map_docker_socket: Option<bool>,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
223#[serde(rename_all = "camelCase")]
224pub struct PipelineResource {
225 pub pipeline: String,
226 pub source: String,
227 pub project: Option<String>,
228 pub trigger: Option<PipelineResourceTrigger>,
229 pub version: Option<String>,
230 pub branch: Option<String>,
231 pub tags: Option<Vec<String>>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct PipelineResourceTrigger {
236 pub enabled: Option<bool>,
237 pub branches: Option<BranchFilter>,
238 pub stages: Option<Vec<String>>,
239 pub tags: Option<Vec<String>>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct PackageResource {
244 pub package: String,
245 #[serde(rename = "type")]
246 pub package_type: String,
247 pub connection: String,
248 pub name: String,
249 pub version: Option<String>,
250 pub tag: Option<String>,
251 pub trigger: Option<bool>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct WebhookResource {
256 pub webhook: String,
257 pub connection: String,
258 #[serde(rename = "type")]
259 pub webhook_type: Option<String>,
260 pub filters: Option<Vec<WebhookFilter>>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct WebhookFilter {
265 pub path: String,
266 pub value: String,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
275#[serde(untagged)]
276pub enum Variable {
277 KeyValue {
279 name: String,
280 value: String,
281 #[serde(default)]
282 readonly: bool,
283 },
284 Group { group: String },
286 Template {
288 template: String,
289 #[serde(default)]
290 parameters: HashMap<String, serde_yaml::Value>,
291 },
292}
293
294fn deserialize_variables<'de, D>(deserializer: D) -> Result<Vec<Variable>, D::Error>
296where
297 D: serde::Deserializer<'de>,
298{
299 use serde::de::{MapAccess, SeqAccess, Visitor};
300
301 struct VariablesVisitor;
302
303 impl<'de> Visitor<'de> for VariablesVisitor {
304 type Value = Vec<Variable>;
305
306 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
307 formatter.write_str("a map of variables or a list of variable definitions")
308 }
309
310 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
311 where
312 A: SeqAccess<'de>,
313 {
314 let mut vars = Vec::new();
315 while let Some(var) = seq.next_element::<Variable>()? {
316 vars.push(var);
317 }
318 Ok(vars)
319 }
320
321 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
322 where
323 M: MapAccess<'de>,
324 {
325 let mut vars = Vec::new();
326 while let Some((key, value)) = map.next_entry::<String, String>()? {
327 vars.push(Variable::KeyValue {
328 name: key,
329 value,
330 readonly: false,
331 });
332 }
333 Ok(vars)
334 }
335 }
336
337 deserializer.deserialize_any(VariablesVisitor)
338}
339
340fn deserialize_tolerant_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
346where
347 D: serde::Deserializer<'de>,
348 T: serde::de::DeserializeOwned,
349{
350 use serde::de::{SeqAccess, Visitor};
351
352 struct TolerantVecVisitor<T>(std::marker::PhantomData<T>);
353
354 impl<'de, T: serde::de::DeserializeOwned> Visitor<'de> for TolerantVecVisitor<T> {
355 type Value = Vec<T>;
356
357 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
358 formatter.write_str("a sequence")
359 }
360
361 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
362 where
363 A: SeqAccess<'de>,
364 {
365 let mut items = Vec::new();
366 while let Some(value) = seq.next_element::<serde_yaml::Value>()? {
369 if is_template_directive(&value) {
374 continue;
375 }
376 if let Ok(item) = serde_yaml::from_value::<T>(value) {
377 items.push(item);
378 }
379 }
381 Ok(items)
382 }
383 }
384
385 deserializer.deserialize_seq(TolerantVecVisitor::<T>(std::marker::PhantomData))
386}
387
388pub fn is_template_directive(value: &serde_yaml::Value) -> bool {
391 if let Some(mapping) = value.as_mapping() {
392 mapping.keys().any(|key| {
393 key.as_str()
394 .is_some_and(|s| s.trim_start().starts_with("${{"))
395 })
396 } else {
397 false
398 }
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct Parameter {
408 pub name: String,
409 pub display_name: Option<String>,
410 #[serde(rename = "type", default)]
411 pub param_type: ParameterType,
412 pub default: Option<serde_yaml::Value>,
413 pub values: Option<Vec<serde_yaml::Value>>,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, Default)]
417#[serde(rename_all = "lowercase")]
418pub enum ParameterType {
419 #[default]
420 String,
421 Number,
422 Boolean,
423 Object,
424 Step,
425 StepList,
426 Job,
427 JobList,
428 Stage,
429 StageList,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
437#[serde(untagged)]
438pub enum Pool {
439 Name(String),
441 Full(PoolSpec),
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
446#[serde(rename_all = "camelCase")]
447pub struct PoolSpec {
448 pub name: Option<String>,
449 pub vm_image: Option<String>,
450 pub demands: Option<PoolDemands>,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(untagged)]
455pub enum PoolDemands {
456 List(Vec<String>),
457 Map(HashMap<String, String>),
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize, Default)]
465#[serde(rename_all = "camelCase")]
466pub struct Stage {
467 pub stage: Option<String>,
469
470 pub display_name: Option<String>,
472
473 #[serde(default)]
475 pub depends_on: DependsOn,
476
477 pub condition: Option<String>,
479
480 #[serde(default, deserialize_with = "deserialize_variables")]
482 pub variables: Vec<Variable>,
483
484 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
486 pub jobs: Vec<Job>,
487
488 pub lock_behavior: Option<LockBehavior>,
490
491 pub template: Option<String>,
493
494 #[serde(default)]
496 pub parameters: HashMap<String, serde_yaml::Value>,
497
498 pub pool: Option<Pool>,
500
501 #[serde(skip)]
505 pub has_template_directives: bool,
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize, Default)]
513#[serde(rename_all = "camelCase")]
514pub struct Job {
515 pub job: Option<String>,
517
518 pub deployment: Option<String>,
520
521 pub display_name: Option<String>,
523
524 #[serde(default)]
526 pub depends_on: DependsOn,
527
528 pub condition: Option<String>,
530
531 pub strategy: Option<Strategy>,
533
534 pub pool: Option<Pool>,
536
537 pub container: Option<ContainerRef>,
539
540 #[serde(default)]
542 pub services: HashMap<String, ContainerRef>,
543
544 #[serde(default, deserialize_with = "deserialize_variables")]
546 pub variables: Vec<Variable>,
547
548 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
550 pub steps: Vec<Step>,
551
552 pub timeout_in_minutes: Option<u32>,
554
555 pub cancel_timeout_in_minutes: Option<u32>,
557
558 #[serde(default)]
560 pub continue_on_error: BoolOrExpression,
561
562 pub workspace: Option<Workspace>,
564
565 pub uses: Option<UsesSpec>,
567
568 pub template: Option<String>,
570
571 #[serde(default)]
573 pub parameters: HashMap<String, serde_yaml::Value>,
574
575 pub environment: Option<Environment>,
577
578 #[serde(skip)]
582 pub has_template_directives: bool,
583}
584
585impl Job {
586 pub fn identifier(&self) -> Option<&str> {
588 self.job.as_deref().or(self.deployment.as_deref())
589 }
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
593#[serde(untagged)]
594pub enum ContainerRef {
595 Image(String),
597 Spec(ContainerSpec),
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
602#[serde(rename_all = "camelCase")]
603pub struct ContainerSpec {
604 pub image: String,
605 pub endpoint: Option<String>,
606 #[serde(default)]
607 pub env: HashMap<String, String>,
608 #[serde(default)]
609 pub ports: Vec<String>,
610 #[serde(default)]
611 pub volumes: Vec<String>,
612 pub options: Option<String>,
613 pub map_docker_socket: Option<bool>,
614 #[serde(rename = "mountReadOnly")]
615 pub mount_read_only: Option<MountReadOnly>,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, Default)]
619pub struct MountReadOnly {
620 pub work: Option<bool>,
621 pub externals: Option<bool>,
622 pub tools: Option<bool>,
623 pub tasks: Option<bool>,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct Workspace {
628 pub clean: Option<WorkspaceClean>,
629}
630
631#[derive(Debug, Clone, Serialize, Deserialize)]
632#[serde(rename_all = "lowercase")]
633pub enum WorkspaceClean {
634 Outputs,
635 Resources,
636 All,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
640pub struct UsesSpec {
641 pub repositories: Option<Vec<String>>,
642 pub pools: Option<Vec<String>>,
643}
644
645#[derive(Debug, Clone, Serialize, Deserialize)]
646#[serde(untagged)]
647pub enum Environment {
648 Name(String),
649 Full(EnvironmentSpec),
650}
651
652#[derive(Debug, Clone, Serialize, Deserialize)]
653#[serde(rename_all = "camelCase")]
654pub struct EnvironmentSpec {
655 pub name: String,
656 pub resource_name: Option<String>,
657 pub resource_id: Option<u64>,
658 pub resource_type: Option<String>,
659 pub tags: Option<Vec<String>>,
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
667#[serde(rename_all = "camelCase")]
668pub struct Strategy {
669 pub matrix: Option<MatrixStrategy>,
671
672 pub parallel: Option<u32>,
674
675 pub max_parallel: Option<u32>,
677
678 pub run_once: Option<DeploymentHooks>,
680 pub rolling: Option<RollingStrategy>,
681 pub canary: Option<CanaryStrategy>,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
685#[serde(untagged)]
686pub enum MatrixStrategy {
687 Inline(HashMap<String, HashMap<String, serde_yaml::Value>>),
689 Expression(String),
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
694#[serde(rename_all = "camelCase")]
695pub struct DeploymentHooks {
696 pub pre_deploy: Option<HookSteps>,
697 pub deploy: Option<HookSteps>,
698 pub route_traffic: Option<HookSteps>,
699 pub post_route_traffic: Option<HookSteps>,
700 pub on_failure: Option<HookSteps>,
701 pub on_success: Option<HookSteps>,
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize)]
705pub struct HookSteps {
706 pub pool: Option<Pool>,
707 #[serde(default, deserialize_with = "deserialize_tolerant_vec")]
708 pub steps: Vec<Step>,
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize)]
712#[serde(rename_all = "camelCase")]
713pub struct RollingStrategy {
714 pub max_parallel: Option<u32>,
715 #[serde(flatten)]
716 pub hooks: DeploymentHooks,
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
720#[serde(rename_all = "camelCase")]
721pub struct CanaryStrategy {
722 pub increments: Vec<u32>,
723 #[serde(flatten)]
724 pub hooks: DeploymentHooks,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize, Default)]
732#[serde(untagged)]
733pub enum DependsOn {
734 #[default]
736 Default,
737 None,
739 Single(String),
741 Multiple(Vec<String>),
743}
744
745impl DependsOn {
746 pub fn as_vec(&self) -> Vec<String> {
747 match self {
748 DependsOn::Default => vec![],
749 DependsOn::None => vec![],
750 DependsOn::Single(s) => vec![s.clone()],
751 DependsOn::Multiple(v) => v.clone(),
752 }
753 }
754
755 pub fn is_explicit_none(&self) -> bool {
756 matches!(self, DependsOn::None)
757 }
758}
759
760#[derive(Debug, Clone, Serialize, Deserialize)]
765#[serde(rename_all = "camelCase")]
766pub struct Step {
767 pub name: Option<String>,
769
770 pub display_name: Option<String>,
772
773 pub condition: Option<String>,
775
776 #[serde(default)]
778 pub continue_on_error: BoolOrExpression,
779
780 #[serde(default = "default_true")]
782 pub enabled: bool,
783
784 pub timeout_in_minutes: Option<u32>,
786
787 pub retry_count_on_task_failure: Option<u32>,
789
790 #[serde(default)]
792 pub env: HashMap<String, String>,
793
794 #[serde(flatten)]
796 pub action: StepAction,
797}
798
799#[derive(Debug, Clone, Serialize, Deserialize)]
801#[serde(untagged)]
802pub enum StepAction {
803 Script(ScriptStep),
805 Bash(BashStep),
807 Pwsh(PwshStep),
809 PowerShell(PowerShellStep),
811 Checkout(CheckoutStep),
813 Task(TaskStep),
815 Template(TemplateStep),
817 Download(DownloadStep),
819 Publish(PublishStep),
821 GetPackage(GetPackageStep),
823 ReviewApp(ReviewAppStep),
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct ScriptStep {
830 pub script: String,
831 pub working_directory: Option<String>,
832 #[serde(default)]
833 pub fail_on_stderr: bool,
834}
835
836#[derive(Debug, Clone, Serialize, Deserialize)]
837#[serde(rename_all = "camelCase")]
838pub struct BashStep {
839 pub bash: String,
840 pub working_directory: Option<String>,
841 #[serde(default)]
842 pub fail_on_stderr: bool,
843}
844
845#[derive(Debug, Clone, Serialize, Deserialize)]
846#[serde(rename_all = "camelCase")]
847pub struct PwshStep {
848 pub pwsh: String,
849 pub working_directory: Option<String>,
850 #[serde(default)]
851 pub fail_on_stderr: bool,
852 pub error_action_preference: Option<String>,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize)]
856#[serde(rename_all = "camelCase")]
857pub struct PowerShellStep {
858 pub powershell: String,
859 pub working_directory: Option<String>,
860 #[serde(default)]
861 pub fail_on_stderr: bool,
862 pub error_action_preference: Option<String>,
863}
864
865#[derive(Debug, Clone, Serialize, Deserialize)]
866#[serde(rename_all = "camelCase")]
867pub struct CheckoutStep {
868 pub checkout: CheckoutSource,
869 #[serde(default)]
870 pub clean: bool,
871 pub fetch_depth: Option<u32>,
872 pub fetch_tags: Option<bool>,
873 #[serde(default)]
874 pub lfs: bool,
875 #[serde(default)]
876 pub submodules: SubmoduleOption,
877 pub path: Option<String>,
878 pub persistent_credentials: Option<bool>,
879}
880
881#[derive(Debug, Clone, Serialize, Deserialize)]
882#[serde(untagged)]
883pub enum CheckoutSource {
884 SelfRepo(CheckoutSelf),
886 None(CheckoutNone),
888 Repository(String),
890}
891
892#[derive(Debug, Clone, Serialize, Deserialize)]
893#[serde(rename_all = "lowercase")]
894pub enum CheckoutSelf {
895 #[serde(rename = "self")]
896 SelfRepo,
897}
898
899#[derive(Debug, Clone, Serialize, Deserialize)]
900#[serde(rename_all = "lowercase")]
901pub enum CheckoutNone {
902 None,
903}
904
905#[derive(Debug, Clone, Serialize, Deserialize, Default)]
906#[serde(untagged)]
907pub enum SubmoduleOption {
908 #[default]
909 False,
910 True,
911 Recursive,
912}
913
914#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct TaskStep {
916 pub task: String,
917 #[serde(default)]
918 pub inputs: HashMap<String, String>,
919}
920
921#[derive(Debug, Clone, Serialize, Deserialize)]
922pub struct TemplateStep {
923 pub template: String,
924 #[serde(default)]
925 pub parameters: HashMap<String, serde_yaml::Value>,
926}
927
928#[derive(Debug, Clone, Serialize, Deserialize)]
929#[serde(rename_all = "camelCase")]
930pub struct DownloadStep {
931 pub download: DownloadSource,
932 pub artifact: Option<String>,
933 pub patterns: Option<String>,
934 pub path: Option<String>,
935}
936
937#[derive(Debug, Clone, Serialize, Deserialize)]
938#[serde(untagged)]
939pub enum DownloadSource {
940 Current(DownloadCurrent),
941 None(DownloadNone),
942 Pipeline(String),
943}
944
945#[derive(Debug, Clone, Serialize, Deserialize)]
946#[serde(rename_all = "lowercase")]
947pub enum DownloadCurrent {
948 Current,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "lowercase")]
953pub enum DownloadNone {
954 None,
955}
956
957#[derive(Debug, Clone, Serialize, Deserialize)]
958#[serde(rename_all = "camelCase")]
959pub struct PublishStep {
960 pub publish: String,
961 pub artifact: Option<String>,
962}
963
964#[derive(Debug, Clone, Serialize, Deserialize)]
965#[serde(rename_all = "camelCase")]
966pub struct GetPackageStep {
967 pub get_package: String,
968 pub path: Option<String>,
969}
970
971#[derive(Debug, Clone, Serialize, Deserialize)]
972#[serde(rename_all = "camelCase")]
973pub struct ReviewAppStep {
974 pub review_app: String,
975}
976
977#[derive(Debug, Clone, Serialize, Deserialize)]
982pub struct Extends {
983 pub template: String,
984 #[serde(default)]
985 pub parameters: HashMap<String, serde_yaml::Value>,
986}
987
988#[derive(Debug, Clone, Serialize, Deserialize)]
993#[serde(rename_all = "lowercase")]
994pub enum LockBehavior {
995 RunLatest,
996 Sequential,
997}
998
999#[derive(Debug, Clone)]
1004pub struct StepResult {
1005 pub step_name: Option<String>,
1006 pub display_name: Option<String>,
1007 pub status: StepStatus,
1008 pub output: String,
1009 pub error: Option<String>,
1010 pub duration: Duration,
1011 pub exit_code: Option<i32>,
1012 pub outputs: HashMap<String, String>,
1013}
1014
1015#[derive(Debug, Clone, PartialEq, Eq)]
1016pub enum StepStatus {
1017 Pending,
1018 Running,
1019 Succeeded,
1020 SucceededWithIssues,
1021 Failed,
1022 Canceled,
1023 Skipped,
1024}
1025
1026#[derive(Debug, Clone)]
1027pub struct JobResult {
1028 pub job_name: String,
1029 pub display_name: Option<String>,
1030 pub status: JobStatus,
1031 pub steps: Vec<StepResult>,
1032 pub duration: Duration,
1033 pub outputs: HashMap<String, String>,
1034}
1035
1036#[derive(Debug, Clone, PartialEq, Eq)]
1037pub enum JobStatus {
1038 Pending,
1039 Running,
1040 Succeeded,
1041 SucceededWithIssues,
1042 Failed,
1043 Canceled,
1044 Skipped,
1045}
1046
1047#[derive(Debug, Clone)]
1048pub struct StageResult {
1049 pub stage_name: String,
1050 pub display_name: Option<String>,
1051 pub status: StageStatus,
1052 pub jobs: Vec<JobResult>,
1053 pub duration: Duration,
1054}
1055
1056#[derive(Debug, Clone, PartialEq, Eq)]
1057pub enum StageStatus {
1058 Pending,
1059 Running,
1060 Succeeded,
1061 SucceededWithIssues,
1062 Failed,
1063 Canceled,
1064 Skipped,
1065}
1066
1067#[derive(Debug, Clone)]
1068pub struct ExecutionContext {
1069 pub pipeline_name: String,
1070 pub env: HashMap<String, String>,
1071 pub working_dir: String,
1072 pub variables: HashMap<String, String>,
1073 pub parameters: HashMap<String, serde_yaml::Value>,
1074}
1075
1076impl ExecutionContext {
1077 pub fn new(pipeline_name: String, working_dir: String) -> Self {
1078 Self {
1079 pipeline_name,
1080 env: HashMap::new(),
1081 working_dir,
1082 variables: HashMap::new(),
1083 parameters: HashMap::new(),
1084 }
1085 }
1086
1087 pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
1088 self.env = env;
1089 self
1090 }
1091
1092 pub fn with_variables(mut self, variables: HashMap<String, String>) -> Self {
1093 self.variables = variables;
1094 self
1095 }
1096
1097 pub fn with_parameters(mut self, parameters: HashMap<String, serde_yaml::Value>) -> Self {
1098 self.parameters = parameters;
1099 self
1100 }
1101}
1102
1103#[derive(Debug, Clone, PartialEq, Default)]
1109pub enum Value {
1110 #[default]
1111 Null,
1112 Bool(bool),
1113 Number(f64),
1114 String(String),
1115 Array(Vec<Value>),
1116 Object(HashMap<String, Value>),
1117}
1118
1119impl Value {
1120 pub fn is_truthy(&self) -> bool {
1121 match self {
1122 Value::Null => false,
1123 Value::Bool(b) => *b,
1124 Value::Number(n) => *n != 0.0,
1125 Value::String(s) => !s.is_empty(),
1126 Value::Array(a) => !a.is_empty(),
1127 Value::Object(o) => !o.is_empty(),
1128 }
1129 }
1130
1131 pub fn as_bool(&self) -> Option<bool> {
1132 match self {
1133 Value::Bool(b) => Some(*b),
1134 _ => None,
1135 }
1136 }
1137
1138 pub fn as_number(&self) -> Option<f64> {
1139 match self {
1140 Value::Number(n) => Some(*n),
1141 Value::String(s) => s.parse().ok(),
1142 _ => None,
1143 }
1144 }
1145
1146 pub fn as_string(&self) -> String {
1147 match self {
1148 Value::Null => "".to_string(),
1149 Value::Bool(b) => b.to_string(),
1150 Value::Number(n) => {
1151 if n.fract() == 0.0 {
1152 (*n as i64).to_string()
1153 } else {
1154 n.to_string()
1155 }
1156 }
1157 Value::String(s) => s.clone(),
1158 Value::Array(_) | Value::Object(_) => self.to_json(),
1159 }
1160 }
1161
1162 pub fn to_json(&self) -> String {
1163 match self {
1164 Value::Null => "null".to_string(),
1165 Value::Bool(b) => b.to_string(),
1166 Value::Number(n) => n.to_string(),
1167 Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
1168 Value::Array(arr) => {
1169 let items: Vec<String> = arr.iter().map(|v| v.to_json()).collect();
1170 format!("[{}]", items.join(","))
1171 }
1172 Value::Object(obj) => {
1173 let items: Vec<String> = obj
1174 .iter()
1175 .map(|(k, v)| format!("\"{}\":{}", k, v.to_json()))
1176 .collect();
1177 format!("{{{}}}", items.join(","))
1178 }
1179 }
1180 }
1181}
1182
1183impl From<bool> for Value {
1184 fn from(b: bool) -> Self {
1185 Value::Bool(b)
1186 }
1187}
1188
1189impl From<i64> for Value {
1190 fn from(n: i64) -> Self {
1191 Value::Number(n as f64)
1192 }
1193}
1194
1195impl From<f64> for Value {
1196 fn from(n: f64) -> Self {
1197 Value::Number(n)
1198 }
1199}
1200
1201impl From<String> for Value {
1202 fn from(s: String) -> Self {
1203 Value::String(s)
1204 }
1205}
1206
1207impl From<&str> for Value {
1208 fn from(s: &str) -> Self {
1209 Value::String(s.to_string())
1210 }
1211}
1212
1213impl<T: Into<Value>> From<Vec<T>> for Value {
1214 fn from(v: Vec<T>) -> Self {
1215 Value::Array(v.into_iter().map(Into::into).collect())
1216 }
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222
1223 #[test]
1224 fn test_value_is_truthy() {
1225 assert!(!Value::Null.is_truthy());
1226 assert!(!Value::Bool(false).is_truthy());
1227 assert!(Value::Bool(true).is_truthy());
1228 assert!(!Value::Number(0.0).is_truthy());
1229 assert!(Value::Number(1.0).is_truthy());
1230 assert!(!Value::String("".to_string()).is_truthy());
1231 assert!(Value::String("hello".to_string()).is_truthy());
1232 }
1233
1234 #[test]
1235 fn test_value_as_string() {
1236 assert_eq!(Value::Null.as_string(), "");
1237 assert_eq!(Value::Bool(true).as_string(), "true");
1238 assert_eq!(Value::Number(42.0).as_string(), "42");
1239 assert_eq!(Value::Number(3.14).as_string(), "3.14");
1240 assert_eq!(Value::String("hello".to_string()).as_string(), "hello");
1241 }
1242
1243 #[test]
1244 fn test_depends_on_as_vec() {
1245 assert_eq!(DependsOn::Default.as_vec(), Vec::<String>::new());
1246 assert_eq!(DependsOn::None.as_vec(), Vec::<String>::new());
1247 assert_eq!(
1248 DependsOn::Single("build".to_string()).as_vec(),
1249 vec!["build".to_string()]
1250 );
1251 assert_eq!(
1252 DependsOn::Multiple(vec!["a".to_string(), "b".to_string()]).as_vec(),
1253 vec!["a".to_string(), "b".to_string()]
1254 );
1255 }
1256}