1use cuenv_core::ci::{CI, RunnerMapping, StringOrVec};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
14#[serde(rename_all = "camelCase")]
15pub struct GitHubConfig {
16 pub runner: Option<StringOrVec>,
18 pub runners: Option<RunnerMapping>,
20 pub cachix: Option<CachixConfig>,
22 pub artifacts: Option<ArtifactsConfig>,
24 pub trusted_publishing: Option<TrustedPublishingConfig>,
26 pub paths_ignore: Option<Vec<String>>,
28 pub permissions: Option<HashMap<String, String>>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
37#[serde(rename_all = "camelCase")]
38pub struct TrustedPublishingConfig {
39 pub crates_io: Option<bool>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(rename_all = "camelCase")]
49pub struct CachixConfig {
50 pub name: String,
52 pub auth_token: Option<String>,
54 pub push_filter: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
60#[serde(rename_all = "camelCase")]
61pub struct ArtifactsConfig {
62 pub paths: Option<Vec<String>>,
64 pub if_no_files_found: Option<String>,
66}
67
68pub trait GitHubConfigExt {
73 fn github_config_for_pipeline(&self, pipeline_name: &str) -> GitHubConfig;
78}
79
80impl GitHubConfigExt for CI {
81 fn github_config_for_pipeline(&self, pipeline_name: &str) -> GitHubConfig {
82 let global = self
83 .provider
84 .as_ref()
85 .and_then(|p| p.get("github"))
86 .and_then(|v| serde_json::from_value::<GitHubConfig>(v.clone()).ok())
87 .unwrap_or_default();
88
89 let pipeline_config = self
90 .pipelines
91 .get(pipeline_name)
92 .and_then(|p| p.provider.as_ref())
93 .and_then(|p| p.get("github"))
94 .and_then(|v| serde_json::from_value::<GitHubConfig>(v.clone()).ok());
95
96 match pipeline_config {
97 Some(pipeline) => GitHubConfig {
98 runner: pipeline.runner.clone().or(global.runner),
99 runners: pipeline.runners.clone().or(global.runners),
100 cachix: pipeline.cachix.clone().or(global.cachix),
101 artifacts: pipeline.artifacts.clone().or(global.artifacts),
102 trusted_publishing: pipeline
103 .trusted_publishing
104 .clone()
105 .or(global.trusted_publishing),
106 paths_ignore: pipeline.paths_ignore.clone().or(global.paths_ignore),
107 permissions: pipeline.permissions.clone().or(global.permissions),
108 },
109 None => global,
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use cuenv_core::ci::{Pipeline, PipelineTask, TaskRef};
118 use serde_json::json;
119 use std::collections::BTreeMap;
120
121 #[test]
122 fn test_github_config_merge() {
123 let ci = CI {
124 provider: Some(
125 serde_json::from_value(json!({
126 "github": {
127 "runner": "ubuntu-latest",
128 "cachix": {
129 "name": "my-cache"
130 }
131 }
132 }))
133 .unwrap(),
134 ),
135 pipelines: BTreeMap::from([
136 (
137 "ci".to_string(),
138 Pipeline {
139 tasks: vec![PipelineTask::Simple(TaskRef::from_name("test"))],
140 provider: Some(
141 serde_json::from_value(json!({
142 "github": {
143 "runner": "self-hosted"
144 }
145 }))
146 .unwrap(),
147 ),
148 ..Default::default()
149 },
150 ),
151 (
152 "release".to_string(),
153 Pipeline {
154 tasks: vec![PipelineTask::Simple(TaskRef::from_name("deploy"))],
155 ..Default::default()
156 },
157 ),
158 ]),
159 ..Default::default()
160 };
161
162 let ci_config = ci.github_config_for_pipeline("ci");
164 assert_eq!(
165 ci_config.runner,
166 Some(StringOrVec::String("self-hosted".to_string()))
167 );
168 assert!(ci_config.cachix.is_some()); let release_config = ci.github_config_for_pipeline("release");
172 assert_eq!(
173 release_config.runner,
174 Some(StringOrVec::String("ubuntu-latest".to_string()))
175 );
176 }
177
178 #[test]
179 fn test_github_config_default() {
180 let config = GitHubConfig::default();
181 assert!(config.runner.is_none());
182 assert!(config.runners.is_none());
183 assert!(config.cachix.is_none());
184 assert!(config.artifacts.is_none());
185 assert!(config.trusted_publishing.is_none());
186 assert!(config.paths_ignore.is_none());
187 assert!(config.permissions.is_none());
188 }
189
190 #[test]
191 fn test_trusted_publishing_config_default() {
192 let config = TrustedPublishingConfig::default();
193 assert!(config.crates_io.is_none());
194 }
195
196 #[test]
197 fn test_artifacts_config_default() {
198 let config = ArtifactsConfig::default();
199 assert!(config.paths.is_none());
200 assert!(config.if_no_files_found.is_none());
201 }
202
203 #[test]
204 fn test_cachix_config_serde() {
205 let config = CachixConfig {
206 name: "my-cache".to_string(),
207 auth_token: Some("CACHIX_TOKEN".to_string()),
208 push_filter: Some(".*".to_string()),
209 };
210 let json = serde_json::to_string(&config).unwrap();
211 assert!(json.contains("my-cache"));
212 assert!(json.contains("CACHIX_TOKEN"));
213
214 let parsed: CachixConfig = serde_json::from_str(&json).unwrap();
215 assert_eq!(parsed.name, "my-cache");
216 }
217
218 #[test]
219 fn test_github_config_serde() {
220 let json = json!({
221 "runner": "ubuntu-latest",
222 "cachix": {
223 "name": "test-cache"
224 },
225 "pathsIgnore": ["*.md", "docs/*"]
226 });
227 let config: GitHubConfig = serde_json::from_value(json).unwrap();
228 assert_eq!(
229 config.runner,
230 Some(StringOrVec::String("ubuntu-latest".to_string()))
231 );
232 assert!(config.cachix.is_some());
233 assert_eq!(config.paths_ignore.as_ref().unwrap().len(), 2);
234 }
235
236 #[test]
237 fn test_github_config_for_nonexistent_pipeline() {
238 let ci = CI {
239 provider: Some(
240 serde_json::from_value(json!({
241 "github": {
242 "runner": "ubuntu-latest"
243 }
244 }))
245 .unwrap(),
246 ),
247 ..Default::default()
248 };
249
250 let config = ci.github_config_for_pipeline("nonexistent");
252 assert_eq!(
253 config.runner,
254 Some(StringOrVec::String("ubuntu-latest".to_string()))
255 );
256 }
257
258 #[test]
259 fn test_github_config_no_global_config() {
260 let ci = CI::default();
261
262 let config = ci.github_config_for_pipeline("any");
263 assert!(config.runner.is_none());
265 }
266
267 #[test]
268 fn test_github_config_with_permissions() {
269 let mut permissions = HashMap::new();
270 permissions.insert("contents".to_string(), "read".to_string());
271 permissions.insert("packages".to_string(), "write".to_string());
272
273 let config = GitHubConfig {
274 permissions: Some(permissions),
275 ..Default::default()
276 };
277
278 let perms = config.permissions.unwrap();
279 assert_eq!(perms.get("contents"), Some(&"read".to_string()));
280 assert_eq!(perms.get("packages"), Some(&"write".to_string()));
281 }
282
283 #[test]
284 fn test_github_config_equality() {
285 let config1 = GitHubConfig {
286 runner: Some(StringOrVec::String("ubuntu-latest".to_string())),
287 ..Default::default()
288 };
289 let config2 = GitHubConfig {
290 runner: Some(StringOrVec::String("ubuntu-latest".to_string())),
291 ..Default::default()
292 };
293 assert_eq!(config1, config2);
294 }
295
296 #[test]
297 fn test_trusted_publishing_with_crates_io() {
298 let config = TrustedPublishingConfig {
299 crates_io: Some(true),
300 };
301 assert_eq!(config.crates_io, Some(true));
302
303 let json = serde_json::to_string(&config).unwrap();
304 let parsed: TrustedPublishingConfig = serde_json::from_str(&json).unwrap();
305 assert_eq!(parsed.crates_io, Some(true));
306 }
307}