@@ -85,10 +85,226 @@ where
8585 . boxed ( ) ;
8686 self . set ( RuntimeApiClientFuture :: Second ( next_fut) ) ;
8787 }
88- Err ( err) => break Err ( err) ,
88+ Err ( err) => {
89+ log_or_print ! (
90+ tracing: tracing:: error!( error = ?err, "failed to build Lambda Runtime API request" ) ,
91+ fallback: eprintln!( "failed to build Lambda Runtime API request: {err:?}" )
92+ ) ;
93+ break Err ( err) ;
94+ }
95+ } ,
96+ RuntimeApiClientFutureProj :: Second ( fut) => match ready ! ( fut. poll( cx) ) {
97+ Ok ( resp) if !resp. status ( ) . is_success ( ) => {
98+ let status = resp. status ( ) ;
99+
100+ log_or_print ! (
101+ tracing: tracing:: error!( status = %status, "Lambda Runtime API returned non-200 response" ) ,
102+ fallback: eprintln!( "Lambda Runtime API returned non-200 response: status={status}" )
103+ ) ;
104+
105+ // Adding more information on top of 410 Gone, to make it more clear since we cannot access the body of the message
106+ if status == 410 {
107+ log_or_print ! (
108+ tracing: tracing:: error!( "Lambda function timeout!" ) ,
109+ fallback: eprintln!( "Lambda function timeout!" )
110+ ) ;
111+ }
112+
113+ // Return Ok to maintain existing contract - runtime continues despite API errors
114+ break Ok ( ( ) ) ;
115+ }
116+ Ok ( _) => break Ok ( ( ) ) ,
117+ Err ( err) => {
118+ log_or_print ! (
119+ tracing: tracing:: error!( error = ?err, "Lambda Runtime API request failed" ) ,
120+ fallback: eprintln!( "Lambda Runtime API request failed: {err:?}" )
121+ ) ;
122+ break Err ( err) ;
123+ }
89124 } ,
90- RuntimeApiClientFutureProj :: Second ( fut) => break ready ! ( fut. poll( cx) ) . map ( |_| ( ) ) ,
91125 }
92126 } )
93127 }
94128}
129+
130+ #[ cfg( test) ]
131+ mod tests {
132+ use super :: * ;
133+ use http:: StatusCode ;
134+ use http_body_util:: Full ;
135+ use hyper:: body:: Bytes ;
136+ use lambda_runtime_api_client:: body:: Body ;
137+ use std:: convert:: Infallible ;
138+ use tokio:: net:: TcpListener ;
139+ use tracing_test:: traced_test;
140+
141+ async fn start_mock_server ( status : StatusCode ) -> String {
142+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
143+ let addr = listener. local_addr ( ) . unwrap ( ) ;
144+ let url = format ! ( "http://{}" , addr) ;
145+
146+ tokio:: spawn ( async move {
147+ let ( stream, _) = listener. accept ( ) . await . unwrap ( ) ;
148+ let io = hyper_util:: rt:: TokioIo :: new ( stream) ;
149+
150+ let service = hyper:: service:: service_fn ( move |_req| async move {
151+ Ok :: < _ , Infallible > (
152+ http:: Response :: builder ( )
153+ . status ( status)
154+ . body ( Full :: new ( Bytes :: from ( "test response" ) ) )
155+ . unwrap ( ) ,
156+ )
157+ } ) ;
158+
159+ let _ = hyper_util:: server:: conn:: auto:: Builder :: new ( hyper_util:: rt:: TokioExecutor :: new ( ) )
160+ . serve_connection ( io, service)
161+ . await ;
162+ } ) ;
163+
164+ // Give the server a moment to start
165+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 10 ) ) . await ;
166+ url
167+ }
168+
169+ #[ tokio:: test]
170+ #[ traced_test]
171+ async fn test_successful_response ( ) {
172+ let url = start_mock_server ( StatusCode :: OK ) . await ;
173+ let client = Arc :: new (
174+ lambda_runtime_api_client:: Client :: builder ( )
175+ . with_endpoint ( url. parse ( ) . unwrap ( ) )
176+ . build ( )
177+ . unwrap ( ) ,
178+ ) ;
179+
180+ let request_fut =
181+ async { Ok :: < _ , BoxError > ( http:: Request :: builder ( ) . uri ( "/test" ) . body ( Body :: empty ( ) ) . unwrap ( ) ) } ;
182+
183+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
184+ let result = future. await ;
185+
186+ assert ! ( result. is_ok( ) ) ;
187+ // No error logs should be present
188+ assert ! ( !logs_contain( "Lambda Runtime API returned non-200 response" ) ) ;
189+ }
190+
191+ #[ tokio:: test]
192+ #[ traced_test]
193+ async fn test_410_timeout_error ( ) {
194+ let url = start_mock_server ( StatusCode :: GONE ) . await ;
195+ let client = Arc :: new (
196+ lambda_runtime_api_client:: Client :: builder ( )
197+ . with_endpoint ( url. parse ( ) . unwrap ( ) )
198+ . build ( )
199+ . unwrap ( ) ,
200+ ) ;
201+
202+ let request_fut =
203+ async { Ok :: < _ , BoxError > ( http:: Request :: builder ( ) . uri ( "/test" ) . body ( Body :: empty ( ) ) . unwrap ( ) ) } ;
204+
205+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
206+ let result = future. await ;
207+
208+ // Returns Ok to maintain contract, but logs the error
209+ assert ! ( result. is_ok( ) ) ;
210+
211+ // Verify the error was logged
212+ assert ! ( logs_contain( "Lambda Runtime API returned non-200 response" ) ) ;
213+ assert ! ( logs_contain( "Lambda function timeout!" ) ) ;
214+ }
215+
216+ #[ tokio:: test]
217+ #[ traced_test]
218+ async fn test_500_error ( ) {
219+ let url = start_mock_server ( StatusCode :: INTERNAL_SERVER_ERROR ) . await ;
220+ let client = Arc :: new (
221+ lambda_runtime_api_client:: Client :: builder ( )
222+ . with_endpoint ( url. parse ( ) . unwrap ( ) )
223+ . build ( )
224+ . unwrap ( ) ,
225+ ) ;
226+
227+ let request_fut =
228+ async { Ok :: < _ , BoxError > ( http:: Request :: builder ( ) . uri ( "/test" ) . body ( Body :: empty ( ) ) . unwrap ( ) ) } ;
229+
230+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
231+ let result = future. await ;
232+
233+ // Returns Ok to maintain contract, but logs the error
234+ assert ! ( result. is_ok( ) ) ;
235+
236+ // Verify the error was logged with status code
237+ assert ! ( logs_contain( "Lambda Runtime API returned non-200 response" ) ) ;
238+ }
239+
240+ #[ tokio:: test]
241+ #[ traced_test]
242+ async fn test_404_error ( ) {
243+ let url = start_mock_server ( StatusCode :: NOT_FOUND ) . await ;
244+ let client = Arc :: new (
245+ lambda_runtime_api_client:: Client :: builder ( )
246+ . with_endpoint ( url. parse ( ) . unwrap ( ) )
247+ . build ( )
248+ . unwrap ( ) ,
249+ ) ;
250+
251+ let request_fut =
252+ async { Ok :: < _ , BoxError > ( http:: Request :: builder ( ) . uri ( "/test" ) . body ( Body :: empty ( ) ) . unwrap ( ) ) } ;
253+
254+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
255+ let result = future. await ;
256+
257+ // Returns Ok to maintain contract, but logs the error
258+ assert ! ( result. is_ok( ) ) ;
259+
260+ // Verify the error was logged
261+ assert ! ( logs_contain( "Lambda Runtime API returned non-200 response" ) ) ;
262+ }
263+
264+ #[ tokio:: test]
265+ #[ traced_test]
266+ async fn test_request_build_error ( ) {
267+ let client = Arc :: new (
268+ lambda_runtime_api_client:: Client :: builder ( )
269+ . with_endpoint ( "http://localhost:9001" . parse ( ) . unwrap ( ) )
270+ . build ( )
271+ . unwrap ( ) ,
272+ ) ;
273+
274+ let request_fut = async { Err :: < http:: Request < Body > , BoxError > ( "Request build error" . into ( ) ) } ;
275+
276+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
277+ let result = future. await ;
278+
279+ assert ! ( result. is_err( ) ) ;
280+ let err = result. unwrap_err ( ) ;
281+ assert ! ( err. to_string( ) . contains( "Request build error" ) ) ;
282+
283+ // Verify the error was logged
284+ assert ! ( logs_contain( "failed to build Lambda Runtime API request" ) ) ;
285+ }
286+
287+ #[ tokio:: test]
288+ #[ traced_test]
289+ async fn test_network_error ( ) {
290+ // Use an invalid endpoint that will fail to connect
291+ let client = Arc :: new (
292+ lambda_runtime_api_client:: Client :: builder ( )
293+ . with_endpoint ( "http://127.0.0.1:1" . parse ( ) . unwrap ( ) ) // Port 1 should be unreachable
294+ . build ( )
295+ . unwrap ( ) ,
296+ ) ;
297+
298+ let request_fut =
299+ async { Ok :: < _ , BoxError > ( http:: Request :: builder ( ) . uri ( "/test" ) . body ( Body :: empty ( ) ) . unwrap ( ) ) } ;
300+
301+ let future = RuntimeApiClientFuture :: First ( request_fut, client) ;
302+ let result = future. await ;
303+
304+ // Network errors should propagate as Err
305+ assert ! ( result. is_err( ) ) ;
306+
307+ // Verify the error was logged
308+ assert ! ( logs_contain( "Lambda Runtime API request failed" ) ) ;
309+ }
310+ }
0 commit comments