@@ -6,8 +6,11 @@ use serde::ser::Serialize;
66use serde_json;
77use std;
88use std:: collections:: HashMap ;
9+ use std:: fmt:: Debug ;
910use std:: io:: Read ;
1011use std:: path:: Path ;
12+ use std:: thread;
13+ use std:: time:: Duration ;
1114
1215
1316/// Project filter criteria used with `ApiClient::query_projects` to define project filter criteria.
@@ -61,7 +64,47 @@ pub struct ApiAnalysisJobResponse {
6164 pub job_id : String
6265}
6366
67+ /// Enumeration representing the 5 possible statuses a Code Dx "job" may be in.
68+ #[ serde( rename_all = "lowercase" ) ]
69+ #[ derive( Copy , Clone , Debug , Deserialize , Serialize ) ]
70+ pub enum JobStatus {
71+ Queued ,
72+ Running ,
73+ Cancelled ,
74+ Completed ,
75+ Failed
76+ }
77+ impl JobStatus {
78+ pub fn is_ready ( & self ) -> bool {
79+ match * self {
80+ JobStatus :: Completed => true ,
81+ JobStatus :: Failed => true ,
82+ _ => false
83+ }
84+ }
85+ pub fn is_success ( & self ) -> bool {
86+ match * self {
87+ JobStatus :: Completed => true ,
88+ _ => false
89+ }
90+ }
91+ }
92+
93+ #[ derive( Debug , Deserialize , Serialize ) ]
94+ pub struct JobStatusResponse {
95+ /// ID of the requested job.
96+ ///
97+ /// This should be the same as the `job_id` sent when you requested the status in the first place.
98+ #[ serde( rename = "jobId" ) ]
99+ pub job_id : String ,
100+
101+ /// The actual job status.
102+ pub status : JobStatus ,
64103
104+ // there are some optional fields like "progress", "blockedBy", and "reason"
105+ // which are present depending on the status, but they aren't necessary for
106+ // our use case, so I'm not going to model them.
107+ }
65108
66109/// Things that can go wrong when making requests with the API.
67110#[ derive( Debug ) ]
@@ -118,6 +161,25 @@ struct ErrorMessageResponse {
118161 error : String
119162}
120163
164+ /// Defines a polling strategy based on the iteration number and current state of the poll.
165+ ///
166+ /// The `next_wait` function decides how long the polling process should wait before re-checking the state.
167+ /// If it returns `Some(duration)`, the polling process will wait that duration before re-checking.
168+ /// If it returns `None`, the polling process will immediately end, typically returning the latest state.
169+ ///
170+ /// The `iteration_number` will start at `1` and increment every time `next_wait` is called for the current poll.
171+ pub trait PollingStrategy < T > {
172+ fn next_wait ( & self , iteration_number : usize , state : & T ) -> Option < Duration > ;
173+ }
174+
175+ /// Simple polling strategy that always waits a fixed amount of time between iterations.
176+ impl < T : Debug > PollingStrategy < T > for Duration {
177+ fn next_wait ( & self , iteration_number : usize , state : & T ) -> Option < Duration > {
178+ println ! ( "in poll (iteration {}, state: {:?})" , iteration_number, state) ;
179+ Some ( * self )
180+ }
181+ }
182+
121183pub type ApiResult < T > = Result < T , ApiError > ;
122184
123185
@@ -176,6 +238,46 @@ impl ApiClient {
176238 ApiClient { config, client }
177239 }
178240
241+ pub fn get_job_status ( & self , job_id : & str ) -> ApiResult < JobStatus > {
242+ self . api_get ( & [ "api" , "jobs" , job_id] )
243+ . expect_success ( )
244+ . expect_json :: < JobStatusResponse > ( )
245+ . map ( |jsr| jsr. status )
246+ }
247+
248+ /// Repeatedly call `get_job_status(job_id)` until it returns an error or a "ready" status.
249+ ///
250+ /// Uses the provided `polling_stategy` to determine how long to wait between each status
251+ /// check, and whether to abort early.
252+ ///
253+ /// If the `polling_strategy` decides to abort early, the result of the poll will be the
254+ /// most recent `JobStatus` to be passed.
255+ ///
256+ /// If at any point the job status check fails (i.e. `get_job_status` returns an `Err(_)`),
257+ /// the poll will immediately stop, returning that error.
258+ pub fn poll_job_completion < P : PollingStrategy < JobStatus > > ( & self , job_id : & str , polling_strategy : P ) -> ApiResult < JobStatus > {
259+ let mut iteration_number: usize = 0 ;
260+ loop {
261+ let status_result = self . get_job_status ( job_id) ;
262+ iteration_number += 1 ;
263+ match status_result {
264+ Ok ( status) => {
265+ if status. is_ready ( ) {
266+ break status_result;
267+ } else {
268+ // call the "step" function to see if the poll should continue,
269+ // and if so, how long it should wait before checking again
270+ match polling_strategy. next_wait ( iteration_number, & status) {
271+ Some ( wait_dur) => thread:: sleep ( wait_dur) ,
272+ None => break status_result,
273+ }
274+ }
275+ } ,
276+ Err ( _) => break status_result,
277+ }
278+ }
279+ }
280+
179281 pub fn get_projects ( & self ) -> ApiResult < Vec < ApiProject > > {
180282 self . api_get ( & [ "x" , "projects" ] )
181283 . expect_success ( )
@@ -204,6 +306,13 @@ impl ApiClient {
204306 } )
205307 }
206308
309+ pub fn set_analysis_name ( & self , project_id : u32 , analysis_id : u32 , name : & str ) -> ApiResult < ( ) > {
310+ self . api_put ( & [ "x" , "projects" , & project_id. to_string ( ) , "analyses" , & analysis_id. to_string ( ) ] , json ! ( { "name" : name } ) )
311+ . expect_success ( )
312+ . get ( )
313+ . map ( |_| ( ) )
314+ }
315+
207316 pub fn api_get ( & self , path_segments : & [ & str ] ) -> ApiResponse {
208317 self . api_request ( Method :: Get , path_segments, ReqBody :: None )
209318 }
0 commit comments