1
- use std:: process:: exit;
1
+ use std:: { collections :: BTreeMap , process:: exit} ;
2
2
3
3
use chrono:: NaiveDate ;
4
4
use indexmap:: IndexMap ;
@@ -35,6 +35,16 @@ async fn main() {
35
35
}
36
36
} ;
37
37
38
+ // TODO: Fetch this from classplanner or somewhere when we have access to a useful API.
39
+ let known_region_aliases = KnownRegions ( btreemap ! {
40
+ "Cape Town" => vec![ "South Africa" , "SouthAfrica" , "ZA" , "ZA Cape Town" ] ,
41
+ "Glasgow" => vec![ "Scotland" ] ,
42
+ "London" => vec![ ] ,
43
+ "North West" => vec![ "NW" , "Manchester" ] ,
44
+ "Sheffield" => vec![ ] ,
45
+ "West Midlands" => vec![ "WM" , "WestMidlands" , "West-Midlands" , "Birmingham" ] ,
46
+ } ) ;
47
+
38
48
let github_token =
39
49
std:: env:: var ( "GH_TOKEN" ) . expect ( "GH_TOKEN wasn't set - must be set to a GitHub API token" ) ;
40
50
let octocrab = octocrab_for_token ( github_token) . expect ( "Failed to get octocrab" ) ;
@@ -46,9 +56,16 @@ async fn main() {
46
56
register_sheet_id : "" . to_owned ( ) ,
47
57
course_schedule,
48
58
} ;
49
- let result = validate_pr ( & octocrab, course, & module_name, & github_org_name, pr_number)
50
- . await
51
- . expect ( "Failed to validate PR" ) ;
59
+ let result = validate_pr (
60
+ & octocrab,
61
+ course,
62
+ & module_name,
63
+ & github_org_name,
64
+ pr_number,
65
+ & known_region_aliases,
66
+ )
67
+ . await
68
+ . expect ( "Failed to validate PR" ) ;
52
69
match result {
53
70
ValidationResult :: Ok => { }
54
71
ValidationResult :: CouldNotMatch => {
@@ -69,6 +86,15 @@ async fn main() {
69
86
. expect ( "Failed to create comment with validation error" ) ;
70
87
exit ( 2 ) ;
71
88
}
89
+ ValidationResult :: UnknownRegion => {
90
+ eprintln ! ( "Validation error: Could not find region in PR title" ) ;
91
+ octocrab
92
+ . issues ( github_org_name, module_name. clone ( ) )
93
+ . create_comment ( pr_number, UNKNOWN_REGION_COMMENT )
94
+ . await
95
+ . expect ( "Failed to create comment with validation error" ) ;
96
+ exit ( 2 ) ;
97
+ }
72
98
}
73
99
}
74
100
@@ -80,14 +106,19 @@ If this PR is not coursework, please add the NotCoursework label (and message on
80
106
81
107
const BAD_TITLE_COMMENT_PREFIX : & str = r#"Your PR's title isn't in the expected format.
82
108
83
- Please check its title is in the correct format, and update it .
109
+ Please check the expected title format, and update yours to match .
84
110
85
111
Reason: "# ;
86
112
113
+ const UNKNOWN_REGION_COMMENT : & str = r#"Your PR's title didn't contain a known region.
114
+
115
+ Please check the expected title format, and make sure your region is in the correct place and spelled correctly."# ;
116
+
87
117
enum ValidationResult {
88
118
Ok ,
89
119
CouldNotMatch ,
90
120
BadTitleFormat { reason : String } ,
121
+ UnknownRegion ,
91
122
}
92
123
93
124
async fn validate_pr (
@@ -96,6 +127,7 @@ async fn validate_pr(
96
127
module_name : & str ,
97
128
github_org_name : & str ,
98
129
pr_number : u64 ,
130
+ known_region_aliases : & KnownRegions ,
99
131
) -> Result < ValidationResult , Error > {
100
132
let course = course_schedule
101
133
. with_assignments ( octocrab, github_org_name)
@@ -124,7 +156,7 @@ async fn validate_pr(
124
156
& course. modules [ module_name] ,
125
157
user_prs,
126
158
Vec :: new ( ) ,
127
- & Region ( "London" . to_owned ( ) ) ,
159
+ & ARBITRARY_REGION ,
128
160
)
129
161
. map_err ( |err| err. context ( "Failed to match PRs to assignments" ) ) ?;
130
162
@@ -140,7 +172,11 @@ async fn validate_pr(
140
172
reason : "Wrong number of parts separated by |s" . to_owned ( ) ,
141
173
} ) ;
142
174
}
143
- // TODO: Validate Regions when they're known (0)
175
+
176
+ if !known_region_aliases. is_known_ignoring_case ( title_sections[ 0 ] . trim ( ) ) {
177
+ return Ok ( ValidationResult :: UnknownRegion ) ;
178
+ }
179
+
144
180
// TODO: Validate cohorts when they're known (1)
145
181
let sprint_regex = Regex :: new ( r"^(S|s)print \d+$" ) . unwrap ( ) ;
146
182
let sprint_section = title_sections[ 3 ] . trim ( ) ;
@@ -157,6 +193,25 @@ async fn validate_pr(
157
193
Ok ( ValidationResult :: Ok )
158
194
}
159
195
196
+ struct KnownRegions ( BTreeMap < & ' static str , Vec < & ' static str > > ) ;
197
+
198
+ impl KnownRegions {
199
+ fn is_known_ignoring_case ( & self , possible_region : & str ) -> bool {
200
+ let possible_region_lower = possible_region. to_ascii_lowercase ( ) ;
201
+ for ( known_region, known_region_aliases) in & self . 0 {
202
+ if known_region. to_ascii_lowercase ( ) == possible_region_lower {
203
+ return true ;
204
+ }
205
+ for known_region_alias in known_region_aliases {
206
+ if known_region_alias. to_ascii_lowercase ( ) == possible_region_lower {
207
+ return true ;
208
+ }
209
+ }
210
+ }
211
+ false
212
+ }
213
+ }
214
+
160
215
fn make_fake_course_schedule ( module_name : String ) -> CourseSchedule {
161
216
let fixed_date = NaiveDate :: from_ymd_opt ( 2030 , 1 , 1 ) . unwrap ( ) ;
162
217
let mut sprints = IndexMap :: new ( ) ;
0 commit comments