-
Notifications
You must be signed in to change notification settings - Fork 197
/
Copy pathrejection.rs
207 lines (186 loc) · 11.4 KB
/
rejection.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
//! Rejection types.
//!
//! This module contains types that are commonly used as the `E` error type in functions that
//! handle requests and responses that return `Result<T, E>` throughout the framework. These
//! include functions to deserialize incoming requests and serialize outgoing responses.
//!
//! All types end with `Rejection`. There are two types:
//!
//! 1. [`RequestRejection`]s are used when the framework fails to deserialize the request into the
//! corresponding operation input.
//! 2. [`ResponseRejection`]s are used when the framework fails to serialize the operation
//! output into a response.
//!
//! They are called _rejection_ types and not _error_ types to signal that the input was _rejected_
//! (as opposed to it causing a recoverable error that would need to be handled, or an
//! unrecoverable error). For example, a [`RequestRejection`] simply means that the request was
//! rejected; there isn't really anything wrong with the service itself that the service
//! implementer would need to handle.
//!
//! Rejection types are an _internal_ detail about the framework: they can be added, removed, and
//! modified at any time without causing breaking changes. They are not surfaced to clients or the
//! service implementer in any way (including this documentation): indeed, they can't be converted
//! into responses. They serve as a mechanism to keep track of all the possible errors that can
//! occur when processing a request or a response, in far more detail than what AWS protocols need
//! to. This is why they are so granular: other (possibly protocol-specific) error types (like
//! [`crate::proto::rest_json_1::runtime_error::RuntimeError`]) can "group" them when exposing
//! errors to clients while the framework does not need to sacrifice fidelity in private error
//! handling routines, and future-proofing itself at the same time (for example, we might want to
//! record metrics about rejection types).
//!
//! Rejection types implement [`std::error::Error`], and some take in type-erased boxed errors
//! (`crate::Error`) to represent their underlying causes, so they can be composed with other types
//! that take in (possibly type-erased) [`std::error::Error`]s, like
//! [`crate::proto::rest_json_1::runtime_error::RuntimeError`], thus allowing us to represent the
//! full error chain.
//!
//! This module hosts rejection types _specific_ to the [`crate::proto::rest_json_1`] protocol, but
//! the paragraphs above apply to _all_ protocol-specific rejection types.
//!
//! Similarly, rejection type variants are exhaustively documented solely in this module if they have
//! direct counterparts in other protocols. This is to avoid documentation getting out of date.
//!
//! Consult `crate::proto::$protocolName::rejection` for rejection types for other protocols.
use crate::rejection::MissingContentTypeReason;
use std::num::TryFromIntError;
use thiserror::Error;
/// Errors that can occur when serializing the operation output provided by the service implementer
/// into an HTTP response.
#[derive(Debug, Error)]
pub enum ResponseRejection {
/// Used when the service implementer provides an integer outside the 100-999 range for a
/// member targeted by `httpResponseCode`.
/// See <https://github.com/awslabs/smithy/issues/1116>.
#[error("invalid bound HTTP status code; status codes must be inside the 100-999 range: {0}")]
InvalidHttpStatusCode(TryFromIntError),
/// Used when an invalid HTTP header name (a value that cannot be parsed as an
/// [`http::header::HeaderName`]) or HTTP header value (a value that cannot be parsed as an
/// [`http::header::HeaderValue`]) is provided for a shape member bound to an HTTP header with
/// `httpHeader` or `httpPrefixHeaders`.
/// Used when failing to serialize an `httpPayload`-bound struct into an HTTP response body.
#[error("error building HTTP response: {0}")]
Build(#[from] aws_smithy_http::operation::error::BuildError),
/// Used when failing to serialize a struct into a `String` for the JSON-encoded HTTP response
/// body.
/// Fun fact: as of writing, this can only happen when date formatting
/// ([`aws_smithy_types::date_time::DateTime:fmt`]) fails, which can only happen if the
/// supplied timestamp is outside of the valid range when formatting using RFC-3339, i.e. a
/// date outside the `0001-01-01T00:00:00.000Z`-`9999-12-31T23:59:59.999Z` range is supplied.
#[error("error serializing JSON-encoded body: {0}")]
Serialization(#[from] aws_smithy_http::operation::error::SerializationError),
/// Used when consuming an [`http::response::Builder`] into the constructed [`http::Response`]
/// when calling [`http::response::Builder::body`].
/// This error can happen if an invalid HTTP header value (a value that cannot be parsed as an
/// `[http::header::HeaderValue]`) is used for the protocol-specific response `Content-Type`
/// header, or for additional protocol-specific headers (like `X-Amzn-Errortype` to signal
/// errors in RestJson1).
#[error("error building HTTP response: {0}")]
HttpBuild(#[from] http::Error),
}
/// Errors that can occur when deserializing an HTTP request into an _operation input_, the input
/// that is passed as the first argument to operation handlers.
///
/// This type allows us to easily keep track of all the possible errors that can occur in the
/// lifecycle of an incoming HTTP request.
///
/// Many inner code-generated and runtime deserialization functions use this as their error type,
/// when they can only instantiate a subset of the variants (most likely a single one). This is a
/// deliberate design choice to keep code generation simple. After all, this type is an inner
/// detail of the framework the service implementer does not interact with.
///
/// If a variant takes in a value, it represents the underlying cause of the error.
///
/// The variants are _roughly_ sorted in the order in which the HTTP request is processed.
#[derive(Debug, Error)]
pub enum RequestRejection {
/// Used when failing to convert non-streaming requests into a byte slab with
/// `hyper::body::to_bytes`.
#[error("error converting non-streaming body to bytes: {0}")]
BufferHttpBodyBytes(crate::Error),
/// Used when checking the `Content-Type` header.
#[error("expected `Content-Type` header not found: {0}")]
MissingContentType(MissingContentTypeReason),
/// Used when failing to deserialize the HTTP body's bytes into a JSON document conforming to
/// the modeled input it should represent.
#[error("error deserializing request HTTP body as JSON: {0}")]
JsonDeserialize(#[from] aws_smithy_json::deserialize::error::DeserializeError),
/// Used when failing to parse HTTP headers that are bound to input members with the `httpHeader`
/// or the `httpPrefixHeaders` traits.
#[error("error binding request HTTP headers: {0}")]
HeaderParse(#[from] aws_smithy_http::header::ParseError),
// In theory, the next two errors should never happen because the router should have already
// rejected the request.
/// Used when the URI pattern has a literal after the greedy label, and it is not found in the
/// request's URL.
#[error("request URI does not match pattern because of literal suffix after greedy label was not found")]
UriPatternGreedyLabelPostfixNotFound,
/// Used when the `nom` parser's input does not match the URI pattern.
#[error("request URI does not match `@http` URI pattern: {0}")]
UriPatternMismatch(crate::Error),
/// Used when percent-decoding URL query string.
/// Used when percent-decoding URI path label.
/// This is caused when calling
/// [`percent_encoding::percent_decode_str`](https://docs.rs/percent-encoding/latest/percent_encoding/fn.percent_decode_str.html).
/// This can happen when the percent-encoded data decodes to bytes that are
/// not a well-formed UTF-8 string.
#[error("request URI cannot be percent decoded into valid UTF-8")]
PercentEncodedUriNotValidUtf8(#[from] core::str::Utf8Error),
/// Used when failing to deserialize strings from a URL query string and from URI path labels
/// into an [`aws_smithy_types::DateTime`].
#[error("error parsing timestamp from request URI: {0}")]
DateTimeParse(#[from] aws_smithy_types::date_time::DateTimeParseError),
/// Used when failing to deserialize strings from a URL query string and from URI path labels
/// into "primitive" types.
#[error("error parsing primitive type from request URI: {0}")]
PrimitiveParse(#[from] aws_smithy_types::primitive::PrimitiveParseError),
/// Used when consuming the input struct builder, and constraint violations occur.
// This rejection is constructed directly in the code-generated SDK instead of in this crate.
#[error("request does not adhere to modeled constraints: {0}")]
ConstraintViolation(String),
}
// Consider a conversion between `T` and `U` followed by a bubbling up of the conversion error
// through `Result<_, RequestRejection>`. This [`From`] implementation accomodates the special case
// where `T` and `U` are equal, in such cases `T`/`U` a enjoy `TryFrom<T>` with
// `Err = std::convert::Infallible`.
//
// Note that when `!` stabilizes `std::convert::Infallible` will become an alias for `!` and there
// will be a blanket `impl From<!> for T`. This will remove the need for this implementation.
//
// More details on this can be found in the following links:
// - https://doc.rust-lang.org/std/primitive.never.html
// - https://doc.rust-lang.org/std/convert/enum.Infallible.html#future-compatibility
impl From<std::convert::Infallible> for RequestRejection {
fn from(_err: std::convert::Infallible) -> Self {
// We opt for this `match` here rather than [`unreachable`] to assure the reader that this
// code path is dead.
match _err {}
}
}
// TODO Remove once MissingContentTypeReason impls Error
impl From<MissingContentTypeReason> for RequestRejection {
fn from(e: MissingContentTypeReason) -> Self {
Self::MissingContentType(e)
}
}
// These converters are solely to make code-generation simpler. They convert from a specific error
// type (from a runtime/third-party crate or the standard library) into a variant of the
// [`crate::rejection::RequestRejection`] enum holding the type-erased boxed [`crate::Error`]
// type. Generated functions that use [crate::rejection::RequestRejection] can thus use `?` to
// bubble up instead of having to sprinkle things like [`Result::map_err`] everywhere.
impl From<nom::Err<nom::error::Error<&str>>> for RequestRejection {
fn from(err: nom::Err<nom::error::Error<&str>>) -> Self {
Self::UriPatternMismatch(crate::Error::new(err.to_owned()))
}
}
// `[crate::body::Body]` is `[hyper::Body]`, whose associated `Error` type is `[hyper::Error]`. We
// need this converter for when we convert the body into bytes in the framework, since protocol
// tests use `[crate::body::Body]` as their body type when constructing requests (and almost
// everyone will run a Hyper-based server in their services).
convert_to_request_rejection!(hyper::Error, BufferHttpBodyBytes);
// Useful in general, but it also required in order to accept Lambda HTTP requests using
// `Router<lambda_http::Body>` since `lambda_http::Error` is a type alias for `Box<dyn Error + ..>`.
convert_to_request_rejection!(Box<dyn std::error::Error + Send + Sync + 'static>, BufferHttpBodyBytes);