1use derive_setters::Setters;
5use indexmap::IndexMap;
6use merge::Merge;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::toolchain::{Abi, Arch, Component, System, Target, Toolchain, Vendor, Version};
11use crate::{private, Artifacts, Env, Expression, RetryStrategy};
12
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
15#[serde(transparent)]
16pub struct Step<A> {
17 pub value: StepValue,
19 #[serde(skip)]
20 pub marker: A,
21}
22
23impl From<Step<Run>> for StepValue {
24 fn from(step: Step<Run>) -> Self {
26 step.value
27 }
28}
29
30impl From<Step<Use>> for StepValue {
31 fn from(step: Step<Use>) -> Self {
33 step.value
34 }
35}
36
37#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
39pub struct Use;
40
41#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
43pub struct Run;
44
45pub trait StepType: Sized + private::Sealed {
47 fn to_value(s: Step<Self>) -> StepValue;
49}
50
51impl private::Sealed for Run {}
52impl private::Sealed for Use {}
53
54impl StepType for Run {
55 fn to_value(s: Step<Self>) -> StepValue {
57 s.into()
58 }
59}
60
61impl StepType for Use {
62 fn to_value(s: Step<Self>) -> StepValue {
64 s.into()
65 }
66}
67
68#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
70#[serde(transparent)]
71pub struct Input(#[serde(skip_serializing_if = "IndexMap::is_empty")] pub IndexMap<String, Value>);
72
73impl From<IndexMap<String, Value>> for Input {
74 fn from(value: IndexMap<String, Value>) -> Self {
76 Input(value)
77 }
78}
79
80impl Merge for Input {
81 fn merge(&mut self, other: Self) {
83 self.0.extend(other.0);
84 }
85}
86
87impl Input {
88 pub fn add<S: ToString, V: Into<Value>>(mut self, key: S, value: V) -> Self {
90 self.0.insert(key.to_string(), value.into());
91 self
92 }
93
94 pub fn is_empty(&self) -> bool {
96 self.0.is_empty()
97 }
98}
99
100#[allow(clippy::duplicated_attributes)]
102#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Merge)]
103#[serde(rename_all = "kebab-case")]
104#[setters(
105 strip_option,
106 into,
107 generate_delegates(ty = "Step<Run>", field = "value"),
108 generate_delegates(ty = "Step<Use>", field = "value")
109)]
110pub struct StepValue {
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub id: Option<String>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub name: Option<String>,
118
119 #[serde(skip_serializing_if = "Option::is_none", rename = "if")]
121 pub if_condition: Option<Expression>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 #[setters(skip)]
126 pub uses: Option<String>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub with: Option<Input>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 #[setters(skip)]
135 pub run: Option<String>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub env: Option<Env>,
140
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub timeout_minutes: Option<u32>,
144
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub continue_on_error: Option<bool>,
148
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub working_directory: Option<String>,
152
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub retry: Option<RetryStrategy>,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub artifacts: Option<Artifacts>,
160}
161
162impl StepValue {
163 pub fn run<T: ToString>(cmd: T) -> Self {
165 StepValue { run: Some(cmd.to_string()), ..Default::default() }
166 }
167
168 pub fn uses<Owner: ToString, Repo: ToString, Version: ToString>(
170 owner: Owner,
171 repo: Repo,
172 version: Version,
173 ) -> Self {
174 StepValue {
175 uses: Some(format!(
176 "{}/{}@{}",
177 owner.to_string(),
178 repo.to_string(),
179 version.to_string()
180 )),
181 ..Default::default()
182 }
183 }
184}
185
186impl<T> Step<T> {
188 pub fn add_env<R: Into<Env>>(mut self, new_env: R) -> Self {
190 let mut env = self.value.env.take().unwrap_or_default();
191
192 env.0.extend(new_env.into().0);
193 self.value.env = Some(env);
194 self
195 }
196}
197
198impl Step<()> {
199 pub fn new(name: impl ToString) -> Self {
200 Step {
201 value: StepValue::default().name(name.to_string()),
202 marker: Default::default(),
203 }
204 }
205
206 pub fn uses<Owner: ToString, Repo: ToString, Version: ToString>(
207 mut self,
208 owner: Owner,
209 repo: Repo,
210 version: Version,
211 ) -> Step<Use> {
212 self.value.merge(StepValue::uses(owner, repo, version));
213 Step { value: self.value, marker: Default::default() }
214 }
215
216 pub fn run(mut self, cmd: impl ToString) -> Step<Run> {
217 self.value.merge(StepValue::run(cmd));
218 Step { value: self.value, marker: Default::default() }
219 }
220}
221
222impl Step<Use> {
224 pub fn checkout() -> Step<Use> {
226 Step::new("Checkout Code").uses("actions", "checkout", "v5")
227 }
228
229 pub fn add_with<I: Into<Input>>(mut self, new_with: I) -> Self {
231 let mut with = self.value.with.take().unwrap_or_default();
232 with.merge(new_with.into());
233 if with.0.is_empty() {
234 self.value.with = None;
235 } else {
236 self.value.with = Some(with);
237 }
238
239 self
240 }
241}
242
243impl<S1: ToString, S2: ToString> From<(S1, S2)> for Input {
245 fn from(value: (S1, S2)) -> Self {
247 let mut index_map: IndexMap<String, Value> = IndexMap::new();
248 index_map.insert(value.0.to_string(), Value::String(value.1.to_string()));
249 Input(index_map)
250 }
251}
252
253impl Step<Toolchain> {
254 pub fn toolchain() -> Step<Toolchain> {
255 Step { value: Default::default(), marker: Toolchain::default() }
256 }
257
258 pub fn add_version(mut self, version: Version) -> Self {
259 self.marker.version.push(version);
260 self
261 }
262
263 pub fn add_component(mut self, component: Component) -> Self {
264 self.marker.components.push(component);
265 self
266 }
267
268 pub fn add_stable(mut self) -> Self {
269 self.marker.version.push(Version::Stable);
270 self
271 }
272
273 pub fn add_nightly(mut self) -> Self {
274 self.marker.version.push(Version::Nightly);
275 self
276 }
277
278 pub fn add_clippy(mut self) -> Self {
279 self.marker.components.push(Component::Clippy);
280 self
281 }
282
283 pub fn add_fmt(mut self) -> Self {
284 self.marker.components.push(Component::Rustfmt);
285 self
286 }
287
288 pub fn target(mut self, arch: Arch, vendor: Vendor, system: System, abi: Option<Abi>) -> Self {
289 self.marker.target = Some(Target { arch, vendor, system, abi });
290 self
291 }
292}
293
294impl StepType for Toolchain {
295 fn to_value(s: Step<Self>) -> StepValue {
296 let step: Step<Use> = s.marker.into();
297 StepValue::from(step)
298 }
299}