Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create the HTTP Response wrapper types #3148

Merged
merged 17 commits into from
Nov 11, 2023
Merged

Create the HTTP Response wrapper types #3148

merged 17 commits into from
Nov 11, 2023

Conversation

jdisanti
Copy link
Collaborator

@jdisanti jdisanti commented Nov 7, 2023

For the same reason that the request wrapper types were created in #3059, this PR establishes the response wrapper types and refactors existing code to use them.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@jdisanti jdisanti added the breaking-change This will require a breaking change label Nov 7, 2023
@jdisanti jdisanti changed the title Create the HttpResponse wrapper types Create the HTTP Response wrapper types Nov 7, 2023
@smithy-lang smithy-lang deleted a comment from github-actions bot Nov 8, 2023
@jdisanti jdisanti marked this pull request as ready for review November 8, 2023 01:40
@jdisanti jdisanti requested review from a team as code owners November 8, 2023 01:40
Copy link

github-actions bot commented Nov 8, 2023

@@ -277,15 +277,20 @@ class DefaultProtocolTestGenerator(
writeInline("let expected_output =")
instantiator.render(this, expectedShape, testCase.params)
write(";")
write("let mut http_response = #T::new()", RT.HttpResponseBuilder)
rustTemplate(
"let mut http_response = #{Response}::try_from(#{HttpResponseBuilder}::new()",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably just do try_into at the bottom..but I guess this is a wide-open block anyway

@@ -428,7 +439,14 @@ mod test {
.body(())
.unwrap();
let read = |name: &str, format: Format| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries either way but this is probably a smaller change with .try_into() on all the requests instead

Copy link

github-actions bot commented Nov 8, 2023

@smithy-lang smithy-lang deleted a comment from github-actions bot Nov 9, 2023
@smithy-lang smithy-lang deleted a comment from github-actions bot Nov 9, 2023
@smithy-lang smithy-lang deleted a comment from github-actions bot Nov 9, 2023
Copy link

github-actions bot commented Nov 9, 2023

Copy link
Contributor

@drganjoo drganjoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed the use of expect and unwraps in several places. While this might not pose a significant issue on the client side, it could be catastrophic if such code paths are executed on the server side. Are these instances confined to handling UTF-8 related headers only?

Additionally, what is our strategy for when http reaches version 1.x? Will we revert to using the types defined by http directly and convert our new Request/Response types into type aliases? Or do we plan on introducing into_http1x?

let content_type = content_type.to_str().map_err(MissingContentTypeReason::ToStrError)?;
content_type_header_classifier(content_type, expected_content_type)
} else {
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm curious as to why we don't generate an error when this function is called with expected_content_type set to Some(...) and yet the http::header::CONTENT_TYPE is missing from the headers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I don't know the answer to this.

.body(SdkBody::from(token))
.unwrap(),
)
.unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could there be a scenario where HttpResponse::try_from fails? If failure isn't possible, why did we implement try_from rather than from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try_from fails when there a header value is not ASCII or when a header value is invalid UTF-8. My intention was for all these try_from(...).unwrap() conversions to only take place in unit tests. In the actual code paths, we should be doing proper error handling for it.

.resolve("client::orchestrator::HttpRequest"),
"HttpResponse" to RuntimeType.smithyRuntimeApi(runtimeConfig)
"HttpResponse" to RuntimeType.smithyRuntimeApiClient(runtimeConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Considering that HttpResponse is used on the server side as well, wouldn't it be more appropriate to place it in smithyRuntimeApi instead of smithyRuntimeApiClient?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears like the types are also available without the client feature flag, under the http module. I wonder then why we don't use them directly instead of going through the client module?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this makes sense to me. We could probably even get rid of the HttpRequest/HttpResponse types from th eorchestrator module at this point. I think it's not so high priority though, and we can always deprecate them later.

@@ -53,7 +53,7 @@ impl ClassifyRetry for SampleRetryClassifier {
.and_then(|err| err.downcast_ref::<GetServerStatisticsError>())
{
if let Some(response) = ctx.response() {
if response.status() == StatusCode::SERVICE_UNAVAILABLE {
if response.status().as_u16() == StatusCode::SERVICE_UNAVAILABLE.as_u16() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It would be nice to have an impl From<http::StatusCode> for the response.status() that we return. This would enable direct conversion from http::StatusCode to the type of response.status().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Added in 08f60b0.

@@ -26,6 +27,10 @@ pub enum RequestRejection {
JsonDeserialize(#[from] aws_smithy_json::deserialize::error::DeserializeError),
#[error("request does not adhere to modeled constraints: {0}")]
ConstraintViolation(String),

/// Typically happens when the request has headers that are not valid UTF-8.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, since this issue occurred previously during deserialization, and now it will simply occur earlier in the lifecycle, what was the error we returned at that time?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We returned a HeaderParse rejection:

https://github.com/awslabs/smithy-rs/blob/d4208b2c8bba1c88c0fd3c7fc8f09f10c9346647/rust-runtime/aws-smithy-http-server/src/protocol/rest_json_1/rejection.rs#L129-L132

Which encompasses other failure scenarios, like expecting a header to be present and it isn't.


/// Typically happens when the request has headers that are not valid UTF-8.
#[error("failed to convert request: {0}")]
HttpConversion(#[from] HttpError),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Do we have a test that checks for this new variant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently no test.

///
/// Depending on the internal storage type, this operation may be free or it may have an internal
/// cost.
pub fn into_http02x(self) -> Result<http0::Response<B>, HttpError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Wondering if we should call this try_into_http02x?

Actually, do we ever return a HttpError?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For responses, the error isn't ever returned, but we keep the error as future proofing in the unlikely event that a http1 response can't easily be converted into a http02 response. I do like the idea of adding a try_ prefix to it. I will change that for the request and the response.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed in bc10bcc.

extensions: Extensions,
}

impl<B> Response<B> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm uncertain about our handling of streaming responses; however, I presume this change won't impact that, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make any changes to streaming.


/// An HTTP Response Type
#[derive(Debug)]
pub struct Response<B = SdkBody> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we implement Default for this, or do you believe there is no need?

.resolve("client::orchestrator::HttpRequest"),
"HttpResponse" to RuntimeType.smithyRuntimeApi(runtimeConfig)
"HttpResponse" to RuntimeType.smithyRuntimeApiClient(runtimeConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears like the types are also available without the client feature flag, under the http module. I wonder then why we don't use them directly instead of going through the client module?

@@ -26,6 +27,10 @@ pub enum RequestRejection {
JsonDeserialize(#[from] aws_smithy_json::deserialize::error::DeserializeError),
#[error("request does not adhere to modeled constraints: {0}")]
ConstraintViolation(String),

/// Typically happens when the request has headers that are not valid UTF-8.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We returned a HeaderParse rejection:

https://github.com/awslabs/smithy-rs/blob/d4208b2c8bba1c88c0fd3c7fc8f09f10c9346647/rust-runtime/aws-smithy-http-server/src/protocol/rest_json_1/rejection.rs#L129-L132

Which encompasses other failure scenarios, like expecting a header to be present and it isn't.

@@ -132,8 +130,8 @@ fn read_many<T>(
/// Read exactly one or none from a headers iterator
///
/// This function does not perform comma splitting like `read_many`
pub fn one_or_none<T: FromStr>(
mut values: ValueIter<'_, HeaderValue>,
pub fn one_or_none<'a, T: FromStr>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(can't leave a comment on line 144). Shouldn't we now no longer need the std::str::from_utf8 call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this file contains several .as_bytes calls, and code that works with &[u8] when I think it could directly work with &str all the way through?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, line 144 looks pretty bad. I'll fix that one at the very least. It'll take a good bit of effort to fix the rest of the file though, it looks like.

Touched up in 85af035.

@@ -95,21 +96,28 @@ class ResponseBindingGeneratorTest {
val testProject = TestWorkspace.testProject(symbolProvider)
testProject.renderOperation()
testProject.withModule(symbolProvider.moduleForShape(outputShape)) {
unitTest(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we forgoing the unitTest style of writing unit tests?

Copy link
Collaborator Author

@jdisanti jdisanti Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least in my opinion, the unitTest function doesn't really buy me anything when I have a predetermined test name.

"""
let #{RequestParts} { uri, headers, body, .. } = #{Request}::try_from(request)?.into_parts();
""",
*preludeScope,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need preludeScope here? We should add it to codegenScope instead.

Better yet, I want rustTemplate to have the prelude scope baked in, but that's a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I want this too!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also want this, but I'll keep it out of this PR. Looks like I can remove the prelude from that rustTemplate call.

@jdisanti
Copy link
Collaborator Author

Additionally, what is our strategy for when http reaches version 1.x? Will we revert to using the types defined by http directly and convert our new Request/Response types into type aliases? Or do we plan on introducing into_http1x?

These wrapper types are forever, unfortunately. We will just change the insides of them to use http 1 at that time. You can read more in the RFC: https://github.com/smithy-lang/smithy-rs/blob/main/design/src/rfcs/rfc0037_http_wrapper.md

@jdisanti jdisanti requested review from a team as code owners November 10, 2023 23:48
Copy link

Copy link

@jdisanti jdisanti merged commit 7eb008c into main Nov 11, 2023
40 of 41 checks passed
@jdisanti jdisanti deleted the jdisanti-http-response branch November 11, 2023 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change This will require a breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants