1
1
//! Response types
2
2
3
3
use http:: {
4
- header:: { HeaderMap , HeaderValue , CONTENT_TYPE } ,
4
+ header:: { HeaderMap , HeaderValue , CONTENT_TYPE , SET_COOKIE } ,
5
5
Response ,
6
6
} ;
7
7
use serde:: {
8
- ser:: { Error as SerError , SerializeMap } ,
8
+ ser:: { Error as SerError , SerializeMap , SerializeSeq } ,
9
9
Serializer ,
10
10
} ;
11
11
use serde_derive:: Serialize ;
12
12
13
13
use crate :: body:: Body ;
14
+ use crate :: request:: RequestOrigin ;
14
15
15
- /// Representation of API Gateway response
16
+ /// Representation of Lambda response
17
+ #[ doc( hidden) ]
18
+ #[ derive( Serialize , Debug ) ]
19
+ #[ serde( untagged) ]
20
+ pub enum LambdaResponse {
21
+ ApiGatewayV2 ( ApiGatewayV2Response ) ,
22
+ Alb ( AlbResponse ) ,
23
+ ApiGateway ( ApiGatewayResponse ) ,
24
+ }
25
+
26
+ /// Representation of API Gateway v2 lambda response
16
27
#[ doc( hidden) ]
17
28
#[ derive( Serialize , Debug ) ]
18
29
#[ serde( rename_all = "camelCase" ) ]
19
- pub struct LambdaResponse {
20
- pub status_code : u16 ,
21
- // ALB requires a statusDescription i.e. "200 OK" field but API Gateway returns an error
22
- // when one is provided. only populate this for ALB responses
30
+ pub struct ApiGatewayV2Response {
31
+ status_code : u16 ,
32
+ #[ serde( serialize_with = "serialize_headers" ) ]
33
+ headers : HeaderMap < HeaderValue > ,
34
+ #[ serde( serialize_with = "serialize_headers_slice" ) ]
35
+ cookies : Vec < HeaderValue > ,
23
36
#[ serde( skip_serializing_if = "Option::is_none" ) ]
24
- pub status_description : Option < String > ,
37
+ body : Option < Body > ,
38
+ is_base64_encoded : bool ,
39
+ }
40
+
41
+ /// Representation of ALB lambda response
42
+ #[ doc( hidden) ]
43
+ #[ derive( Serialize , Debug ) ]
44
+ #[ serde( rename_all = "camelCase" ) ]
45
+ pub struct AlbResponse {
46
+ status_code : u16 ,
47
+ status_description : String ,
25
48
#[ serde( serialize_with = "serialize_headers" ) ]
26
- pub headers : HeaderMap < HeaderValue > ,
27
- #[ serde( serialize_with = "serialize_multi_value_headers" ) ]
28
- pub multi_value_headers : HeaderMap < HeaderValue > ,
49
+ headers : HeaderMap < HeaderValue > ,
29
50
#[ serde( skip_serializing_if = "Option::is_none" ) ]
30
- pub body : Option < Body > ,
31
- // This field is optional for API Gateway but required for ALB
32
- pub is_base64_encoded : bool ,
51
+ body : Option < Body > ,
52
+ is_base64_encoded : bool ,
33
53
}
34
54
35
- #[ cfg( test) ]
36
- impl Default for LambdaResponse {
37
- fn default ( ) -> Self {
38
- Self {
39
- status_code : 200 ,
40
- status_description : Default :: default ( ) ,
41
- headers : Default :: default ( ) ,
42
- multi_value_headers : Default :: default ( ) ,
43
- body : Default :: default ( ) ,
44
- is_base64_encoded : Default :: default ( ) ,
45
- }
46
- }
55
+ /// Representation of API Gateway lambda response
56
+ #[ doc( hidden) ]
57
+ #[ derive( Serialize , Debug ) ]
58
+ #[ serde( rename_all = "camelCase" ) ]
59
+ pub struct ApiGatewayResponse {
60
+ status_code : u16 ,
61
+ #[ serde( serialize_with = "serialize_headers" ) ]
62
+ headers : HeaderMap < HeaderValue > ,
63
+ #[ serde( serialize_with = "serialize_multi_value_headers" ) ]
64
+ multi_value_headers : HeaderMap < HeaderValue > ,
65
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
66
+ body : Option < Body > ,
67
+ is_base64_encoded : bool ,
47
68
}
48
69
49
70
/// Serialize a http::HeaderMap into a serde str => str map
75
96
map. end ( )
76
97
}
77
98
99
+ /// Serialize a &[HeaderValue] into a Vec<str>
100
+ fn serialize_headers_slice < S > ( headers : & [ HeaderValue ] , serializer : S ) -> Result < S :: Ok , S :: Error >
101
+ where
102
+ S : Serializer ,
103
+ {
104
+ let mut seq = serializer. serialize_seq ( Some ( headers. len ( ) ) ) ?;
105
+ for header in headers {
106
+ seq. serialize_element ( header. to_str ( ) . map_err ( S :: Error :: custom) ?) ?;
107
+ }
108
+ seq. end ( )
109
+ }
110
+
78
111
/// tranformation from http type to internal type
79
112
impl LambdaResponse {
80
- pub ( crate ) fn from_response < T > ( is_alb : bool , value : Response < T > ) -> Self
113
+ pub ( crate ) fn from_response < T > ( request_origin : & RequestOrigin , value : Response < T > ) -> Self
81
114
where
82
115
T : Into < Body > ,
83
116
{
@@ -87,21 +120,43 @@ impl LambdaResponse {
87
120
b @ Body :: Text ( _) => ( false , Some ( b) ) ,
88
121
b @ Body :: Binary ( _) => ( true , Some ( b) ) ,
89
122
} ;
90
- Self {
91
- status_code : parts. status . as_u16 ( ) ,
92
- status_description : if is_alb {
93
- Some ( format ! (
123
+
124
+ let mut headers = parts. headers ;
125
+ let status_code = parts. status . as_u16 ( ) ;
126
+
127
+ match request_origin {
128
+ RequestOrigin :: ApiGatewayV2 => {
129
+ // ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute,
130
+ // so remove them from the headers.
131
+ let cookies: Vec < HeaderValue > = headers. get_all ( SET_COOKIE ) . iter ( ) . cloned ( ) . collect ( ) ;
132
+ headers. remove ( SET_COOKIE ) ;
133
+
134
+ LambdaResponse :: ApiGatewayV2 ( ApiGatewayV2Response {
135
+ body,
136
+ status_code,
137
+ is_base64_encoded,
138
+ cookies,
139
+ headers,
140
+ } )
141
+ }
142
+ RequestOrigin :: ApiGateway => LambdaResponse :: ApiGateway ( ApiGatewayResponse {
143
+ body,
144
+ status_code,
145
+ is_base64_encoded,
146
+ headers : headers. clone ( ) ,
147
+ multi_value_headers : headers,
148
+ } ) ,
149
+ RequestOrigin :: Alb => LambdaResponse :: Alb ( AlbResponse {
150
+ body,
151
+ status_code,
152
+ is_base64_encoded,
153
+ headers,
154
+ status_description : format ! (
94
155
"{} {}" ,
95
- parts . status . as_u16 ( ) ,
156
+ status_code ,
96
157
parts. status. canonical_reason( ) . unwrap_or_default( )
97
- ) )
98
- } else {
99
- None
100
- } ,
101
- body,
102
- headers : parts. headers . clone ( ) ,
103
- multi_value_headers : parts. headers ,
104
- is_base64_encoded,
158
+ ) ,
159
+ } ) ,
105
160
}
106
161
}
107
162
}
@@ -161,10 +216,42 @@ impl IntoResponse for serde_json::Value {
161
216
162
217
#[ cfg( test) ]
163
218
mod tests {
164
- use super :: { Body , IntoResponse , LambdaResponse } ;
219
+ use super :: {
220
+ AlbResponse , ApiGatewayResponse , ApiGatewayV2Response , Body , IntoResponse , LambdaResponse , RequestOrigin ,
221
+ } ;
165
222
use http:: { header:: CONTENT_TYPE , Response } ;
166
223
use serde_json:: { self , json} ;
167
224
225
+ fn api_gateway_response ( ) -> ApiGatewayResponse {
226
+ ApiGatewayResponse {
227
+ status_code : 200 ,
228
+ headers : Default :: default ( ) ,
229
+ multi_value_headers : Default :: default ( ) ,
230
+ body : Default :: default ( ) ,
231
+ is_base64_encoded : Default :: default ( ) ,
232
+ }
233
+ }
234
+
235
+ fn alb_response ( ) -> AlbResponse {
236
+ AlbResponse {
237
+ status_code : 200 ,
238
+ status_description : "200 OK" . to_string ( ) ,
239
+ headers : Default :: default ( ) ,
240
+ body : Default :: default ( ) ,
241
+ is_base64_encoded : Default :: default ( ) ,
242
+ }
243
+ }
244
+
245
+ fn api_gateway_v2_response ( ) -> ApiGatewayV2Response {
246
+ ApiGatewayV2Response {
247
+ status_code : 200 ,
248
+ headers : Default :: default ( ) ,
249
+ body : Default :: default ( ) ,
250
+ cookies : Default :: default ( ) ,
251
+ is_base64_encoded : Default :: default ( ) ,
252
+ }
253
+ }
254
+
168
255
#[ test]
169
256
fn json_into_response ( ) {
170
257
let response = json ! ( { "hello" : "lambda" } ) . into_response ( ) ;
@@ -191,32 +278,39 @@ mod tests {
191
278
}
192
279
193
280
#[ test]
194
- fn default_response ( ) {
195
- assert_eq ! ( LambdaResponse :: default ( ) . status_code, 200 )
281
+ fn serialize_body_for_api_gateway ( ) {
282
+ let mut resp = api_gateway_response ( ) ;
283
+ resp. body = Some ( "foo" . into ( ) ) ;
284
+ assert_eq ! (
285
+ serde_json:: to_string( & resp) . expect( "failed to serialize response" ) ,
286
+ r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"foo","isBase64Encoded":false}"#
287
+ ) ;
196
288
}
197
289
198
290
#[ test]
199
- fn serialize_default ( ) {
291
+ fn serialize_body_for_alb ( ) {
292
+ let mut resp = alb_response ( ) ;
293
+ resp. body = Some ( "foo" . into ( ) ) ;
200
294
assert_eq ! (
201
- serde_json:: to_string( & LambdaResponse :: default ( ) ) . expect( "failed to serialize response" ) ,
202
- r#"{"statusCode":200,"headers":{},"multiValueHeaders":{} ,"isBase64Encoded":false}"#
295
+ serde_json:: to_string( & resp ) . expect( "failed to serialize response" ) ,
296
+ r#"{"statusCode":200,"statusDescription":"200 OK"," headers":{},"body":"foo" ,"isBase64Encoded":false}"#
203
297
) ;
204
298
}
205
299
206
300
#[ test]
207
- fn serialize_body ( ) {
208
- let mut resp = LambdaResponse :: default ( ) ;
301
+ fn serialize_body_for_api_gateway_v2 ( ) {
302
+ let mut resp = api_gateway_v2_response ( ) ;
209
303
resp. body = Some ( "foo" . into ( ) ) ;
210
304
assert_eq ! (
211
305
serde_json:: to_string( & resp) . expect( "failed to serialize response" ) ,
212
- r#"{"statusCode":200,"headers":{},"multiValueHeaders":{} ,"body":"foo","isBase64Encoded":false}"#
306
+ r#"{"statusCode":200,"headers":{},"cookies":[] ,"body":"foo","isBase64Encoded":false}"#
213
307
) ;
214
308
}
215
309
216
310
#[ test]
217
311
fn serialize_multi_value_headers ( ) {
218
312
let res = LambdaResponse :: from_response (
219
- false ,
313
+ & RequestOrigin :: ApiGateway ,
220
314
Response :: builder ( )
221
315
. header ( "multi" , "a" )
222
316
. header ( "multi" , "b" )
@@ -229,4 +323,21 @@ mod tests {
229
323
r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"#
230
324
)
231
325
}
326
+
327
+ #[ test]
328
+ fn serialize_cookies ( ) {
329
+ let res = LambdaResponse :: from_response (
330
+ & RequestOrigin :: ApiGatewayV2 ,
331
+ Response :: builder ( )
332
+ . header ( "set-cookie" , "cookie1=a" )
333
+ . header ( "set-cookie" , "cookie2=b" )
334
+ . body ( Body :: from ( ( ) ) )
335
+ . expect ( "failed to create response" ) ,
336
+ ) ;
337
+ let json = serde_json:: to_string ( & res) . expect ( "failed to serialize to json" ) ;
338
+ assert_eq ! (
339
+ json,
340
+ r#"{"statusCode":200,"headers":{},"cookies":["cookie1=a","cookie2=b"],"isBase64Encoded":false}"#
341
+ )
342
+ }
232
343
}
0 commit comments