11use anyhow:: { anyhow, Result } ;
2+ use chrono_tz:: Tz ;
23use cron:: Schedule ;
34use serde:: Deserialize ;
45use std:: collections:: HashMap ;
56use std:: str:: FromStr ;
67use std:: time:: Duration ;
78
9+ #[ derive( Debug , Clone , Default ) ]
10+ pub struct RunnerConfig {
11+ pub timezone : Option < Tz > ,
12+ }
13+
14+ #[ derive( Debug , Deserialize , Default ) ]
15+ struct RunnerConfigRaw {
16+ timezone : Option < String > ,
17+ }
18+
819#[ derive( Debug , Clone , Default , Deserialize , PartialEq ) ]
920#[ serde( rename_all = "lowercase" ) ]
1021pub enum Concurrency {
@@ -16,8 +27,10 @@ pub enum Concurrency {
1627}
1728
1829#[ derive( Debug , Deserialize ) ]
19- pub struct Config {
20- pub jobs : HashMap < String , JobConfig > ,
30+ struct Config {
31+ #[ serde( default ) ]
32+ runner : RunnerConfigRaw ,
33+ jobs : HashMap < String , JobConfig > ,
2134}
2235
2336#[ derive( Debug , Deserialize ) ]
@@ -30,6 +43,7 @@ pub struct JobConfig {
3043 #[ serde( default ) ]
3144 pub concurrency : Concurrency ,
3245 pub retry : Option < RetryConfigRaw > ,
46+ pub working_dir : Option < String > ,
3347}
3448
3549#[ derive( Debug , Deserialize ) ]
@@ -62,6 +76,7 @@ pub struct Job {
6276 pub timeout : Duration ,
6377 pub concurrency : Concurrency ,
6478 pub retry : Option < RetryConfig > ,
79+ pub working_dir : Option < String > ,
6580}
6681
6782#[ derive( Debug , Clone ) ]
@@ -70,11 +85,20 @@ pub struct RetryConfig {
7085 pub delay : Duration ,
7186}
7287
73- pub fn parse_config ( content : & str ) -> Result < Vec < Job > > {
88+ pub fn parse_config ( content : & str ) -> Result < ( RunnerConfig , Vec < Job > ) > {
7489 let config: Config = serde_yaml:: from_str ( content)
7590 . map_err ( |e| anyhow ! ( "Failed to parse YAML: {}" , e) ) ?;
7691
77- config
92+ let timezone = config
93+ . runner
94+ . timezone
95+ . map ( |s| s. parse :: < Tz > ( ) )
96+ . transpose ( )
97+ . map_err ( |e| anyhow ! ( "Invalid timezone: {}" , e) ) ?;
98+
99+ let runner = RunnerConfig { timezone } ;
100+
101+ let jobs = config
78102 . jobs
79103 . into_iter ( )
80104 . map ( |( id, job) | {
@@ -104,9 +128,12 @@ pub fn parse_config(content: &str) -> Result<Vec<Job>> {
104128 timeout,
105129 concurrency : job. concurrency ,
106130 retry,
131+ working_dir : job. working_dir ,
107132 } )
108133 } )
109- . collect ( )
134+ . collect :: < Result < Vec < _ > > > ( ) ?;
135+
136+ Ok ( ( runner, jobs) )
110137}
111138
112139fn parse_duration ( s : & str ) -> Result < Duration > {
@@ -135,7 +162,7 @@ jobs:
135162 cron: "*/5 * * * *"
136163 run: echo hello
137164"# ;
138- let jobs = parse_config ( yaml) . unwrap ( ) ;
165+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
139166 assert_eq ! ( jobs. len( ) , 1 ) ;
140167 assert_eq ! ( jobs[ 0 ] . id, "hello" ) ;
141168 assert_eq ! ( jobs[ 0 ] . name, "hello" ) ;
@@ -153,7 +180,7 @@ jobs:
153180 cron: "0 * * * *"
154181 run: cargo build
155182"# ;
156- let jobs = parse_config ( yaml) . unwrap ( ) ;
183+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
157184 assert_eq ! ( jobs[ 0 ] . id, "build" ) ;
158185 assert_eq ! ( jobs[ 0 ] . name, "Build Project" ) ;
159186 }
@@ -168,7 +195,7 @@ jobs:
168195 run: sleep 30
169196 timeout: 60s
170197"# ;
171- let jobs = parse_config ( yaml) . unwrap ( ) ;
198+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
172199 assert_eq ! ( jobs[ 0 ] . timeout, Duration :: from_secs( 60 ) ) ;
173200 }
174201
@@ -187,7 +214,7 @@ jobs:
187214 run: echo two
188215 timeout: 30s
189216"# ;
190- let jobs = parse_config ( yaml) . unwrap ( ) ;
217+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
191218 assert_eq ! ( jobs. len( ) , 2 ) ;
192219 }
193220
@@ -207,7 +234,7 @@ jobs:
207234 cron: "* * * * *"
208235 run: echo test
209236"# ;
210- let jobs = parse_config ( yaml) . unwrap ( ) ;
237+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
211238 assert_eq ! ( jobs[ 0 ] . concurrency, Concurrency :: Skip ) ;
212239 }
213240
@@ -236,7 +263,7 @@ jobs:
236263 run: echo 4
237264 concurrency: replace
238265"# ;
239- let jobs = parse_config ( yaml) . unwrap ( ) ;
266+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
240267 let find = |id : & str | jobs. iter ( ) . find ( |j| j. id == id) . unwrap ( ) ;
241268
242269 assert_eq ! ( find( "parallel_job" ) . concurrency, Concurrency :: Parallel ) ;
@@ -261,7 +288,7 @@ jobs:
261288 cron: "* * * * *"
262289 run: echo test
263290"# ;
264- let jobs = parse_config ( yaml) . unwrap ( ) ;
291+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
265292 let find = |id : & str | jobs. iter ( ) . find ( |j| j. id == id) . unwrap ( ) ;
266293
267294 let retry = find ( "with_retry" ) . retry . as_ref ( ) . unwrap ( ) ;
@@ -282,9 +309,65 @@ jobs:
282309 retry:
283310 max: 2
284311"# ;
285- let jobs = parse_config ( yaml) . unwrap ( ) ;
312+ let ( _ , jobs) = parse_config ( yaml) . unwrap ( ) ;
286313 let retry = jobs[ 0 ] . retry . as_ref ( ) . unwrap ( ) ;
287314 assert_eq ! ( retry. max, 2 ) ;
288315 assert_eq ! ( retry. delay, Duration :: from_secs( 1 ) ) ; // default delay
289316 }
317+
318+ #[ test]
319+ fn parse_runner_config ( ) {
320+ let yaml = r#"
321+ runner:
322+ timezone: Asia/Tokyo
323+ jobs:
324+ test:
325+ schedule:
326+ cron: "* * * * *"
327+ run: echo test
328+ "# ;
329+ let ( runner, _) = parse_config ( yaml) . unwrap ( ) ;
330+ assert_eq ! ( runner. timezone, Some ( chrono_tz:: Asia :: Tokyo ) ) ;
331+ }
332+
333+ #[ test]
334+ fn parse_runner_config_defaults ( ) {
335+ let yaml = r#"
336+ jobs:
337+ test:
338+ schedule:
339+ cron: "* * * * *"
340+ run: echo test
341+ "# ;
342+ let ( runner, _) = parse_config ( yaml) . unwrap ( ) ;
343+ assert ! ( runner. timezone. is_none( ) ) ;
344+ }
345+
346+ #[ test]
347+ fn parse_working_dir ( ) {
348+ let yaml = r#"
349+ jobs:
350+ test:
351+ schedule:
352+ cron: "* * * * *"
353+ run: echo test
354+ working_dir: ./scripts
355+ "# ;
356+ let ( _, jobs) = parse_config ( yaml) . unwrap ( ) ;
357+ assert_eq ! ( jobs[ 0 ] . working_dir. as_deref( ) , Some ( "./scripts" ) ) ;
358+ }
359+
360+ #[ test]
361+ fn parse_invalid_timezone ( ) {
362+ let yaml = r#"
363+ runner:
364+ timezone: Invalid/Zone
365+ jobs:
366+ test:
367+ schedule:
368+ cron: "* * * * *"
369+ run: echo test
370+ "# ;
371+ assert ! ( parse_config( yaml) . is_err( ) ) ;
372+ }
290373}
0 commit comments