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

Send x-amzn-query-mode to inform a service with the awsQueryCompatible trait that SDK is operating in that mode #3883

Merged
merged 4 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changelog/1729271936.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
applies_to:
- aws-sdk-rust
authors:
- ysaito1001
references:
- smithy-rs#3883
breaking: false
new_feature: false
bug_fix: false
---
Client SDKs built with the `awsQueryCompatible` trait now include the `x-amzn-query-mode` header. This header signals the service that the clients are operating in compatible mode.
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,29 @@
package software.amazon.smithy.rust.codegen.client.smithy.protocols

import org.junit.jupiter.api.Test
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
import software.amazon.smithy.rust.codegen.core.util.lookup
import software.amazon.smithy.rust.codegen.core.testutil.testModule
import software.amazon.smithy.rust.codegen.core.testutil.tokioTest
import software.amazon.smithy.rust.codegen.core.util.letIf

class AwsQueryCompatibleTest {
@Test
fun `aws-query-compatible json with aws query error should allow for retrieving error code and type from custom header`() {
val model =
"""
companion object {
const val prologue = """
namespace test
use aws.protocols#awsJson1_0
use aws.protocols#awsQueryCompatible
use aws.protocols#awsQueryError
"""

@awsQueryCompatible
@awsJson1_0
const val awsjson10Trait = "@awsJson1_0"
const val awsQueryCompatibleTrait = "@awsQueryCompatible"

fun testService(withAwsQueryError: Boolean = true) =
"""
service TestService {
version: "2023-02-20",
operations: [SomeOperation]
Expand All @@ -40,36 +44,45 @@ class AwsQueryCompatibleTest {
a: String,
b: Integer
}

@awsQueryError(
code: "InvalidThing",
httpResponseCode: 400,
)
@error("client")
structure InvalidThingException {
message: String
""".letIf(withAwsQueryError) {
it +
"""
@awsQueryError(
code: "InvalidThing",
httpResponseCode: 400,
)
"""
}.let {
it +
"""
@error("client")
structure InvalidThingException {
message: String
}
"""
}
""".asSmithyModel()
}

@Test
fun `aws-query-compatible json with aws query error should allow for retrieving error code and type from custom header`() {
val model =
(prologue + awsQueryCompatibleTrait + awsjson10Trait + testService()).asSmithyModel(
smithyVersion = "2",
)
clientIntegrationTest(model) { context, rustCrate ->
val operation: OperationShape = context.model.lookup("test#SomeOperation")
rustCrate.withModule(context.symbolProvider.moduleForShape(operation)) {
rustTemplate(
"""
##[cfg(test)]
##[#{tokio}::test]
async fn should_parse_code_and_type_fields() {
use aws_smithy_types::body::SdkBody;

let response = |_: http::Request<SdkBody>| {
rustCrate.testModule {
tokioTest("should_parse_code_and_type_fields") {
rustTemplate(
"""
let response = |_: http::Request<#{SdkBody}>| {
http::Response::builder()
.header(
"x-amzn-query-error",
http::HeaderValue::from_static("AWS.SimpleQueueService.NonExistentQueue;Sender"),
)
.status(400)
.body(
SdkBody::from(
#{SdkBody}::from(
r##"{
"__type": "com.amazonaws.sqs##QueueDoesNotExist",
"message": "Some user-visible message"
Expand All @@ -86,68 +99,38 @@ class AwsQueryCompatibleTest {
);
let error = dbg!(client.some_operation().send().await).err().unwrap().into_service_error();
assert_eq!(
Some("AWS.SimpleQueueService.NonExistentQueue"),
#{Some}("AWS.SimpleQueueService.NonExistentQueue"),
error.meta().code(),
);
assert_eq!(Some("Sender"), error.meta().extra("type"));
}
""",
"infallible_client_fn" to
CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig)
.toType().resolve("client::http::test_util::infallible_client_fn"),
"tokio" to CargoDependency.Tokio.toType(),
)
assert_eq!(#{Some}("Sender"), error.meta().extra("type"));
""",
*RuntimeType.preludeScope,
"SdkBody" to RuntimeType.sdkBody(context.runtimeConfig),
"infallible_client_fn" to
CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig)
.toType().resolve("client::http::test_util::infallible_client_fn"),
)
}
}
}
}

@Test
fun `aws-query-compatible json without aws query error should allow for retrieving error code from payload`() {
val model =
"""
namespace test
use aws.protocols#awsJson1_0
use aws.protocols#awsQueryCompatible

@awsQueryCompatible
@awsJson1_0
service TestService {
version: "2023-02-20",
operations: [SomeOperation]
}

operation SomeOperation {
input: SomeOperationInputOutput,
output: SomeOperationInputOutput,
errors: [InvalidThingException],
}

structure SomeOperationInputOutput {
a: String,
b: Integer
}

@error("client")
structure InvalidThingException {
message: String
}
""".asSmithyModel()

(prologue + awsQueryCompatibleTrait + awsjson10Trait + testService(withAwsQueryError = false)).asSmithyModel(
smithyVersion = "2",
)
clientIntegrationTest(model) { context, rustCrate ->
val operation: OperationShape = context.model.lookup("test#SomeOperation")
rustCrate.withModule(context.symbolProvider.moduleForShape(operation)) {
rustTemplate(
"""
##[cfg(test)]
##[#{tokio}::test]
async fn should_parse_code_from_payload() {
use aws_smithy_types::body::SdkBody;

let response = |_: http::Request<SdkBody>| {
rustCrate.testModule {
tokioTest("should_parse_code_from_payload") {
rustTemplate(
"""
let response = |_: http::Request<#{SdkBody}>| {
http::Response::builder()
.status(400)
.body(
SdkBody::from(
#{SdkBody}::from(
r##"{
"__type": "com.amazonaws.sqs##QueueDoesNotExist",
"message": "Some user-visible message"
Expand All @@ -163,15 +146,45 @@ class AwsQueryCompatibleTest {
.build()
);
let error = dbg!(client.some_operation().send().await).err().unwrap().into_service_error();
assert_eq!(Some("QueueDoesNotExist"), error.meta().code());
assert_eq!(None, error.meta().extra("type"));
}
""",
"infallible_client_fn" to
CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig)
.toType().resolve("client::http::test_util::infallible_client_fn"),
"tokio" to CargoDependency.Tokio.toType(),
)
assert_eq!(#{Some}("QueueDoesNotExist"), error.meta().code());
assert_eq!(#{None}, error.meta().extra("type"));
""",
*RuntimeType.preludeScope,
"SdkBody" to RuntimeType.sdkBody(context.runtimeConfig),
"infallible_client_fn" to
CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig)
.toType().resolve("client::http::test_util::infallible_client_fn"),
)
}
}
}
}

@Test
fun `request header should include x-amzn-query-mode when the service has the awsQueryCompatible trait`() {
val model =
(prologue + awsQueryCompatibleTrait + awsjson10Trait + testService()).asSmithyModel(
smithyVersion = "2",
)
clientIntegrationTest(model) { context, rustCrate ->
rustCrate.testModule {
tokioTest("test_request_header_should_include_x_amzn_query_mode") {
rustTemplate(
"""
let (http_client, rx) = #{capture_request}(#{None});
let config = crate::Config::builder()
.http_client(http_client)
.endpoint_url("http://localhost:1234/SomeOperation")
.build();
let client = crate::Client::from_conf(config);
let _ = dbg!(client.some_operation().send().await);
let request = rx.expect_request();
assert_eq!("true", request.headers().get("x-amzn-query-mode").unwrap());
""",
*RuntimeType.preludeScope,
"capture_request" to RuntimeType.captureRequest(context.runtimeConfig),
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ service QueryCompatService {
params: {
message: "hello!"
},
headers: { "x-amz-target": "QueryCompatService.Operation"}

headers: {
"x-amz-target": "QueryCompatService.Operation",
"x-amzn-query-mode": "true",
}
}
])
operation Operation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,8 @@ class AwsQueryCompatible(
awsJson.parseEventStreamErrorMetadata(operationShape)

override fun additionalRequestHeaders(operationShape: OperationShape): List<Pair<String, String>> =
listOf("x-amz-target" to "${codegenContext.serviceShape.id.name}.${operationShape.id.name}")
listOf(
"x-amz-target" to "${codegenContext.serviceShape.id.name}.${operationShape.id.name}",
"x-amzn-query-mode" to "true",
)
}