Skip to content

Commit 29d6644

Browse files
rcohjdisanti
authored andcommitted
[smithy-rs] Add http = "1.0" support to the http request wrapper (#3373)
## Motivation and Context - aws-sdk-rust#977 - smithy-rs#3365 ## Description Add `try_from` and `try_into` for HTTP 1.x to the HTTP request/response wrapper. This is a stepping stone en route to supporting Hyper 1.0 ## Testing - [x] New unit tests ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [x] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --------- Co-authored-by: John DiSanti <[email protected]>
1 parent be0205d commit 29d6644

File tree

11 files changed

+503
-86
lines changed

11 files changed

+503
-86
lines changed

sdk/aws-config/src/test_case.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ impl TestEnvironment {
282282
.await
283283
{
284284
Ok(()) => {}
285-
Err(e) => panic!("{}", e),
285+
Err(e) => panic!("{}", DisplayErrorContext(e.as_ref())),
286286
}
287287
let contents = rx.contents();
288288
let leaking_lines = self.lines_with_secrets(&contents);

sdk/aws-smithy-runtime-api/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ stable = true
2020
default = []
2121
client = []
2222
http-auth = ["dep:zeroize"]
23-
test-util = ["aws-smithy-types/test-util"]
23+
test-util = ["aws-smithy-types/test-util", "http-1x"]
2424
http-02x = []
25+
http-1x = []
2526

2627
[dependencies]
2728
bytes = "1"
@@ -37,6 +38,10 @@ version = "1.1.4"
3738
path = "../aws-smithy-types"
3839
version = "1.1.4"
3940

41+
[dependencies.http1]
42+
package = "http"
43+
version = "1"
44+
4045
[dependencies.tokio]
4146
version = "1.25"
4247
features = ["sync"]

sdk/aws-smithy-runtime-api/src/http.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! HTTP request and response types
77
88
mod error;
9+
mod extensions;
910
mod headers;
1011
mod request;
1112
mod response;

sdk/aws-smithy-runtime-api/src/http/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ impl HttpError {
2424
HttpError(err.into())
2525
}
2626

27+
#[allow(dead_code)]
28+
pub(super) fn invalid_extensions() -> Self {
29+
Self("Extensions were provided during initialization. This prevents the request format from being converted.".into())
30+
}
31+
2732
pub(super) fn invalid_header_value(err: InvalidHeaderValue) -> Self {
2833
Self(err.into())
2934
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use crate::http::HttpError;
7+
use http as http0;
8+
9+
#[derive(Default, Debug)]
10+
pub(crate) struct Extensions {
11+
extensions_02x: http0::Extensions,
12+
extensions_1x: http1::Extensions,
13+
}
14+
15+
impl Extensions {
16+
pub(crate) fn new() -> Self {
17+
Self::default()
18+
}
19+
20+
/// Adds an extension to the request extensions
21+
pub(crate) fn insert<T: Send + Sync + Clone + 'static>(&mut self, extension: T) {
22+
self.extensions_1x.insert(extension.clone());
23+
self.extensions_02x.insert(extension);
24+
}
25+
}
26+
27+
impl From<http0::Extensions> for Extensions {
28+
fn from(value: http0::Extensions) -> Self {
29+
Self {
30+
extensions_02x: value,
31+
extensions_1x: Default::default(),
32+
}
33+
}
34+
}
35+
36+
impl From<http1::Extensions> for Extensions {
37+
fn from(value: http1::Extensions) -> Self {
38+
Self {
39+
extensions_02x: Default::default(),
40+
extensions_1x: value,
41+
}
42+
}
43+
}
44+
45+
impl TryFrom<Extensions> for http0::Extensions {
46+
type Error = HttpError;
47+
48+
fn try_from(value: Extensions) -> Result<Self, Self::Error> {
49+
if value.extensions_1x.len() > value.extensions_02x.len() {
50+
Err(HttpError::invalid_extensions())
51+
} else {
52+
Ok(value.extensions_02x)
53+
}
54+
}
55+
}
56+
57+
impl TryFrom<Extensions> for http1::Extensions {
58+
type Error = HttpError;
59+
60+
fn try_from(value: Extensions) -> Result<Self, Self::Error> {
61+
if value.extensions_02x.len() > value.extensions_1x.len() {
62+
Err(HttpError::invalid_extensions())
63+
} else {
64+
Ok(value.extensions_1x)
65+
}
66+
}
67+
}

sdk/aws-smithy-runtime-api/src/http/headers.rs

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,29 @@ impl Headers {
4949
Self::default()
5050
}
5151

52+
#[cfg(feature = "http-1x")]
53+
pub(crate) fn http1_headermap(self) -> http1::HeaderMap {
54+
let mut headers = http1::HeaderMap::new();
55+
headers.reserve(self.headers.len());
56+
headers.extend(self.headers.into_iter().map(|(k, v)| {
57+
(
58+
k.map(|n| {
59+
http1::HeaderName::from_bytes(n.as_str().as_bytes()).expect("proven valid")
60+
}),
61+
v.into_http1x(),
62+
)
63+
}));
64+
headers
65+
}
66+
67+
#[cfg(feature = "http-02x")]
68+
pub(crate) fn http0_headermap(self) -> http0::HeaderMap {
69+
let mut headers = http0::HeaderMap::new();
70+
headers.reserve(self.headers.len());
71+
headers.extend(self.headers.into_iter().map(|(k, v)| (k, v.into_http02x())));
72+
headers
73+
}
74+
5275
/// Returns the value for a given key
5376
///
5477
/// If multiple values are associated, the first value is returned
@@ -181,6 +204,34 @@ impl TryFrom<HeaderMap> for Headers {
181204
}
182205
}
183206

207+
#[cfg(feature = "http-1x")]
208+
impl TryFrom<http1::HeaderMap> for Headers {
209+
type Error = HttpError;
210+
211+
fn try_from(value: http1::HeaderMap) -> Result<Self, Self::Error> {
212+
if let Some(e) = value
213+
.values()
214+
.filter_map(|value| std::str::from_utf8(value.as_bytes()).err())
215+
.next()
216+
{
217+
Err(HttpError::header_was_not_a_string(e))
218+
} else {
219+
let mut string_safe_headers: http0::HeaderMap<HeaderValue> = Default::default();
220+
string_safe_headers.extend(value.into_iter().map(|(k, v)| {
221+
(
222+
k.map(|v| {
223+
http0::HeaderName::from_bytes(v.as_str().as_bytes()).expect("known valid")
224+
}),
225+
HeaderValue::from_http1x(v).expect("validated above"),
226+
)
227+
}));
228+
Ok(Headers {
229+
headers: string_safe_headers,
230+
})
231+
}
232+
}
233+
}
234+
184235
use sealed::AsHeaderComponent;
185236

186237
mod sealed {
@@ -273,25 +324,57 @@ mod header_value {
273324
/// **Note**: Unlike `HeaderValue` in `http`, this only supports UTF-8 header values
274325
#[derive(Debug, Clone)]
275326
pub struct HeaderValue {
276-
_private: http0::HeaderValue,
327+
_private: Inner,
328+
}
329+
330+
#[derive(Debug, Clone)]
331+
enum Inner {
332+
H0(http0::HeaderValue),
333+
#[allow(dead_code)]
334+
H1(http1::HeaderValue),
277335
}
278336

279337
impl HeaderValue {
338+
#[allow(dead_code)]
280339
pub(crate) fn from_http02x(value: http0::HeaderValue) -> Result<Self, Utf8Error> {
281340
let _ = std::str::from_utf8(value.as_bytes())?;
282-
Ok(Self { _private: value })
341+
Ok(Self {
342+
_private: Inner::H0(value),
343+
})
344+
}
345+
346+
#[allow(dead_code)]
347+
pub(crate) fn from_http1x(value: http1::HeaderValue) -> Result<Self, Utf8Error> {
348+
let _ = std::str::from_utf8(value.as_bytes())?;
349+
Ok(Self {
350+
_private: Inner::H1(value),
351+
})
283352
}
284353

285354
#[allow(dead_code)]
286355
pub(crate) fn into_http02x(self) -> http0::HeaderValue {
287-
self._private
356+
match self._private {
357+
Inner::H0(v) => v,
358+
Inner::H1(v) => http0::HeaderValue::from_maybe_shared(v).expect("unreachable"),
359+
}
360+
}
361+
362+
#[allow(dead_code)]
363+
pub(crate) fn into_http1x(self) -> http1::HeaderValue {
364+
match self._private {
365+
Inner::H1(v) => v,
366+
Inner::H0(v) => http1::HeaderValue::from_maybe_shared(v).expect("unreachable"),
367+
}
288368
}
289369
}
290370

291371
impl AsRef<str> for HeaderValue {
292372
fn as_ref(&self) -> &str {
293-
std::str::from_utf8(self._private.as_bytes())
294-
.expect("unreachable—only strings may be stored")
373+
let bytes = match &self._private {
374+
Inner::H0(v) => v.as_bytes(),
375+
Inner::H1(v) => v.as_bytes(),
376+
};
377+
std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
295378
}
296379
}
297380

0 commit comments

Comments
 (0)