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