lambda_http/ext/
extensions.rs

1//! Extension methods for `http::Extensions` and `http::Request<T>` types
2
3use aws_lambda_events::query_map::QueryMap;
4use http::request::Parts;
5use lambda_runtime::Context;
6
7use crate::request::RequestContext;
8
9/// ALB/API gateway pre-parsed http query string parameters
10#[derive(Clone)]
11pub(crate) struct QueryStringParameters(pub(crate) QueryMap);
12
13/// API gateway pre-extracted url path parameters
14///
15/// These will always be empty for ALB requests
16#[derive(Clone)]
17pub(crate) struct PathParameters(pub(crate) QueryMap);
18
19/// API gateway configured
20/// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html)
21///
22/// These will always be empty for ALB requests
23#[derive(Clone)]
24pub(crate) struct StageVariables(pub(crate) QueryMap);
25
26/// ALB/API gateway raw http path without any stage information
27#[derive(Clone)]
28pub(crate) struct RawHttpPath(pub(crate) String);
29
30/// Extensions for [`lambda_http::Request`], `http::request::Parts`, and `http::Extensions` structs
31/// that provide access to
32/// [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format)
33/// and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html)
34/// features.
35///
36/// [`lambda_http::Request`]: crate::Request
37pub trait RequestExt {
38    /// Return the raw http path for a request without any stage information.
39    fn raw_http_path(&self) -> &str;
40
41    /// Configures instance with the raw http path.
42    fn with_raw_http_path<S>(self, path: S) -> Self
43    where
44        S: Into<String>;
45
46    /// Return pre-parsed HTTP query string parameters, parameters
47    /// provided after the `?` portion of a URL,
48    /// associated with the API gateway request.
49    ///
50    /// The yielded value represents both single and multi-valued
51    /// parameters alike. When multiple query string parameters with the same
52    /// name are expected, use `query_string_parameters().all("many")` to
53    /// retrieve them all.
54    ///
55    /// Having no query parameters will yield an empty `QueryMap`.
56    fn query_string_parameters(&self) -> QueryMap;
57
58    /// Return pre-parsed HTTP query string parameters, parameters
59    /// provided after the `?` portion of a URL,
60    /// associated with the API gateway request.
61    ///
62    /// The yielded value represents both single and multi-valued
63    /// parameters alike. When multiple query string parameters with the same
64    /// name are expected, use
65    /// `query_string_parameters_ref().and_then(|params| params.all("many"))` to
66    /// retrieve them all.
67    ///
68    /// Having no query parameters will yield `None`.
69    fn query_string_parameters_ref(&self) -> Option<&QueryMap>;
70
71    /// Configures instance with query string parameters
72    ///
73    /// This is intended for use in mock testing contexts.
74    fn with_query_string_parameters<Q>(self, parameters: Q) -> Self
75    where
76        Q: Into<QueryMap>;
77
78    /// Return pre-extracted path parameters, parameter provided in URL placeholders
79    /// `/foo/{bar}/baz/{qux}`,
80    /// associated with the API gateway request. Having no path parameters
81    /// will yield an empty `QueryMap`.
82    ///
83    /// These will always be empty for ALB triggered requests.
84    fn path_parameters(&self) -> QueryMap;
85
86    /// Return pre-extracted path parameters, parameter provided in URL placeholders
87    /// `/foo/{bar}/baz/{qux}`,
88    /// associated with the API gateway request. Having no path parameters
89    /// will yield `None`.
90    ///
91    /// These will always be `None` for ALB triggered requests.
92    fn path_parameters_ref(&self) -> Option<&QueryMap>;
93
94    /// Configures instance with path parameters
95    ///
96    /// This is intended for use in mock testing contexts.
97    fn with_path_parameters<P>(self, parameters: P) -> Self
98    where
99        P: Into<QueryMap>;
100
101    /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html)
102    /// associated with the API gateway request. Having no stage parameters
103    /// will yield an empty `QueryMap`.
104    ///
105    /// These will always be empty for ALB triggered requests.
106    fn stage_variables(&self) -> QueryMap;
107
108    /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html)
109    /// associated with the API gateway request. Having no stage parameters
110    /// will yield `None`.
111    ///
112    /// These will always be `None` for ALB triggered requests.
113    fn stage_variables_ref(&self) -> Option<&QueryMap>;
114
115    /// Configures instance with stage variables
116    ///
117    /// This is intended for use in mock testing contexts.
118    fn with_stage_variables<V>(self, variables: V) -> Self
119    where
120        V: Into<QueryMap>;
121
122    /// Return request context data associated with the ALB or
123    /// API gateway request
124    fn request_context(&self) -> RequestContext;
125
126    /// Return a reference to the request context data associated with the ALB or
127    /// API gateway request
128    fn request_context_ref(&self) -> Option<&RequestContext>;
129
130    /// Configures instance with request context
131    ///
132    /// This is intended for use in mock testing contexts.
133    fn with_request_context(self, context: RequestContext) -> Self;
134
135    /// Return Lambda function context data associated with the
136    /// request
137    fn lambda_context(&self) -> Context;
138
139    /// Return a reference to the Lambda function context data associated with the
140    /// request
141    fn lambda_context_ref(&self) -> Option<&Context>;
142
143    /// Configures instance with lambda context
144    fn with_lambda_context(self, context: Context) -> Self;
145}
146
147impl RequestExt for http::Extensions {
148    fn raw_http_path(&self) -> &str {
149        self.get::<RawHttpPath>()
150            .map(|RawHttpPath(path)| path.as_str())
151            .unwrap_or_default()
152    }
153
154    fn with_raw_http_path<S>(self, path: S) -> Self
155    where
156        S: Into<String>,
157    {
158        let mut s = self;
159        s.insert(RawHttpPath(path.into()));
160        s
161    }
162
163    fn query_string_parameters(&self) -> QueryMap {
164        self.query_string_parameters_ref().cloned().unwrap_or_default()
165    }
166
167    fn query_string_parameters_ref(&self) -> Option<&QueryMap> {
168        self.get::<QueryStringParameters>().and_then(
169            |QueryStringParameters(params)| {
170                if params.is_empty() {
171                    None
172                } else {
173                    Some(params)
174                }
175            },
176        )
177    }
178
179    fn with_query_string_parameters<Q>(self, parameters: Q) -> Self
180    where
181        Q: Into<QueryMap>,
182    {
183        let mut s = self;
184        s.insert(QueryStringParameters(parameters.into()));
185        s
186    }
187
188    fn path_parameters(&self) -> QueryMap {
189        self.path_parameters_ref().cloned().unwrap_or_default()
190    }
191
192    fn path_parameters_ref(&self) -> Option<&QueryMap> {
193        self.get::<PathParameters>().and_then(
194            |PathParameters(params)| {
195                if params.is_empty() {
196                    None
197                } else {
198                    Some(params)
199                }
200            },
201        )
202    }
203
204    fn with_path_parameters<P>(self, parameters: P) -> Self
205    where
206        P: Into<QueryMap>,
207    {
208        let mut s = self;
209        s.insert(PathParameters(parameters.into()));
210        s
211    }
212
213    fn stage_variables(&self) -> QueryMap {
214        self.stage_variables_ref().cloned().unwrap_or_default()
215    }
216
217    fn stage_variables_ref(&self) -> Option<&QueryMap> {
218        self.get::<StageVariables>()
219            .and_then(|StageVariables(vars)| if vars.is_empty() { None } else { Some(vars) })
220    }
221
222    fn with_stage_variables<V>(self, variables: V) -> Self
223    where
224        V: Into<QueryMap>,
225    {
226        let mut s = self;
227        s.insert(StageVariables(variables.into()));
228        s
229    }
230
231    fn request_context(&self) -> RequestContext {
232        self.request_context_ref()
233            .cloned()
234            .expect("Request did not contain a request context")
235    }
236
237    fn request_context_ref(&self) -> Option<&RequestContext> {
238        self.get::<RequestContext>()
239    }
240
241    fn with_request_context(self, context: RequestContext) -> Self {
242        let mut s = self;
243        s.insert(context);
244        s
245    }
246
247    fn lambda_context(&self) -> Context {
248        self.lambda_context_ref()
249            .cloned()
250            .expect("Request did not contain a lambda context")
251    }
252
253    fn lambda_context_ref(&self) -> Option<&Context> {
254        self.get::<Context>()
255    }
256
257    fn with_lambda_context(self, context: Context) -> Self {
258        let mut s = self;
259        s.insert(context);
260        s
261    }
262}
263
264impl RequestExt for Parts {
265    fn raw_http_path(&self) -> &str {
266        self.extensions.raw_http_path()
267    }
268
269    fn with_raw_http_path<S>(self, path: S) -> Self
270    where
271        S: Into<String>,
272    {
273        let mut s = self;
274        s.extensions = s.extensions.with_raw_http_path(path);
275
276        s
277    }
278
279    fn query_string_parameters(&self) -> QueryMap {
280        self.extensions.query_string_parameters()
281    }
282
283    fn query_string_parameters_ref(&self) -> Option<&QueryMap> {
284        self.extensions.query_string_parameters_ref()
285    }
286
287    fn with_query_string_parameters<Q>(self, parameters: Q) -> Self
288    where
289        Q: Into<QueryMap>,
290    {
291        let mut s = self;
292        s.extensions = s.extensions.with_query_string_parameters(parameters);
293
294        s
295    }
296
297    fn path_parameters(&self) -> QueryMap {
298        self.extensions.path_parameters()
299    }
300
301    fn path_parameters_ref(&self) -> Option<&QueryMap> {
302        self.extensions.path_parameters_ref()
303    }
304
305    fn with_path_parameters<P>(self, parameters: P) -> Self
306    where
307        P: Into<QueryMap>,
308    {
309        let mut s = self;
310        s.extensions = s.extensions.with_path_parameters(parameters);
311
312        s
313    }
314
315    fn stage_variables(&self) -> QueryMap {
316        self.extensions.stage_variables()
317    }
318
319    fn stage_variables_ref(&self) -> Option<&QueryMap> {
320        self.extensions.stage_variables_ref()
321    }
322
323    fn with_stage_variables<V>(self, variables: V) -> Self
324    where
325        V: Into<QueryMap>,
326    {
327        let mut s = self;
328        s.extensions = s.extensions.with_stage_variables(variables);
329
330        s
331    }
332
333    fn request_context(&self) -> RequestContext {
334        self.extensions.request_context()
335    }
336
337    fn request_context_ref(&self) -> Option<&RequestContext> {
338        self.extensions.request_context_ref()
339    }
340
341    fn with_request_context(self, context: RequestContext) -> Self {
342        let mut s = self;
343        s.extensions = s.extensions.with_request_context(context);
344
345        s
346    }
347
348    fn lambda_context(&self) -> Context {
349        self.extensions.lambda_context()
350    }
351
352    fn lambda_context_ref(&self) -> Option<&Context> {
353        self.extensions.lambda_context_ref()
354    }
355
356    fn with_lambda_context(self, context: Context) -> Self {
357        let mut s = self;
358        s.extensions = s.extensions.with_lambda_context(context);
359
360        s
361    }
362}
363
364fn map_req_ext<B, F>(req: http::Request<B>, f: F) -> http::Request<B>
365where
366    F: FnOnce(http::Extensions) -> http::Extensions,
367{
368    let (mut parts, body) = req.into_parts();
369    parts.extensions = (f)(parts.extensions);
370
371    http::Request::from_parts(parts, body)
372}
373
374impl<B> RequestExt for http::Request<B> {
375    fn raw_http_path(&self) -> &str {
376        self.extensions().raw_http_path()
377    }
378
379    fn with_raw_http_path<S>(self, path: S) -> Self
380    where
381        S: Into<String>,
382    {
383        map_req_ext(self, |ext| ext.with_raw_http_path(path))
384    }
385
386    fn query_string_parameters(&self) -> QueryMap {
387        self.extensions().query_string_parameters()
388    }
389
390    fn query_string_parameters_ref(&self) -> Option<&QueryMap> {
391        self.extensions().query_string_parameters_ref()
392    }
393
394    fn with_query_string_parameters<Q>(self, parameters: Q) -> Self
395    where
396        Q: Into<QueryMap>,
397    {
398        map_req_ext(self, |ext| ext.with_query_string_parameters(parameters))
399    }
400
401    fn path_parameters(&self) -> QueryMap {
402        self.extensions().path_parameters()
403    }
404
405    fn path_parameters_ref(&self) -> Option<&QueryMap> {
406        self.extensions().path_parameters_ref()
407    }
408
409    fn with_path_parameters<P>(self, parameters: P) -> Self
410    where
411        P: Into<QueryMap>,
412    {
413        map_req_ext(self, |ext| ext.with_path_parameters(parameters))
414    }
415
416    fn stage_variables(&self) -> QueryMap {
417        self.extensions().stage_variables()
418    }
419
420    fn stage_variables_ref(&self) -> Option<&QueryMap> {
421        self.extensions().stage_variables_ref()
422    }
423
424    fn with_stage_variables<V>(self, variables: V) -> Self
425    where
426        V: Into<QueryMap>,
427    {
428        map_req_ext(self, |ext| ext.with_stage_variables(variables))
429    }
430
431    fn request_context(&self) -> RequestContext {
432        self.extensions().request_context()
433    }
434
435    fn request_context_ref(&self) -> Option<&RequestContext> {
436        self.extensions().request_context_ref()
437    }
438
439    fn with_request_context(self, context: RequestContext) -> Self {
440        map_req_ext(self, |ext| ext.with_request_context(context))
441    }
442
443    fn lambda_context(&self) -> Context {
444        self.extensions().lambda_context()
445    }
446
447    fn lambda_context_ref(&self) -> Option<&Context> {
448        self.extensions().lambda_context_ref()
449    }
450
451    fn with_lambda_context(self, context: Context) -> Self {
452        map_req_ext(self, |ext| ext.with_lambda_context(context))
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use aws_lambda_events::query_map::QueryMap;
459    use http::Extensions;
460
461    use crate::Request;
462
463    use super::RequestExt;
464
465    #[test]
466    fn extensions_can_mock_query_string_parameters_ext() {
467        let ext = Extensions::default();
468        assert_eq!(ext.query_string_parameters_ref(), None);
469        assert_eq!(ext.query_string_parameters(), QueryMap::default());
470
471        let mocked: QueryMap = hashmap! {
472            "foo".into() => vec!["bar".into()]
473        }
474        .into();
475
476        let ext = ext.with_query_string_parameters(mocked.clone());
477        assert_eq!(ext.query_string_parameters_ref(), Some(&mocked));
478        assert_eq!(ext.query_string_parameters(), mocked);
479    }
480
481    #[test]
482    fn parts_can_mock_query_string_parameters_ext() {
483        let (parts, _) = Request::default().into_parts();
484        assert_eq!(parts.query_string_parameters_ref(), None);
485        assert_eq!(parts.query_string_parameters(), QueryMap::default());
486
487        let mocked: QueryMap = hashmap! {
488            "foo".into() => vec!["bar".into()]
489        }
490        .into();
491
492        let parts = parts.with_query_string_parameters(mocked.clone());
493        assert_eq!(parts.query_string_parameters_ref(), Some(&mocked));
494        assert_eq!(parts.query_string_parameters(), mocked);
495    }
496
497    #[test]
498    fn requests_can_mock_query_string_parameters_ext() {
499        let request = Request::default();
500        assert_eq!(request.query_string_parameters_ref(), None);
501        assert_eq!(request.query_string_parameters(), QueryMap::default());
502
503        let mocked: QueryMap = hashmap! {
504            "foo".into() => vec!["bar".into()]
505        }
506        .into();
507
508        let request = request.with_query_string_parameters(mocked.clone());
509        assert_eq!(request.query_string_parameters_ref(), Some(&mocked));
510        assert_eq!(request.query_string_parameters(), mocked);
511    }
512
513    #[test]
514    fn extensions_can_mock_path_parameters_ext() {
515        let ext = Extensions::default();
516        assert_eq!(ext.path_parameters_ref(), None);
517        assert_eq!(ext.path_parameters(), QueryMap::default());
518
519        let mocked: QueryMap = hashmap! {
520            "foo".into() => vec!["bar".into()]
521        }
522        .into();
523
524        let ext = ext.with_path_parameters(mocked.clone());
525        assert_eq!(ext.path_parameters_ref(), Some(&mocked));
526        assert_eq!(ext.path_parameters(), mocked);
527    }
528
529    #[test]
530    fn parts_can_mock_path_parameters_ext() {
531        let (parts, _) = Request::default().into_parts();
532        assert_eq!(parts.path_parameters_ref(), None);
533        assert_eq!(parts.path_parameters(), QueryMap::default());
534
535        let mocked: QueryMap = hashmap! {
536            "foo".into() => vec!["bar".into()]
537        }
538        .into();
539
540        let parts = parts.with_path_parameters(mocked.clone());
541        assert_eq!(parts.path_parameters_ref(), Some(&mocked));
542        assert_eq!(parts.path_parameters(), mocked);
543    }
544
545    #[test]
546    fn requests_can_mock_path_parameters_ext() {
547        let request = Request::default();
548        assert_eq!(request.path_parameters_ref(), None);
549        assert_eq!(request.path_parameters(), QueryMap::default());
550
551        let mocked: QueryMap = hashmap! {
552            "foo".into() => vec!["bar".into()]
553        }
554        .into();
555
556        let request = request.with_path_parameters(mocked.clone());
557        assert_eq!(request.path_parameters_ref(), Some(&mocked));
558        assert_eq!(request.path_parameters(), mocked);
559    }
560
561    #[test]
562    fn extensions_can_mock_stage_variables_ext() {
563        let ext = Extensions::default();
564        assert_eq!(ext.stage_variables_ref(), None);
565        assert_eq!(ext.stage_variables(), QueryMap::default());
566
567        let mocked: QueryMap = hashmap! {
568            "foo".into() => vec!["bar".into()]
569        }
570        .into();
571
572        let ext = ext.with_stage_variables(mocked.clone());
573        assert_eq!(ext.stage_variables_ref(), Some(&mocked));
574        assert_eq!(ext.stage_variables(), mocked);
575    }
576
577    #[test]
578    fn parts_can_mock_stage_variables_ext() {
579        let (parts, _) = Request::default().into_parts();
580        assert_eq!(parts.stage_variables_ref(), None);
581        assert_eq!(parts.stage_variables(), QueryMap::default());
582
583        let mocked: QueryMap = hashmap! {
584            "foo".into() => vec!["bar".into()]
585        }
586        .into();
587
588        let parts = parts.with_stage_variables(mocked.clone());
589        assert_eq!(parts.stage_variables_ref(), Some(&mocked));
590        assert_eq!(parts.stage_variables(), mocked);
591    }
592
593    #[test]
594    fn requests_can_mock_stage_variables_ext() {
595        let request = Request::default();
596        assert_eq!(request.stage_variables_ref(), None);
597        assert_eq!(request.stage_variables(), QueryMap::default());
598
599        let mocked: QueryMap = hashmap! {
600            "foo".into() => vec!["bar".into()]
601        }
602        .into();
603
604        let request = request.with_stage_variables(mocked.clone());
605        assert_eq!(request.stage_variables_ref(), Some(&mocked));
606        assert_eq!(request.stage_variables(), mocked);
607    }
608
609    #[test]
610    fn extensions_can_mock_raw_http_path_ext() {
611        let ext = Extensions::default().with_raw_http_path("/raw-path");
612        assert_eq!("/raw-path", ext.raw_http_path());
613    }
614
615    #[test]
616    fn parts_can_mock_raw_http_path_ext() {
617        let (parts, _) = Request::default().into_parts();
618        let parts = parts.with_raw_http_path("/raw-path");
619        assert_eq!("/raw-path", parts.raw_http_path());
620    }
621
622    #[test]
623    fn requests_can_mock_raw_http_path_ext() {
624        let request = Request::default().with_raw_http_path("/raw-path");
625        assert_eq!("/raw-path", request.raw_http_path());
626    }
627}