1818use std:: collections:: HashMap ;
1919use std:: fmt:: { Debug , Formatter } ;
2020
21- use http:: StatusCode ;
21+ use http:: { HeaderValue , StatusCode } ;
2222use iceberg:: { Error , ErrorKind , Result } ;
23+ use reqsign:: { AwsDefaultLoader , AwsV4Signer } ;
2324use reqwest:: header:: HeaderMap ;
2425use reqwest:: { Client , IntoUrl , Method , Request , RequestBuilder , Response } ;
2526use serde:: de:: DeserializeOwned ;
@@ -43,6 +44,8 @@ pub(crate) struct HttpClient {
4344 extra_headers : HeaderMap ,
4445 /// Extra oauth parameters to be added to each authentication request.
4546 extra_oauth_params : HashMap < String , String > ,
47+
48+ signer : Option < ( AwsDefaultLoader , AwsV4Signer ) > ,
4649}
4750
4851impl Debug for HttpClient {
@@ -65,6 +68,7 @@ impl HttpClient {
6568 credential : cfg. credential ( ) ,
6669 extra_headers,
6770 extra_oauth_params : cfg. extra_oauth_params ( ) ,
71+ signer : cfg. get_signer ( ) ?,
6872 } )
6973 }
7074
@@ -88,6 +92,7 @@ impl HttpClient {
8892 extra_oauth_params : ( !cfg. extra_oauth_params ( ) . is_empty ( ) )
8993 . then ( || cfg. extra_oauth_params ( ) )
9094 . unwrap_or ( self . extra_oauth_params ) ,
95+ signer : cfg. get_signer ( ) ?,
9196 } )
9297 }
9398
@@ -220,6 +225,39 @@ impl HttpClient {
220225 /// Executes the given `Request` and returns a `Response`.
221226 pub async fn execute ( & self , mut request : Request ) -> Result < Response > {
222227 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+ }
223261 Ok ( self . client . execute ( request) . await ?)
224262 }
225263
@@ -255,6 +293,7 @@ pub(crate) async fn deserialize_catalog_response<R: DeserializeOwned>(
255293/// codes that all endpoints share (400, 404, etc.).
256294pub ( crate ) async fn deserialize_unexpected_catalog_error ( response : Response ) -> Error {
257295 let ( status, headers) = ( response. status ( ) , response. headers ( ) . clone ( ) ) ;
296+ let url = response. url ( ) . to_string ( ) ;
258297 let bytes = match response. bytes ( ) . await {
259298 Ok ( bytes) => bytes,
260299 Err ( err) => return err. into ( ) ,
@@ -264,4 +303,5 @@ pub(crate) async fn deserialize_unexpected_catalog_error(response: Response) ->
264303 . with_context ( "status" , status. to_string ( ) )
265304 . with_context ( "headers" , format ! ( "{:?}" , headers) )
266305 . with_context ( "json" , String :: from_utf8_lossy ( & bytes) )
306+ . with_context ( "url" , url)
267307}
0 commit comments