Skip to content

Commit

Permalink
Add http = "1.0" support to the http request wrapper
Browse files Browse the repository at this point in the history
- Requests are not creatible if extensions exist avoiding the cross-version extension issue.
- Response _were_ creatable. For responses, the wrapper will error if extensions existed and you attempt to cross convert Http versions.
  • Loading branch information
rcoh committed Jan 17, 2024
1 parent bac720e commit d59096a
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 75 deletions.
4 changes: 3 additions & 1 deletion rust-runtime/aws-smithy-runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ repository = "https://github.com/smithy-lang/smithy-rs"
default = []
client = []
http-auth = ["dep:zeroize"]
test-util = ["aws-smithy-types/test-util"]
test-util = ["aws-smithy-types/test-util", "http-1x"]
http-02x = []
http-1x = []

[dependencies]
aws-smithy-async = { path = "../aws-smithy-async" }
aws-smithy-types = { path = "../aws-smithy-types" }
bytes = "1"
http = "0.2.9"
http1 = { package = "http", version = "1" }
pin-project-lite = "0.2"
tokio = { version = "1.25", features = ["sync"] }
tracing = "0.1"
Expand Down
5 changes: 5 additions & 0 deletions rust-runtime/aws-smithy-runtime-api/src/http/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ impl HttpError {
HttpError(err.into())
}

#[allow(dead_code)]
pub(super) fn invalid_extensions() -> Self {
Self("Extensions were provided during initialization. This prevents the request format from being converted.".into())
}

pub(super) fn invalid_header_value(err: InvalidHeaderValue) -> Self {
Self(err.into())
}
Expand Down
91 changes: 86 additions & 5 deletions rust-runtime/aws-smithy-runtime-api/src/http/headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,29 @@ impl Headers {
Self::default()
}

#[cfg(feature = "http-1x")]
pub(crate) fn http1_headermap(self) -> http1::HeaderMap {
let mut headers = http1::HeaderMap::new();
headers.reserve(self.headers.len());
headers.extend(self.headers.into_iter().map(|(k, v)| {
(
k.map(|n| {
http1::HeaderName::from_bytes(n.as_str().as_bytes()).expect("proven valid")
}),
v.into_http1x(),
)
}));
headers
}

#[cfg(feature = "http-02x")]
pub(crate) fn http0_headermap(self) -> http0::HeaderMap {
let mut headers = http0::HeaderMap::new();
headers.reserve(self.headers.len());
headers.extend(self.headers.into_iter().map(|(k, v)| (k, v.into_http02x())));
headers
}

/// Returns the value for a given key
///
/// If multiple values are associated, the first value is returned
Expand Down Expand Up @@ -181,6 +204,34 @@ impl TryFrom<HeaderMap> for Headers {
}
}

#[cfg(feature = "http-1x")]
impl TryFrom<http1::HeaderMap> for Headers {
type Error = HttpError;

fn try_from(value: http1::HeaderMap) -> Result<Self, Self::Error> {
if let Some(e) = value
.values()
.filter_map(|value| std::str::from_utf8(value.as_bytes()).err())
.next()
{
Err(HttpError::header_was_not_a_string(e))
} else {
let mut string_safe_headers: http0::HeaderMap<HeaderValue> = Default::default();
string_safe_headers.extend(value.into_iter().map(|(k, v)| {
(
k.map(|v| {
http0::HeaderName::from_bytes(v.as_str().as_bytes()).expect("known valid")
}),
HeaderValue::from_http1x(v).expect("validated above"),
)
}));
Ok(Headers {
headers: string_safe_headers,
})
}
}
}

use sealed::AsHeaderComponent;

mod sealed {
Expand Down Expand Up @@ -273,25 +324,55 @@ mod header_value {
/// **Note**: Unlike `HeaderValue` in `http`, this only supports UTF-8 header values
#[derive(Debug, Clone)]
pub struct HeaderValue {
_private: http0::HeaderValue,
_private: Inner,
}

#[derive(Debug, Clone)]
enum Inner {
H0(http0::HeaderValue),
#[allow(dead_code)]
H1(http1::HeaderValue),
}

impl HeaderValue {
pub(crate) fn from_http02x(value: http0::HeaderValue) -> Result<Self, Utf8Error> {
let _ = std::str::from_utf8(value.as_bytes())?;
Ok(Self { _private: value })
Ok(Self {
_private: Inner::H0(value),
})
}

pub(crate) fn from_http1x(value: http1::HeaderValue) -> Result<Self, Utf8Error> {
let _ = std::str::from_utf8(value.as_bytes())?;
Ok(Self {
_private: Inner::H1(value),
})
}

#[allow(dead_code)]
pub(crate) fn into_http02x(self) -> http0::HeaderValue {
self._private
match self._private {
Inner::H0(v) => v,
Inner::H1(v) => http0::HeaderValue::from_maybe_shared(v).expect("unreachable"),
}
}

#[allow(dead_code)]
pub(crate) fn into_http1x(self) -> http1::HeaderValue {
match self._private {
Inner::H1(v) => v,
Inner::H0(v) => http1::HeaderValue::from_maybe_shared(v).expect("unreachable"),
}
}
}

impl AsRef<str> for HeaderValue {
fn as_ref(&self) -> &str {
std::str::from_utf8(self._private.as_bytes())
.expect("unreachable—only strings may be stored")
let bytes = match &self._private {
Inner::H0(v) => v.as_bytes(),
Inner::H1(v) => v.as_bytes(),
};
std::str::from_utf8(bytes).expect("unreachable—only strings may be stored")
}
}

Expand Down
Loading

0 comments on commit d59096a

Please sign in to comment.