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

Unsupported content type #1723

Merged
merged 2 commits into from
Sep 13, 2022
Merged

Unsupported content type #1723

merged 2 commits into from
Sep 13, 2022

Conversation

82marbag
Copy link
Contributor

@82marbag 82marbag commented Sep 9, 2022

Motivation and Context

Add validation for the Content-Type header and pass (remove from the failing list) the relevant protocol tests.

Closes: #1663

Testing

./gradlew :codegen-server-test:test && ./gradlew :codegen-test:test

Checklist

  • I have updated CHANGELOG.next.toml if I made changes to the smithy-rs codegen or runtime crates
  • 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.

@82marbag 82marbag requested a review from a team as a code owner September 9, 2022 08:37
@github-actions
Copy link

github-actions bot commented Sep 9, 2022

A new generated diff is ready to view.

A new doc preview is ready to view.

rust-runtime/aws-smithy-http-server/src/protocols.rs Outdated Show resolved Hide resolved
@@ -667,16 +695,13 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
Attribute.AllowUnusedMut.render(this)
rust("let mut input = #T::default();", inputShape.builderSymbol(symbolProvider))
val parser = structuredDataParser.serverInputParser(operationShape)
val noInputs = model.expectShape(operationShape.inputShape).expectTrait<SyntheticInputTrait>().originalId == null
Copy link
Contributor

Choose a reason for hiding this comment

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

Move this condition closer to where it's used.

I don't think this condition is quite right though. This condition is true when the user did not model any operation input structure. However, I think we want to assert that the body is empty when all of the operation input is bound to other parts of the HTTP message too. For example, with an operation input modeled like this:

structure MyOperationInput {
    @httpHeader("header")
    header: String
}

We also want to assert that the HTTP body is empty.


Add a protocol test to ensure we don't regress on this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this condition is wrong in that SyntheticInputTrait is only added when no operation input/output is modeled for an operation, but it is not if the operation input/output is modeled but is empty. See the documentation in OperationNormalizer.kt.

For example, consider the Healthcheck operation of the simple model:

@readonly
@http(uri: "/healthcheck", method: "GET")
@documentation("Read-only healthcheck operation")
operation Healthcheck {
    input: HealthcheckInputRequest,
    output: HealthcheckOutputResponse
}

@documentation("Service healthcheck output structure")
structure HealthcheckInputRequest {

}

@documentation("Service healthcheck input structure")
structure HealthcheckOutputResponse {

}

This model will make noInputs be false. Only with a:

operation Healthcheck {
}

will noInputs be true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is on purpose because of this test. It's the first example you give, but has a non-empty body and a content-type

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see. Interesting. What's the protocol test that asserts that body must be empty when there is no modeled input? I can't seem to find it right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one (which corresponds to the second example you give)

let found_mime = req
.headers()
.ok_or(MissingContentTypeReason::HeadersTakenByAnotherExtractor)?
if req.headers().is_none() || (protocol != Protocol::RestJson1 && protocol != Protocol::RestXml) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we returning Ok(()) for protocols other than these two?

Is the plan to handle e.g. application/x-amz-json-1.0 for the AwsJson 1.0 protocol in a separate PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's just that a client should always send an empty JSON object payload in AwsJson 1.0, so with {} in the body this would fail (this only checks whether the body is empty)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. Good callout. I think it's best then that we don't call this function in the codegen when we're generating AwsJson 1.x. Consider that we want to support custom protocols loaded from decorators, and that we would be performing this check for them inadvertently.

Since this check is now a protocol-specific concern, whether we perform or not this check should be up to the particular protocol implementation. So I would suggest that we add a method to the Protocol.kt interface that determines whether we should perform this check, and only call the function in the runtime when the protocol implementation of the interface returns true.

@@ -667,16 +695,13 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
Attribute.AllowUnusedMut.render(this)
rust("let mut input = #T::default();", inputShape.builderSymbol(symbolProvider))
val parser = structuredDataParser.serverInputParser(operationShape)
val noInputs = model.expectShape(operationShape.inputShape).expectTrait<SyntheticInputTrait>().originalId == null
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this condition is wrong in that SyntheticInputTrait is only added when no operation input/output is modeled for an operation, but it is not if the operation input/output is modeled but is empty. See the documentation in OperationNormalizer.kt.

For example, consider the Healthcheck operation of the simple model:

@readonly
@http(uri: "/healthcheck", method: "GET")
@documentation("Read-only healthcheck operation")
operation Healthcheck {
    input: HealthcheckInputRequest,
    output: HealthcheckOutputResponse
}

@documentation("Service healthcheck output structure")
structure HealthcheckInputRequest {

}

@documentation("Service healthcheck input structure")
structure HealthcheckOutputResponse {

}

This model will make noInputs be false. Only with a:

operation Healthcheck {
}

will noInputs be true.

let found_mime = req
.headers()
.ok_or(MissingContentTypeReason::HeadersTakenByAnotherExtractor)?
if req.headers().is_none() || (protocol != Protocol::RestJson1 && protocol != Protocol::RestXml) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. Good callout. I think it's best then that we don't call this function in the codegen when we're generating AwsJson 1.x. Consider that we want to support custom protocols loaded from decorators, and that we would be performing this check for them inadvertently.

Since this check is now a protocol-specific concern, whether we perform or not this check should be up to the particular protocol implementation. So I would suggest that we add a method to the Protocol.kt interface that determines whether we should perform this check, and only call the function in the runtime when the protocol implementation of the interface returns true.

rust-runtime/aws-smithy-http-server/src/protocols.rs Outdated Show resolved Hide resolved
rust-runtime/aws-smithy-http-server/src/protocols.rs Outdated Show resolved Hide resolved
rust-runtime/aws-smithy-http-server/src/protocols.rs Outdated Show resolved Hide resolved
* when there is no modeled body input, content type must not be set and the body must be empty
* Returns a writable to perform this check
*/
fun serverContentTypeCheckNoModeledInput(): Boolean = false
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't want to block approval on this, but now that the prerequisite work has been done in #1698, it would be better to have this in the ServerProtocol implementations instead.

Copy link
Contributor

@hlbarber hlbarber Sep 13, 2022

Choose a reason for hiding this comment

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

After this is merged I'll move this method in #1731

Add validation for the Content-Type header and pass (remove from the failing
list) the relevant protocol tests

Signed-off-by: Daniele Ahmed <[email protected]>
@82marbag 82marbag force-pushed the unsupported-content-type branch from c0dbea0 to fc71c5b Compare September 13, 2022 17:41
@82marbag 82marbag removed the request for review from crisidev September 13, 2022 17:41
@82marbag 82marbag enabled auto-merge (squash) September 13, 2022 17:42
@github-actions
Copy link

A new generated diff is ready to view.

A new doc preview is ready to view.

@82marbag 82marbag merged commit f324240 into main Sep 13, 2022
@82marbag 82marbag deleted the unsupported-content-type branch September 13, 2022 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support UnsupportedMediaType exception
4 participants