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 BareEvent(event::BareEvent),
64 BareEvents(Vec<event::BareEvent>),
65 Events(Box<event::Events>),
66}
67
68#[derive(Deserialize, Debug)]
69#[serde(rename_all = "kebab-case")]
70pub struct Defaults {
71 pub run: Option<RunDefaults>,
72}
73
74#[derive(Deserialize, Debug)]
75#[serde(rename_all = "kebab-case")]
76pub struct RunDefaults {
77 pub shell: Option<String>,
78 pub working_directory: Option<String>,
79}
80
81#[derive(Deserialize, Debug)]
82#[serde(rename_all_fields = "kebab-case", untagged)]
83pub enum Concurrency {
84 Bare(String),
85 Rich {
86 group: String,
87 #[serde(default)]
88 cancel_in_progress: BoE,
89 },
90}
91
92#[derive(Deserialize, Debug)]
93#[serde(rename_all = "kebab-case", untagged)]
94pub enum Job {
95 NormalJob(Box<job::NormalJob>),
96 ReusableWorkflowCallJob(Box<job::ReusableWorkflowCallJob>),
97}
98
99impl Job {
100 pub fn name(&self) -> Option<&str> {
103 match self {
104 Self::NormalJob(job) => job.name.as_deref(),
105 Self::ReusableWorkflowCallJob(job) => job.name.as_deref(),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use crate::{
113 common::expr::BoE,
114 workflow::event::{OptionalBody, WorkflowCall, WorkflowDispatch},
115 };
116
117 use super::{Concurrency, Trigger};
118
119 #[test]
120 fn test_concurrency() {
121 let bare = "foo";
122 let concurrency: Concurrency = serde_yaml::from_str(bare).unwrap();
123 assert!(matches!(concurrency, Concurrency::Bare(_)));
124
125 let rich = "group: foo\ncancel-in-progress: true";
126 let concurrency: Concurrency = serde_yaml::from_str(rich).unwrap();
127 assert!(matches!(
128 concurrency,
129 Concurrency::Rich {
130 group: _,
131 cancel_in_progress: BoE::Literal(true)
132 }
133 ));
134 }
135
136 #[test]
137 fn test_workflow_triggers() {
138 let on = "
139 issues:
140 workflow_dispatch:
141 inputs:
142 foo:
143 type: string
144 workflow_call:
145 inputs:
146 bar:
147 type: string
148 pull_request_target:
149 ";
150
151 let trigger: Trigger = serde_yaml::from_str(on).unwrap();
152 let Trigger::Events(events) = trigger else {
153 panic!("wrong trigger type");
154 };
155
156 assert!(matches!(events.issues, OptionalBody::Default));
157 assert!(matches!(
158 events.workflow_dispatch,
159 OptionalBody::Body(WorkflowDispatch { .. })
160 ));
161 assert!(matches!(
162 events.workflow_call,
163 OptionalBody::Body(WorkflowCall { .. })
164 ));
165 assert!(matches!(events.pull_request_target, OptionalBody::Default));
166 }
167}