7
7
//!
8
8
//! An implementation using HTTP is available: [AggregatorHTTPClient].
9
9
10
+ use std:: sync:: Arc ;
11
+
10
12
use anyhow:: { anyhow, Context } ;
11
13
use async_recursion:: async_recursion;
12
14
use async_trait:: async_trait;
13
15
use reqwest:: { Response , StatusCode , Url } ;
14
16
use semver:: Version ;
15
17
use slog:: { debug, Logger } ;
16
- use std:: sync:: Arc ;
17
18
use thiserror:: Error ;
18
19
use tokio:: sync:: RwLock ;
19
20
20
- #[ cfg( test) ]
21
- use mockall:: automock;
22
-
21
+ use mithril_common:: entities:: { ClientError , ServerError } ;
23
22
use mithril_common:: MITHRIL_API_VERSION_HEADER ;
24
23
25
24
use crate :: { MithrilError , MithrilResult } ;
@@ -28,11 +27,11 @@ use crate::{MithrilError, MithrilResult};
28
27
#[ derive( Error , Debug ) ]
29
28
pub enum AggregatorClientError {
30
29
/// Error raised when querying the aggregator returned a 5XX error.
31
- #[ error( "remote server technical error " ) ]
30
+ #[ error( "Internal error of the Aggregator " ) ]
32
31
RemoteServerTechnical ( #[ source] MithrilError ) ,
33
32
34
33
/// Error raised when querying the aggregator returned a 4XX error.
35
- #[ error( "remote server logical error " ) ]
34
+ #[ error( "Invalid request to the Aggregator " ) ]
36
35
RemoteServerLogical ( #[ source] MithrilError ) ,
37
36
38
37
/// Error raised when the server API version mismatch the client API version.
@@ -258,11 +257,12 @@ impl AggregatorHTTPClient {
258
257
Err ( self . handle_api_error ( & response) . await )
259
258
}
260
259
StatusCode :: NOT_FOUND => Err ( AggregatorClientError :: RemoteServerLogical ( anyhow ! (
261
- "Url='{url} not found"
262
- ) ) ) ,
263
- status_code => Err ( AggregatorClientError :: RemoteServerTechnical ( anyhow ! (
264
- "Unhandled error {status_code}"
260
+ "Url='{url}' not found"
265
261
) ) ) ,
262
+ status_code if status_code. is_client_error ( ) => {
263
+ Err ( Self :: remote_logical_error ( response) . await )
264
+ }
265
+ _ => Err ( Self :: remote_technical_error ( response) . await ) ,
266
266
}
267
267
}
268
268
@@ -303,9 +303,10 @@ impl AggregatorHTTPClient {
303
303
StatusCode :: NOT_FOUND => Err ( AggregatorClientError :: RemoteServerLogical ( anyhow ! (
304
304
"Url='{url} not found"
305
305
) ) ) ,
306
- status_code => Err ( AggregatorClientError :: RemoteServerTechnical ( anyhow ! (
307
- "Unhandled error {status_code}"
308
- ) ) ) ,
306
+ status_code if status_code. is_client_error ( ) => {
307
+ Err ( Self :: remote_logical_error ( response) . await )
308
+ }
309
+ _ => Err ( Self :: remote_technical_error ( response) . await ) ,
309
310
}
310
311
}
311
312
@@ -336,9 +337,32 @@ impl AggregatorHTTPClient {
336
337
} )
337
338
. map_err ( AggregatorClientError :: SubsystemError )
338
339
}
340
+
341
+ async fn remote_logical_error ( response : Response ) -> AggregatorClientError {
342
+ let status_code = response. status ( ) ;
343
+ let client_error = response
344
+ . json :: < ClientError > ( )
345
+ . await
346
+ . unwrap_or ( ClientError :: new (
347
+ format ! ( "Unhandled error {status_code}" ) ,
348
+ "" ,
349
+ ) ) ;
350
+
351
+ AggregatorClientError :: RemoteServerLogical ( anyhow ! ( "{client_error}" ) )
352
+ }
353
+
354
+ async fn remote_technical_error ( response : Response ) -> AggregatorClientError {
355
+ let status_code = response. status ( ) ;
356
+ let server_error = response
357
+ . json :: < ServerError > ( )
358
+ . await
359
+ . unwrap_or ( ServerError :: new ( format ! ( "Unhandled error {status_code}" ) ) ) ;
360
+
361
+ AggregatorClientError :: RemoteServerTechnical ( anyhow ! ( "{server_error}" ) )
362
+ }
339
363
}
340
364
341
- #[ cfg_attr( test, automock) ]
365
+ #[ cfg_attr( test, mockall :: automock) ]
342
366
#[ cfg_attr( target_family = "wasm" , async_trait( ?Send ) ) ]
343
367
#[ cfg_attr( not( target_family = "wasm" ) , async_trait) ]
344
368
impl AggregatorClient for AggregatorHTTPClient {
@@ -377,8 +401,30 @@ impl AggregatorClient for AggregatorHTTPClient {
377
401
378
402
#[ cfg( test) ]
379
403
mod tests {
404
+ use httpmock:: MockServer ;
405
+
406
+ use mithril_common:: api_version:: APIVersionProvider ;
407
+ use mithril_common:: entities:: { ClientError , ServerError } ;
408
+
380
409
use super :: * ;
381
410
411
+ macro_rules! assert_error_eq {
412
+ ( $left: expr, $right: expr) => {
413
+ assert_eq!( format!( "{:?}" , & $left) , format!( "{:?}" , & $right) , ) ;
414
+ } ;
415
+ }
416
+
417
+ fn setup_server_and_client ( ) -> ( MockServer , AggregatorHTTPClient ) {
418
+ let server = MockServer :: start ( ) ;
419
+ let client = AggregatorHTTPClient :: new (
420
+ Url :: parse ( & server. url ( "" ) ) . unwrap ( ) ,
421
+ APIVersionProvider :: compute_all_versions_sorted ( ) . unwrap ( ) ,
422
+ crate :: test_utils:: test_logger ( ) ,
423
+ )
424
+ . expect ( "building aggregator http client should not fail" ) ;
425
+ ( server, client)
426
+ }
427
+
382
428
#[ test]
383
429
fn always_append_trailing_slash_at_build ( ) {
384
430
for ( expected, url) in [
@@ -482,4 +528,55 @@ mod tests {
482
528
) ;
483
529
}
484
530
}
531
+
532
+ #[ tokio:: test]
533
+ async fn test_client_handle_4xx_errors ( ) {
534
+ let client_error = ClientError :: new ( "label" , "message" ) ;
535
+
536
+ let ( aggregator, client) = setup_server_and_client ( ) ;
537
+ aggregator. mock ( |_when, then| {
538
+ then. status ( StatusCode :: IM_A_TEAPOT . as_u16 ( ) )
539
+ . json_body_obj ( & client_error) ;
540
+ } ) ;
541
+
542
+ let expected_error = AggregatorClientError :: RemoteServerLogical ( anyhow ! ( "{client_error}" ) ) ;
543
+
544
+ let get_content_error = client
545
+ . get_content ( AggregatorRequest :: ListCertificates )
546
+ . await
547
+ . unwrap_err ( ) ;
548
+ assert_error_eq ! ( get_content_error, expected_error) ;
549
+
550
+ let post_content_error = client
551
+ . post_content ( AggregatorRequest :: ListCertificates )
552
+ . await
553
+ . unwrap_err ( ) ;
554
+ assert_error_eq ! ( post_content_error, expected_error) ;
555
+ }
556
+
557
+ #[ tokio:: test]
558
+ async fn test_client_handle_5xx_errors ( ) {
559
+ let server_error = ServerError :: new ( "message" ) ;
560
+
561
+ let ( aggregator, client) = setup_server_and_client ( ) ;
562
+ aggregator. mock ( |_when, then| {
563
+ then. status ( StatusCode :: INTERNAL_SERVER_ERROR . as_u16 ( ) )
564
+ . json_body_obj ( & server_error) ;
565
+ } ) ;
566
+
567
+ let expected_error =
568
+ AggregatorClientError :: RemoteServerTechnical ( anyhow ! ( "{server_error}" ) ) ;
569
+
570
+ let get_content_error = client
571
+ . get_content ( AggregatorRequest :: ListCertificates )
572
+ . await
573
+ . unwrap_err ( ) ;
574
+ assert_error_eq ! ( get_content_error, expected_error) ;
575
+
576
+ let post_content_error = client
577
+ . post_content ( AggregatorRequest :: ListCertificates )
578
+ . await
579
+ . unwrap_err ( ) ;
580
+ assert_error_eq ! ( post_content_error, expected_error) ;
581
+ }
485
582
}
0 commit comments