18
18
use std:: collections:: HashMap ;
19
19
use std:: fmt:: { Debug , Formatter } ;
20
20
21
- use http:: StatusCode ;
21
+ use http:: { HeaderValue , StatusCode } ;
22
22
use iceberg:: { Error , ErrorKind , Result } ;
23
+ use reqsign:: { AwsDefaultLoader , AwsV4Signer } ;
23
24
use reqwest:: header:: HeaderMap ;
24
25
use reqwest:: { Client , IntoUrl , Method , Request , RequestBuilder , Response } ;
25
26
use serde:: de:: DeserializeOwned ;
@@ -43,6 +44,8 @@ pub(crate) struct HttpClient {
43
44
extra_headers : HeaderMap ,
44
45
/// Extra oauth parameters to be added to each authentication request.
45
46
extra_oauth_params : HashMap < String , String > ,
47
+
48
+ signer : Option < ( AwsDefaultLoader , AwsV4Signer ) > ,
46
49
}
47
50
48
51
impl Debug for HttpClient {
@@ -65,6 +68,7 @@ impl HttpClient {
65
68
credential : cfg. credential ( ) ,
66
69
extra_headers,
67
70
extra_oauth_params : cfg. extra_oauth_params ( ) ,
71
+ signer : cfg. get_signer ( ) ?,
68
72
} )
69
73
}
70
74
@@ -88,6 +92,7 @@ impl HttpClient {
88
92
extra_oauth_params : ( !cfg. extra_oauth_params ( ) . is_empty ( ) )
89
93
. then ( || cfg. extra_oauth_params ( ) )
90
94
. unwrap_or ( self . extra_oauth_params ) ,
95
+ signer : cfg. get_signer ( ) ?,
91
96
} )
92
97
}
93
98
@@ -220,6 +225,39 @@ impl HttpClient {
220
225
/// Executes the given `Request` and returns a `Response`.
221
226
pub async fn execute ( & self , mut request : Request ) -> Result < Response > {
222
227
request. headers_mut ( ) . extend ( self . extra_headers . clone ( ) ) ;
228
+
229
+ if let Some ( ( loader, signer) ) = & self . signer {
230
+ match loader. load ( ) . await {
231
+ Ok ( Some ( credential) ) => {
232
+ const EMPTY_STRING_SHA256 : & str =
233
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ;
234
+ request. headers_mut ( ) . insert (
235
+ "x-amz-content-sha256" ,
236
+ HeaderValue :: from_str ( EMPTY_STRING_SHA256 ) . unwrap ( ) ,
237
+ ) ;
238
+ if let Err ( e) = signer. sign ( & mut request, & credential) {
239
+ return Err ( Error :: new (
240
+ ErrorKind :: Unexpected ,
241
+ "Failed to sign request for sigv4 signing" ,
242
+ )
243
+ . with_source ( e) ) ;
244
+ }
245
+ }
246
+ Ok ( None ) => {
247
+ return Err ( Error :: new (
248
+ ErrorKind :: Unexpected ,
249
+ "Credential not found for sigv4 signing" ,
250
+ ) ) ;
251
+ }
252
+ Err ( e) => {
253
+ return Err ( Error :: new (
254
+ ErrorKind :: Unexpected ,
255
+ "Failed to load credential for sigv4 signing" ,
256
+ )
257
+ . with_source ( e) ) ;
258
+ }
259
+ }
260
+ }
223
261
Ok ( self . client . execute ( request) . await ?)
224
262
}
225
263
@@ -255,6 +293,7 @@ pub(crate) async fn deserialize_catalog_response<R: DeserializeOwned>(
255
293
/// codes that all endpoints share (400, 404, etc.).
256
294
pub ( crate ) async fn deserialize_unexpected_catalog_error ( response : Response ) -> Error {
257
295
let ( status, headers) = ( response. status ( ) , response. headers ( ) . clone ( ) ) ;
296
+ let url = response. url ( ) . to_string ( ) ;
258
297
let bytes = match response. bytes ( ) . await {
259
298
Ok ( bytes) => bytes,
260
299
Err ( err) => return err. into ( ) ,
@@ -264,4 +303,5 @@ pub(crate) async fn deserialize_unexpected_catalog_error(response: Response) ->
264
303
. with_context ( "status" , status. to_string ( ) )
265
304
. with_context ( "headers" , format ! ( "{:?}" , headers) )
266
305
. with_context ( "json" , String :: from_utf8_lossy ( & bytes) )
306
+ . with_context ( "url" , url)
267
307
}
0 commit comments