aws_smithy_http_server/request/mod.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6// This code was copied and then modified from Tokio's Axum.
7
8/* Copyright (c) 2022 Tower Contributors
9 *
10 * Permission is hereby granted, free of charge, to any
11 * person obtaining a copy of this software and associated
12 * documentation files (the "Software"), to deal in the
13 * Software without restriction, including without
14 * limitation the rights to use, copy, modify, merge,
15 * publish, distribute, sublicense, and/or sell copies of
16 * the Software, and to permit persons to whom the Software
17 * is furnished to do so, subject to the following
18 * conditions:
19 *
20 * The above copyright notice and this permission notice
21 * shall be included in all copies or substantial portions
22 * of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
25 * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
26 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
27 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
28 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
31 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 * DEALINGS IN THE SOFTWARE.
33 */
34
35//! Types and traits for extracting data from requests.
36//!
37//! See [Accessing Un-modelled data](https://github.com/smithy-lang/smithy-rs/blob/main/design/src/server/from_parts.md)
38//! a comprehensive overview.
39//!
40//! The following implementations exist:
41//! * Tuples up to size 8, extracting each component.
42//! * `Option<T>`: `Some(T)` if extracting `T` is successful, `None` otherwise.
43//! * `Result<T, T::Rejection>`: `Ok(T)` if extracting `T` is successful, `Err(T::Rejection)` otherwise.
44//!
45//! when `T: FromParts`.
46//!
47
48use std::{
49 convert::Infallible,
50 future::{ready, Future, Ready},
51};
52
53use futures_util::{
54 future::{try_join, MapErr, MapOk, TryJoin},
55 TryFutureExt,
56};
57use http::{request::Parts, Request, StatusCode};
58
59use crate::{
60 body::{empty, BoxBody},
61 rejection::any_rejections,
62 response::IntoResponse,
63};
64
65pub mod connect_info;
66pub mod extension;
67#[cfg(feature = "aws-lambda")]
68#[cfg_attr(docsrs, doc(cfg(feature = "aws-lambda")))]
69pub mod lambda;
70#[cfg(feature = "request-id")]
71#[cfg_attr(docsrs, doc(cfg(feature = "request-id")))]
72pub mod request_id;
73
74fn internal_server_error() -> http::Response<BoxBody> {
75 let mut response = http::Response::new(empty());
76 *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
77 response
78}
79
80/// Provides a protocol aware extraction from a [`Request`]. This borrows the [`Parts`], in contrast to
81/// [`FromRequest`] which consumes the entire [`http::Request`] including the body.
82pub trait FromParts<Protocol>: Sized {
83 /// The type of the extraction failures.
84 ///
85 /// Both serialization systems use this conversion contract. Generated serde calls
86 /// `into_response`; schema serde supplies the routed protocol to
87 /// `into_response_with_protocol`. Existing rejections inherit the latter's default.
88 /// Schema extractors can wrap an [`HttpModeledError`](crate::schema::HttpModeledError)
89 /// in [`ModeledRejection`](crate::response::ModeledRejection) to obtain this conversion
90 /// without implementing it themselves.
91 type Rejection: IntoResponse<Protocol>;
92
93 /// Extracts `self` from a [`Parts`] synchronously.
94 fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection>;
95}
96
97impl<P> FromParts<P> for () {
98 type Rejection = Infallible;
99
100 fn from_parts(_parts: &mut Parts) -> Result<Self, Self::Rejection> {
101 Ok(())
102 }
103}
104
105impl<P, T> FromParts<P> for (T,)
106where
107 T: FromParts<P>,
108{
109 type Rejection = T::Rejection;
110
111 fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
112 Ok((T::from_parts(parts)?,))
113 }
114}
115
116macro_rules! impl_from_parts {
117 ($error_name:ident, $($var:ident),+) => (
118 impl<P, $($var,)*> FromParts<P> for ($($var),*)
119 where
120 $($var: FromParts<P>,)*
121 {
122 type Rejection = any_rejections::$error_name<$($var::Rejection),*>;
123
124 fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
125 let tuple = (
126 $($var::from_parts(parts).map_err(any_rejections::$error_name::$var)?,)*
127 );
128 Ok(tuple)
129 }
130 }
131 )
132}
133
134impl_from_parts!(Two, A, B);
135impl_from_parts!(Three, A, B, C);
136impl_from_parts!(Four, A, B, C, D);
137impl_from_parts!(Five, A, B, C, D, E);
138impl_from_parts!(Six, A, B, C, D, E, F);
139impl_from_parts!(Seven, A, B, C, D, E, F, G);
140impl_from_parts!(Eight, A, B, C, D, E, F, G, H);
141
142/// Provides a protocol aware extraction from a [`Request`]. This consumes the
143/// [`Request`], including the body, in contrast to [`FromParts`] which borrows the [`Parts`].
144///
145/// This should not be implemented by hand. Code generation should implement this for your operations input. To extract
146/// items from a HTTP request [`FromParts`] should be used.
147pub trait FromRequest<Protocol, B>: Sized {
148 /// The type of the extraction failures.
149 type Rejection: IntoResponse<Protocol>;
150 /// The type of the extraction [`Future`].
151 type Future: Future<Output = Result<Self, Self::Rejection>>;
152
153 /// Extracts `self` from a [`Request`] asynchronously.
154 fn from_request(request: Request<B>) -> Self::Future;
155}
156
157impl<P, B, T1> FromRequest<P, B> for (T1,)
158where
159 T1: FromRequest<P, B>,
160{
161 type Rejection = T1::Rejection;
162 type Future = MapOk<T1::Future, fn(T1) -> (T1,)>;
163
164 fn from_request(request: Request<B>) -> Self::Future {
165 T1::from_request(request).map_ok(|t1| (t1,))
166 }
167}
168
169impl<P, B, T1, T2> FromRequest<P, B> for (T1, T2)
170where
171 T1: FromRequest<P, B>,
172 T2: FromParts<P>,
173 T1::Rejection: std::fmt::Display,
174 T2::Rejection: std::fmt::Display + IntoResponse<P>,
175{
176 type Rejection = any_rejections::Two<T1::Rejection, T2::Rejection>;
177 type Future = TryJoin<MapErr<T1::Future, fn(T1::Rejection) -> Self::Rejection>, Ready<Result<T2, Self::Rejection>>>;
178
179 fn from_request(request: Request<B>) -> Self::Future {
180 let (mut parts, body) = request.into_parts();
181 let t2_result: Result<T2, any_rejections::Two<T1::Rejection, T2::Rejection>> = T2::from_parts(&mut parts)
182 .map_err(|e| {
183 // The error is likely caused by a failure to construct a parameter from the
184 // `Request` required by the user handler. This typically occurs when the
185 // user handler expects a specific type, such as `Extension<State>`, but
186 // either the `ExtensionLayer` has not been added, or it adds a different
187 // type to the extension bag, such as `Extension<Arc<State>>`.
188 tracing::error!(
189 error = %e,
190 "additional parameter for the handler function could not be constructed");
191 any_rejections::Two::B(e)
192 });
193 try_join(
194 T1::from_request(Request::from_parts(parts, body)).map_err(|e| {
195 // `T1`, the first parameter of a handler function, represents the input parameter
196 // defined in the Smithy model. An error at this stage suggests that `T1` could not
197 // be constructed from the `Request`.
198 tracing::debug!(error = %e, "failed to deserialize request into operation's input");
199 any_rejections::Two::A(e)
200 }),
201 ready(t2_result),
202 )
203 }
204}
205
206impl<P, T> FromParts<P> for Option<T>
207where
208 T: FromParts<P>,
209{
210 type Rejection = Infallible;
211
212 fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
213 Ok(T::from_parts(parts).ok())
214 }
215}
216
217impl<P, T> FromParts<P> for Result<T, T::Rejection>
218where
219 T: FromParts<P>,
220{
221 type Rejection = Infallible;
222
223 fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
224 Ok(T::from_parts(parts))
225 }
226}