gh_workflow/
workflow.rs

1//!
2//! The serde representation of Github Actions Workflow.
3
4use derive_setters::Setters;
5use indexmap::IndexMap;
6use merge::Merge;
7use serde::{Deserialize, Serialize};
8
9use crate::concurrency::Concurrency;
10use crate::defaults::Defaults;
11// Import the moved types
12use crate::env::Env;
13use crate::error::Result;
14use crate::generate::Generate;
15use crate::job::Job;
16use crate::permissions::Permissions;
17use crate::secret::Secret;
18use crate::Event;
19
20#[derive(Debug, Default, Serialize, Deserialize, Clone)]
21#[serde(transparent)]
22pub struct Jobs(pub(crate) IndexMap<String, Job>);
23impl Jobs {
24    pub fn add(mut self, key: String, value: Job) -> Self {
25        self.0.insert(key, value);
26        self
27    }
28
29    /// Gets a reference to a job by its key.
30    ///
31    /// # Arguments
32    ///
33    /// * `key` - The key of the job to retrieve
34    ///
35    /// # Returns
36    ///
37    /// Returns `Some(&Job)` if the job exists, `None` otherwise.
38    pub fn get(&self, key: &str) -> Option<&Job> {
39        self.0.get(key)
40    }
41}
42
43/// Represents the configuration for a GitHub workflow.
44///
45/// A workflow is a configurable automated process made up of one or more jobs.
46/// This struct defines the properties that can be set in a workflow YAML file
47/// for GitHub Actions, including the name, environment variables, permissions,
48/// jobs, concurrency settings, and more.
49#[derive(Debug, Default, Setters, Serialize, Deserialize, Clone)]
50#[serde(rename_all = "kebab-case")]
51#[setters(strip_option, into)]
52pub struct Workflow {
53    /// The name of the workflow. GitHub displays the names of your workflows
54    /// under your repository's "Actions" tab.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub name: Option<String>,
57
58    /// Environment variables that can be used in the workflow.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub env: Option<Env>,
61
62    /// The name for workflow runs generated from the workflow.
63    /// GitHub displays the workflow run name in the list of workflow runs.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub run_name: Option<String>,
66
67    /// The event that triggers the workflow. This can include events like
68    /// `push`, `pull_request`, etc.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub on: Option<Event>,
71
72    /// Permissions granted to the `GITHUB_TOKEN` for the workflow.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub permissions: Option<Permissions>,
75
76    /// The jobs that are defined in the workflow.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub jobs: Option<Jobs>,
79
80    /// Concurrency settings for the workflow, allowing control over
81    /// how jobs are executed.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub concurrency: Option<Concurrency>,
84
85    /// Default settings for jobs in the workflow.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub defaults: Option<Defaults>,
88
89    /// Secrets that can be used in the workflow, such as tokens or passwords.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub secrets: Option<IndexMap<String, Secret>>,
92
93    /// The maximum number of minutes a job can run before it is canceled.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub timeout_minutes: Option<u32>,
96}
97
98/// Represents an action that can be triggered by an event in the workflow.
99#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
100#[serde(rename_all = "kebab-case")]
101pub struct EventAction {
102    /// A list of branches that trigger the action.
103    #[serde(skip_serializing_if = "Vec::is_empty")]
104    branches: Vec<String>,
105
106    /// A list of branches that are ignored for the action.
107    #[serde(skip_serializing_if = "Vec::is_empty")]
108    branches_ignore: Vec<String>,
109}
110
111impl Workflow {
112    /// Creates a new `Workflow` with the specified name.
113    pub fn new<T: ToString>(name: T) -> Self {
114        Self { name: Some(name.to_string()), ..Default::default() }
115    }
116
117    /// Converts the `Workflow` to a YAML string representation.
118    pub fn to_string(&self) -> Result<String> {
119        Ok(serde_yaml::to_string(self)?)
120    }
121
122    /// Adds a job to the workflow with the specified ID and job configuration.
123    pub fn add_job<T: ToString, J: Into<Job>>(mut self, id: T, job: J) -> Self {
124        let key = id.to_string();
125        let jobs = self.jobs.take().unwrap_or_default().add(key, job.into());
126
127        self.jobs = Some(jobs);
128        self
129    }
130
131    /// Parses a YAML string into a `Workflow`.
132    pub fn parse(yml: &str) -> Result<Self> {
133        Ok(serde_yaml::from_str(yml)?)
134    }
135
136    /// Generates the workflow using the `Generate` struct.
137    pub fn generate(self) -> Result<()> {
138        Generate::new(self).generate()
139    }
140
141    /// Adds an event to the workflow.
142    pub fn add_event<T: Into<Event>>(mut self, that: T) -> Self {
143        if let Some(mut this) = self.on.take() {
144            this.merge(that.into());
145            self.on = Some(this);
146        } else {
147            self.on = Some(that.into());
148        }
149        self
150    }
151
152    /// Adds an environment variable to the workflow.
153    pub fn add_env<T: Into<Env>>(mut self, new_env: T) -> Self {
154        let mut env = self.env.take().unwrap_or_default();
155
156        env.0.extend(new_env.into().0);
157        self.env = Some(env);
158        self
159    }
160}