11//! GitHub Check Runs API client.
22
33use anyhow:: Result ;
4+ use serde:: Deserialize ;
5+
6+ use crate :: { client, send_json, GitHubRequest , DEFAULT_API_BASE_URL } ;
47
58/// Check Run status values.
69#[ derive( Debug , Clone ) ]
@@ -20,6 +23,11 @@ pub enum CheckConclusion {
2023 ActionRequired ,
2124}
2225
26+ #[ derive( Debug , Deserialize ) ]
27+ struct CheckRunResponse {
28+ id : u64 ,
29+ }
30+
2331/// Creates a new Check Run and returns its ID.
2432pub async fn create (
2533 installation_token : & str ,
@@ -29,14 +37,42 @@ pub async fn create(
2937 name : & str ,
3038 details_url : Option < & str > ,
3139) -> Result < u64 > {
32- // TODO: implement via reqwest + GitHub REST API
33- // POST /repos/{owner}/{repo}/check-runs
3440 tracing:: info!(
3541 repo = %format!( "{repo_owner}/{repo_name}" ) ,
3642 name,
3743 "creating check run"
3844 ) ;
39- Ok ( 0 ) // placeholder
45+ create_with_base_url (
46+ DEFAULT_API_BASE_URL ,
47+ installation_token,
48+ repo_owner,
49+ repo_name,
50+ head_sha,
51+ name,
52+ details_url,
53+ )
54+ . await
55+ }
56+
57+ pub async fn create_with_base_url (
58+ api_base_url : & str ,
59+ installation_token : & str ,
60+ repo_owner : & str ,
61+ repo_name : & str ,
62+ head_sha : & str ,
63+ name : & str ,
64+ details_url : Option < & str > ,
65+ ) -> Result < u64 > {
66+ let client = client ( ) ?;
67+ let response = send_json (
68+ & client,
69+ api_base_url,
70+ installation_token,
71+ create_request ( repo_owner, repo_name, head_sha, name, details_url) ,
72+ )
73+ . await ?;
74+ let body: CheckRunResponse = response. json ( ) . await ?;
75+ Ok ( body. id )
4076}
4177
4278/// Updates an existing Check Run with progress output.
@@ -50,7 +86,38 @@ pub async fn update(
5086 summary : & str ,
5187) -> Result < ( ) > {
5288 tracing:: info!( check_run_id, title, "updating check run" ) ;
53- Ok ( ( ) ) // placeholder
89+ update_with_base_url (
90+ DEFAULT_API_BASE_URL ,
91+ installation_token,
92+ repo_owner,
93+ repo_name,
94+ check_run_id,
95+ status,
96+ title,
97+ summary,
98+ )
99+ . await
100+ }
101+
102+ pub async fn update_with_base_url (
103+ api_base_url : & str ,
104+ installation_token : & str ,
105+ repo_owner : & str ,
106+ repo_name : & str ,
107+ check_run_id : u64 ,
108+ status : CheckStatus ,
109+ title : & str ,
110+ summary : & str ,
111+ ) -> Result < ( ) > {
112+ let client = client ( ) ?;
113+ send_json (
114+ & client,
115+ api_base_url,
116+ installation_token,
117+ update_request ( repo_owner, repo_name, check_run_id, status, title, summary) ,
118+ )
119+ . await ?;
120+ Ok ( ( ) )
54121}
55122
56123/// Completes a Check Run.
@@ -64,5 +131,211 @@ pub async fn complete(
64131 summary : & str ,
65132) -> Result < ( ) > {
66133 tracing:: info!( check_run_id, title, "completing check run" ) ;
67- Ok ( ( ) ) // placeholder
134+ complete_with_base_url (
135+ DEFAULT_API_BASE_URL ,
136+ installation_token,
137+ repo_owner,
138+ repo_name,
139+ check_run_id,
140+ conclusion,
141+ title,
142+ summary,
143+ )
144+ . await
145+ }
146+
147+ pub async fn complete_with_base_url (
148+ api_base_url : & str ,
149+ installation_token : & str ,
150+ repo_owner : & str ,
151+ repo_name : & str ,
152+ check_run_id : u64 ,
153+ conclusion : CheckConclusion ,
154+ title : & str ,
155+ summary : & str ,
156+ ) -> Result < ( ) > {
157+ let client = client ( ) ?;
158+ send_json (
159+ & client,
160+ api_base_url,
161+ installation_token,
162+ complete_request (
163+ repo_owner,
164+ repo_name,
165+ check_run_id,
166+ conclusion,
167+ title,
168+ summary,
169+ ) ,
170+ )
171+ . await ?;
172+ Ok ( ( ) )
173+ }
174+
175+ fn create_request (
176+ repo_owner : & str ,
177+ repo_name : & str ,
178+ head_sha : & str ,
179+ name : & str ,
180+ details_url : Option < & str > ,
181+ ) -> GitHubRequest {
182+ let mut body = serde_json:: json!( {
183+ "name" : name,
184+ "head_sha" : head_sha,
185+ "status" : "queued" ,
186+ } ) ;
187+ if let Some ( details_url) = details_url {
188+ body[ "details_url" ] = serde_json:: json!( details_url) ;
189+ }
190+
191+ GitHubRequest {
192+ method : "POST" ,
193+ path : format ! ( "/repos/{repo_owner}/{repo_name}/check-runs" ) ,
194+ body,
195+ }
196+ }
197+
198+ fn update_request (
199+ repo_owner : & str ,
200+ repo_name : & str ,
201+ check_run_id : u64 ,
202+ status : CheckStatus ,
203+ title : & str ,
204+ summary : & str ,
205+ ) -> GitHubRequest {
206+ GitHubRequest {
207+ method : "PATCH" ,
208+ path : format ! ( "/repos/{repo_owner}/{repo_name}/check-runs/{check_run_id}" ) ,
209+ body : serde_json:: json!( {
210+ "status" : status. as_str( ) ,
211+ "output" : {
212+ "title" : title,
213+ "summary" : summary,
214+ }
215+ } ) ,
216+ }
217+ }
218+
219+ fn complete_request (
220+ repo_owner : & str ,
221+ repo_name : & str ,
222+ check_run_id : u64 ,
223+ conclusion : CheckConclusion ,
224+ title : & str ,
225+ summary : & str ,
226+ ) -> GitHubRequest {
227+ GitHubRequest {
228+ method : "PATCH" ,
229+ path : format ! ( "/repos/{repo_owner}/{repo_name}/check-runs/{check_run_id}" ) ,
230+ body : serde_json:: json!( {
231+ "status" : "completed" ,
232+ "conclusion" : conclusion. as_str( ) ,
233+ "output" : {
234+ "title" : title,
235+ "summary" : summary,
236+ }
237+ } ) ,
238+ }
239+ }
240+
241+ impl CheckStatus {
242+ fn as_str ( & self ) -> & ' static str {
243+ match self {
244+ Self :: Queued => "queued" ,
245+ Self :: InProgress => "in_progress" ,
246+ Self :: Completed => "completed" ,
247+ }
248+ }
249+ }
250+
251+ impl CheckConclusion {
252+ fn as_str ( & self ) -> & ' static str {
253+ match self {
254+ Self :: Success => "success" ,
255+ Self :: Failure => "failure" ,
256+ Self :: Neutral => "neutral" ,
257+ Self :: Cancelled => "cancelled" ,
258+ Self :: ActionRequired => "action_required" ,
259+ }
260+ }
261+ }
262+
263+ #[ cfg( test) ]
264+ mod tests {
265+ use super :: * ;
266+ use serde_json:: json;
267+
268+ #[ test]
269+ fn create_request_targets_check_runs_with_expected_payload ( ) {
270+ let request = create_request (
271+ "octo" ,
272+ "repo" ,
273+ "abc123" ,
274+ "Cody — Fix issue #7" ,
275+ Some ( "https://cave.example/sessions/task-7" ) ,
276+ ) ;
277+
278+ assert_eq ! ( request. method, "POST" ) ;
279+ assert_eq ! ( request. path, "/repos/octo/repo/check-runs" ) ;
280+ assert_eq ! (
281+ request. body,
282+ json!( {
283+ "name" : "Cody — Fix issue #7" ,
284+ "head_sha" : "abc123" ,
285+ "status" : "queued" ,
286+ "details_url" : "https://cave.example/sessions/task-7"
287+ } )
288+ ) ;
289+ }
290+
291+ #[ test]
292+ fn update_request_sets_output_without_conclusion ( ) {
293+ let request = update_request (
294+ "octo" ,
295+ "repo" ,
296+ 42 ,
297+ CheckStatus :: InProgress ,
298+ "Running" ,
299+ "Working" ,
300+ ) ;
301+
302+ assert_eq ! ( request. method, "PATCH" ) ;
303+ assert_eq ! ( request. path, "/repos/octo/repo/check-runs/42" ) ;
304+ assert_eq ! (
305+ request. body,
306+ json!( {
307+ "status" : "in_progress" ,
308+ "output" : {
309+ "title" : "Running" ,
310+ "summary" : "Working"
311+ }
312+ } )
313+ ) ;
314+ }
315+
316+ #[ test]
317+ fn complete_request_sets_completed_status_and_conclusion ( ) {
318+ let request = complete_request (
319+ "octo" ,
320+ "repo" ,
321+ 42 ,
322+ CheckConclusion :: Success ,
323+ "Done" ,
324+ "Ready" ,
325+ ) ;
326+
327+ assert_eq ! ( request. method, "PATCH" ) ;
328+ assert_eq ! ( request. path, "/repos/octo/repo/check-runs/42" ) ;
329+ assert_eq ! (
330+ request. body,
331+ json!( {
332+ "status" : "completed" ,
333+ "conclusion" : "success" ,
334+ "output" : {
335+ "title" : "Done" ,
336+ "summary" : "Ready"
337+ }
338+ } )
339+ ) ;
340+ }
68341}
0 commit comments