Skip to main content

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