github_actions_models/workflow/
mod.rs1use indexmap::IndexMap;
11use serde::Deserialize;
12
13use crate::common::{
14 Env, Permissions,
15 expr::{BoE, LoE},
16};
17
18pub mod event;
19pub mod job;
20
21#[derive(Deserialize, Debug)]
23#[serde(rename_all = "kebab-case")]
24pub struct Workflow {
25 pub name: Option<String>,
26 pub run_name: Option<String>,
27 pub on: Trigger,
28 #[serde(default)]
29 pub permissions: Permissions,
30 #[serde(default)]
31 pub env: LoE<Env>,
32 pub defaults: Option<Defaults>,
33 pub concurrency: Option<Concurrency>,
34 pub jobs: IndexMap<String, Job>,
35}
36
37#[derive(Deserialize, Debug)]
61#[serde(rename_all = "snake_case", untagged)]
62pub enum Trigger {
63 Events(Box<event::Events>),
67 BareEvent(event::BareEvent),
68 BareEvents(Vec<event::BareEvent>),
69}
70
71#[derive(Deserialize, Debug)]
72#[serde(rename_all = "kebab-case")]
73pub struct Defaults {
74 pub run: Option<RunDefaults>,
75}
76
77#[derive(Deserialize, Debug)]
78#[serde(rename_all = "kebab-case")]
79pub struct RunDefaults {
80 pub shell: Option<LoE<String>>,
81 pub working_directory: Option<String>,
82}
83
84#[derive(Deserialize, Debug)]
85#[serde(rename_all_fields = "kebab-case", untagged)]
86pub enum Concurrency {
87 Bare(String),
88 Rich {
89 group: String,
90 #[serde(default)]
91 cancel_in_progress: BoE,
92 },
93}
94
95#[derive(Deserialize, Debug)]
96#[serde(rename_all = "kebab-case", untagged)]
97pub enum Job {
98 NormalJob(Box<job::NormalJob>),
99 ReusableWorkflowCallJob(Box<job::ReusableWorkflowCallJob>),
100}
101
102impl Job {
103 pub fn name(&self) -> Option<&str> {
106 match self {
107 Self::NormalJob(job) => job.name.as_deref(),
108 Self::ReusableWorkflowCallJob(job) => job.name.as_deref(),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::{
116 common::expr::BoE,
117 workflow::event::{OptionalBody, WorkflowCall, WorkflowDispatch},
118 };
119
120 use super::{Concurrency, Trigger};
121
122 #[test]
123 fn test_concurrency() {
124 let bare = "foo";
125 let concurrency: Concurrency = yaml_serde::from_str(bare).unwrap();
126 assert!(matches!(concurrency, Concurrency::Bare(_)));
127
128 let rich = "group: foo\ncancel-in-progress: true";
129 let concurrency: Concurrency = yaml_serde::from_str(rich).unwrap();
130 assert!(matches!(
131 concurrency,
132 Concurrency::Rich {
133 group: _,
134 cancel_in_progress: BoE::Literal(true)
135 }
136 ));
137 }
138
139 #[test]
140 fn test_workflow_triggers() {
141 let on = "
142 issues:
143 workflow_dispatch:
144 inputs:
145 foo:
146 type: string
147 workflow_call:
148 inputs:
149 bar:
150 type: string
151 pull_request_target:
152 ";
153
154 let trigger: Trigger = yaml_serde::from_str(on).unwrap();
155 let Trigger::Events(events) = trigger else {
156 panic!("wrong trigger type");
157 };
158
159 assert!(matches!(events.issues, OptionalBody::Default));
160 assert!(matches!(
161 events.workflow_dispatch,
162 OptionalBody::Body(WorkflowDispatch { .. })
163 ));
164 assert!(matches!(
165 events.workflow_call,
166 OptionalBody::Body(WorkflowCall { .. })
167 ));
168 assert!(matches!(events.pull_request_target, OptionalBody::Default));
169 }
170}