@@ -4,6 +4,7 @@ use std::fmt::{self, Display};
44use std:: time:: Duration ;
55
66use async_trait:: async_trait;
7+ use bytes:: Bytes ;
78use common:: api:: provision:: SealedSeedId ;
89use common:: api:: qs:: { GetByUserPk , GetByUserPkAndMeasurement } ;
910use common:: api:: runner:: UserPorts ;
@@ -15,7 +16,7 @@ use reqwest::Client;
1516use serde:: de:: DeserializeOwned ;
1617use serde:: Serialize ;
1718use tokio:: time;
18- use tracing:: debug;
19+ use tracing:: { debug, trace } ;
1920
2021use self :: ApiVersion :: * ;
2122use self :: BaseUrl :: * ;
@@ -26,6 +27,8 @@ const API_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
2627/// This is relatively long since if the second try failed, it probably means
2728/// that the backend is down, which could be the case for a while.
2829const RETRY_INTERVAL : Duration = Duration :: from_secs ( 15 ) ;
30+ /// 0 retries by default
31+ const DEFAULT_RETRIES : usize = 0 ;
2932
3033// Avoid `Method::` prefix. Associated constants can't be imported
3134const GET : Method = Method :: GET ;
@@ -53,6 +56,12 @@ impl Display for ApiVersion {
5356 }
5457}
5558
59+ struct RequestParts {
60+ method : Method ,
61+ url : String ,
62+ body : Bytes ,
63+ }
64+
5665pub struct LexeApiClient {
5766 client : Client ,
5867 backend_url : String ,
@@ -184,7 +193,27 @@ impl ApiClient for LexeApiClient {
184193}
185194
186195impl LexeApiClient {
187- /// Tries to complete an API request, retrying up to `retries` times.
196+ /// Makes an API request, retrying up to `DEFAULT_RETRIES` times.
197+ async fn request < D : Serialize , T : DeserializeOwned > (
198+ & self ,
199+ method : & Method ,
200+ base : BaseUrl ,
201+ ver : ApiVersion ,
202+ endpoint : & str ,
203+ data : & D ,
204+ ) -> Result < T , ApiError > {
205+ self . request_with_retries (
206+ method,
207+ base,
208+ ver,
209+ endpoint,
210+ data,
211+ DEFAULT_RETRIES ,
212+ )
213+ . await
214+ }
215+
216+ /// Makes an API request, retrying up to `retries` times.
188217 async fn request_with_retries < D : Serialize , T : DeserializeOwned > (
189218 & self ,
190219 method : & Method ,
@@ -196,10 +225,12 @@ impl LexeApiClient {
196225 ) -> Result < T , ApiError > {
197226 let mut retry_timer = time:: interval ( RETRY_INTERVAL ) ;
198227
199- // 'Do the retries first' and return early if successful.
200- // If retries == 0 this block is a noop.
228+ let parts = self . serialize_parts ( method, base, ver, endpoint, data) ?;
229+
230+ // Do the 'retries' first and return early if successful.
231+ // This block is a noop if retries == 0.
201232 for _ in 0 ..retries {
202- let res = self . request ( method , base , ver , endpoint , data ) . await ;
233+ let res = self . send_request ( & parts ) . await ;
203234 if res. is_ok ( ) {
204235 return res;
205236 } else {
@@ -215,19 +246,19 @@ impl LexeApiClient {
215246 }
216247 }
217248
218- // Do the 'main attempt' .
219- self . request ( method , base , ver , endpoint , & data ) . await
249+ // Do the 'main' attempt.
250+ self . send_request ( & parts ) . await
220251 }
221252
222- /// Executes an API request once .
223- async fn request < D : Serialize , T : DeserializeOwned > (
253+ /// Constructs the final, serialized parts of a [`reqwest::Request`] .
254+ fn serialize_parts < D : Serialize > (
224255 & self ,
225256 method : & Method ,
226257 base : BaseUrl ,
227258 ver : ApiVersion ,
228259 endpoint : & str ,
229260 data : & D ,
230- ) -> Result < T , ApiError > {
261+ ) -> Result < RequestParts , ApiError > {
231262 // Node backend api is versioned but runner api is not
232263 let ( base, ver) = match base {
233264 Backend => ( & self . backend_url , ver. to_string ( ) ) ,
@@ -251,14 +282,31 @@ impl LexeApiClient {
251282 debug ! ( %method, %url, "sending request" ) ;
252283
253284 // If PUT or POST, serialize the data in the request body
254- let body = match method {
285+ let body_str = match method {
255286 PUT | POST => serde_json:: to_string ( data) ?,
256287 _ => String :: new ( ) ,
257288 } ;
258- // println!(" Body: {}", body);
289+ trace ! ( %body_str) ;
290+ let body = Bytes :: from ( body_str) ;
259291
260- let response =
261- self . client . request ( method, url) . body ( body) . send ( ) . await ?;
292+ Ok ( RequestParts { method, url, body } )
293+ }
294+
295+ /// Build a [`reqwest::Request`] from [`RequestParts`], send it, and
296+ /// deserialize into the expected struct or an error message depending on
297+ /// the response status.
298+ async fn send_request < T : DeserializeOwned > (
299+ & self ,
300+ parts : & RequestParts ,
301+ ) -> Result < T , ApiError > {
302+ let response = self
303+ . client
304+ // Method doesn't implement Copy
305+ . request ( parts. method . clone ( ) , & parts. url )
306+ // body is Bytes which can be cheaply cloned
307+ . body ( parts. body . clone ( ) )
308+ . send ( )
309+ . await ?;
262310
263311 if response. status ( ) . is_success ( ) {
264312 // Uncomment for debugging
0 commit comments