@@ -88,7 +88,7 @@ pub use crate::{
8888} ;
8989use crate :: {
9090 request:: { LambdaRequest , RequestOrigin } ,
91- response:: LambdaResponse ,
91+ response:: { BodyConversionError , LambdaResponse } ,
9292} ;
9393
9494// Reexported in its entirety, regardless of what feature flags are enabled
@@ -112,9 +112,7 @@ pub use streaming::{run_with_streaming_response_concurrent, streaming_runtime_co
112112/// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type
113113pub type Request = http:: Request < Body > ;
114114
115- /// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
116- ///
117- /// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
115+ /// Future used by [`Adapter`] to convert an [`IntoResponse`] into a [`LambdaResponse`].
118116#[ non_exhaustive]
119117#[ doc( hidden) ]
120118pub enum TransformResponse < ' a , R , E > {
@@ -146,9 +144,109 @@ where
146144 }
147145}
148146
147+ // The public Adapter must preserve its handler's error type. The runtime helpers
148+ // can use Diagnostic as an internal common error channel for conversion failures.
149+ enum RuntimeTransformResponse < ' a , R , E > {
150+ Request ( RequestOrigin , RequestFuture < ' a , R , E > ) ,
151+ Response ( RequestOrigin , ResponseFuture ) ,
152+ }
153+
154+ impl < R , E > Future for RuntimeTransformResponse < ' _ , R , E >
155+ where
156+ R : IntoResponse ,
157+ E : Into < Diagnostic > ,
158+ {
159+ type Output = Result < LambdaResponse , Diagnostic > ;
160+
161+ fn poll ( mut self : Pin < & mut Self > , cx : & mut TaskContext < ' _ > ) -> Poll < Self :: Output > {
162+ match * self {
163+ RuntimeTransformResponse :: Request ( ref mut origin, ref mut request) => match request. as_mut ( ) . poll ( cx) {
164+ Poll :: Ready ( Ok ( resp) ) => {
165+ * self = RuntimeTransformResponse :: Response ( origin. clone ( ) , resp. into_response ( ) ) ;
166+ self . poll ( cx)
167+ }
168+ Poll :: Ready ( Err ( err) ) => Poll :: Ready ( Err ( err. into ( ) ) ) ,
169+ Poll :: Pending => Poll :: Pending ,
170+ } ,
171+ RuntimeTransformResponse :: Response ( ref mut origin, ref mut response) => match response. as_mut ( ) . poll ( cx) {
172+ Poll :: Ready ( mut resp) => {
173+ if let Some ( error) = resp. extensions_mut ( ) . remove :: < BodyConversionError > ( ) {
174+ return Poll :: Ready ( Err ( Diagnostic {
175+ error_type : error. error_type . to_owned ( ) ,
176+ error_message : error. error_message ,
177+ } ) ) ;
178+ }
179+
180+ Poll :: Ready ( Ok ( LambdaResponse :: from_response ( origin, resp) ) )
181+ }
182+ Poll :: Pending => Poll :: Pending ,
183+ } ,
184+ }
185+ }
186+ }
187+
188+ struct RuntimeAdapter < ' a , R , S > {
189+ service : S ,
190+ _phantom_data : PhantomData < & ' a R > ,
191+ }
192+
193+ impl < ' a , R , S > Clone for RuntimeAdapter < ' a , R , S >
194+ where
195+ S : Clone ,
196+ {
197+ fn clone ( & self ) -> Self {
198+ Self {
199+ service : self . service . clone ( ) ,
200+ _phantom_data : PhantomData ,
201+ }
202+ }
203+ }
204+
205+ impl < ' a , R , S , E > From < S > for RuntimeAdapter < ' a , R , S >
206+ where
207+ S : Service < Request , Response = R , Error = E > ,
208+ S :: Future : Send + ' a ,
209+ R : IntoResponse ,
210+ E : Into < Diagnostic > ,
211+ {
212+ fn from ( service : S ) -> Self {
213+ Self {
214+ service,
215+ _phantom_data : PhantomData ,
216+ }
217+ }
218+ }
219+
220+ impl < ' a , R , S , E > Service < LambdaEvent < LambdaRequest > > for RuntimeAdapter < ' a , R , S >
221+ where
222+ S : Service < Request , Response = R , Error = E > ,
223+ S :: Future : Send + ' a ,
224+ R : IntoResponse ,
225+ E : Into < Diagnostic > ,
226+ {
227+ type Response = LambdaResponse ;
228+ type Error = Diagnostic ;
229+ type Future = RuntimeTransformResponse < ' a , R , E > ;
230+
231+ fn poll_ready ( & mut self , cx : & mut core:: task:: Context < ' _ > ) -> core:: task:: Poll < Result < ( ) , Self :: Error > > {
232+ self . service . poll_ready ( cx) . map_err ( Into :: into)
233+ }
234+
235+ fn call ( & mut self , req : LambdaEvent < LambdaRequest > ) -> Self :: Future {
236+ let LambdaEvent { payload, context } = req;
237+ let request_origin = payload. request_origin ( ) ;
238+ let mut event: Request = payload. into ( ) ;
239+ update_xray_trace_id_header ( event. headers_mut ( ) , & context) ;
240+ let fut = Box :: pin ( self . service . call ( event. with_lambda_context ( context) ) ) ;
241+
242+ RuntimeTransformResponse :: Request ( request_origin, fut)
243+ }
244+ }
245+
149246/// Wraps a `Service<Request>` in a `Service<LambdaEvent<Request>>`
150247///
151- /// This is completely internal to the `lambda_http::run` function.
248+ /// This adapter preserves the wrapped service's error type. Response body conversion
249+ /// failures are returned as deterministic HTTP 500 responses.
152250#[ non_exhaustive]
153251#[ doc( hidden) ]
154252pub struct Adapter < ' a , R , S > {
@@ -232,7 +330,7 @@ where
232330 R : IntoResponse ,
233331 E : std:: fmt:: Debug + Into < Diagnostic > ,
234332{
235- lambda_runtime:: run ( Adapter :: from ( handler) ) . await
333+ lambda_runtime:: run ( RuntimeAdapter :: from ( handler) ) . await
236334}
237335
238336/// Starts the Lambda Rust runtime and begins polling for events on the [Lambda
@@ -265,7 +363,7 @@ where
265363 R : IntoResponse + Send + Sync + ' static ,
266364 E : std:: fmt:: Debug + Into < Diagnostic > + Send + ' static ,
267365{
268- lambda_runtime:: run_concurrent ( Adapter :: from ( handler) ) . await
366+ lambda_runtime:: run_concurrent ( RuntimeAdapter :: from ( handler) ) . await
269367}
270368
271369/// Returns a configured [`Runtime`](lambda_runtime::Runtime) wrapping the given
@@ -323,7 +421,7 @@ where
323421 R : IntoResponse + Send + Sync + ' static ,
324422 E : std:: fmt:: Debug + Into < Diagnostic > + Send + ' static ,
325423{
326- lambda_runtime:: Runtime :: new ( Adapter :: from ( handler) )
424+ lambda_runtime:: Runtime :: new ( RuntimeAdapter :: from ( handler) )
327425}
328426
329427/// Returns a configured [`Runtime`](lambda_runtime::Runtime) wrapping the given
@@ -355,7 +453,7 @@ where
355453 R : IntoResponse + Send + Sync + ' static ,
356454 E : std:: fmt:: Debug + Into < Diagnostic > + Send + ' static ,
357455{
358- lambda_runtime:: Runtime :: new ( Adapter :: from ( handler) )
456+ lambda_runtime:: Runtime :: new ( RuntimeAdapter :: from ( handler) )
359457}
360458
361459// In concurrent mode we must use the per-request context.
@@ -369,17 +467,35 @@ fn update_xray_trace_id_header(headers: &mut http::HeaderMap, context: &Context)
369467
370468#[ cfg( test) ]
371469mod test_adapter {
372- use std:: task:: { Context , Poll } ;
470+ use bytes:: Bytes ;
471+ use futures_util:: stream;
472+ use http_body:: Frame ;
473+ use http_body_util:: StreamBody ;
474+ use std:: {
475+ io:: { self , ErrorKind } ,
476+ task:: { Context , Poll } ,
477+ } ;
373478
374479 use crate :: {
480+ aws_lambda_events:: apigw:: ApiGatewayV2httpRequest ,
375481 http:: { Response , StatusCode } ,
376482 lambda_runtime:: LambdaEvent ,
377483 request:: LambdaRequest ,
378484 response:: LambdaResponse ,
379485 tower:: { util:: BoxService , Service , ServiceBuilder , ServiceExt } ,
380- Adapter , Body , Request ,
486+ Adapter , Body , Request , RuntimeAdapter ,
381487 } ;
382488
489+ fn fallible_body ( ) -> impl http_body:: Body < Data = Bytes , Error = io:: Error > + Unpin {
490+ StreamBody :: new ( stream:: iter ( [
491+ Ok ( Frame :: data ( Bytes :: from_static ( b"partial response" ) ) ) ,
492+ Err ( io:: Error :: new (
493+ ErrorKind :: UnexpectedEof ,
494+ "simulated truncated response body" ,
495+ ) ) ,
496+ ] ) )
497+ }
498+
383499 // A middleware that logs requests before forwarding them to another service
384500 struct LogService < S > {
385501 inner : S ,
@@ -422,6 +538,32 @@ mod test_adapter {
422538 . boxed ( ) ;
423539 }
424540
541+ #[ tokio:: test]
542+ async fn runtime_adapter_propagates_body_errors ( ) {
543+ for content_type in [ "text/plain; charset=utf-8" , "application/octet-stream" ] {
544+ let handler = crate :: service_fn ( move |_event : Request | async move {
545+ Ok :: < _ , std:: convert:: Infallible > (
546+ Response :: builder ( )
547+ . header ( http:: header:: CONTENT_TYPE , content_type)
548+ . body ( fallible_body ( ) )
549+ . expect ( "unable to build http::Response" ) ,
550+ )
551+ } ) ;
552+ let event = LambdaEvent :: new (
553+ LambdaRequest :: ApiGatewayV2 ( ApiGatewayV2httpRequest :: default ( ) ) ,
554+ crate :: Context :: default ( ) ,
555+ ) ;
556+
557+ let error = RuntimeAdapter :: from ( handler)
558+ . oneshot ( event)
559+ . await
560+ . expect_err ( "body collection error should be propagated" ) ;
561+
562+ assert_eq ! ( error. error_type, std:: any:: type_name:: <io:: Error >( ) ) ;
563+ assert ! ( error. error_message. contains( "simulated truncated response body" ) ) ;
564+ }
565+ }
566+
425567 async fn http_handler ( _req : Request ) -> Result < & ' static str , std:: convert:: Infallible > {
426568 Ok ( "hello" )
427569 }
0 commit comments