Skip to content

Commit 1de3802

Browse files
authored
Fix @httpChecksumRequired and idempotency tokens in the orchestrator (#2817)
This PR fixes the Smithy `@httpChecksumRequired` trait and idempotency token auto-fill in the orchestrator implementation. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent ee5aadc commit 1de3802

File tree

8 files changed

+243
-18
lines changed

8 files changed

+243
-18
lines changed

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpChecksumRequiredGenerator.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations
88
import software.amazon.smithy.codegen.core.CodegenException
99
import software.amazon.smithy.model.shapes.OperationShape
1010
import software.amazon.smithy.model.traits.HttpChecksumRequiredTrait
11+
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
1112
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
1213
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
14+
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
15+
import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency
16+
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
1317
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
1418
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
19+
import software.amazon.smithy.rust.codegen.core.rustlang.toType
1520
import software.amazon.smithy.rust.codegen.core.rustlang.writable
1621
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
1722
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
@@ -32,6 +37,22 @@ class HttpChecksumRequiredGenerator(
3237
throw CodegenException("HttpChecksum required cannot be applied to a streaming shape")
3338
}
3439
return when (section) {
40+
is OperationSection.AdditionalRuntimePlugins -> writable {
41+
section.addOperationRuntimePlugin(this) {
42+
rustTemplate(
43+
"#{HttpChecksumRequiredRuntimePlugin}",
44+
"HttpChecksumRequiredRuntimePlugin" to
45+
InlineDependency.forRustFile(
46+
RustModule.pubCrate("client_http_checksum_required", parent = ClientRustModule.root),
47+
"/inlineable/src/client_http_checksum_required.rs",
48+
CargoDependency.smithyRuntimeApi(codegenContext.runtimeConfig),
49+
CargoDependency.smithyTypes(codegenContext.runtimeConfig),
50+
CargoDependency.Http,
51+
CargoDependency.Md5,
52+
).toType().resolve("HttpChecksumRequiredRuntimePlugin"),
53+
)
54+
}
55+
}
3556
is OperationSection.MutateRequest -> writable {
3657
rustTemplate(
3758
"""

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/IdempotencyTokenGenerator.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,60 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations
77

88
import software.amazon.smithy.model.shapes.OperationShape
99
import software.amazon.smithy.model.traits.IdempotencyTokenTrait
10+
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
1011
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationCustomization
1112
import software.amazon.smithy.rust.codegen.client.smithy.generators.OperationSection
13+
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
14+
import software.amazon.smithy.rust.codegen.core.rustlang.InlineDependency
15+
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
1216
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
1317
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
18+
import software.amazon.smithy.rust.codegen.core.rustlang.toType
1419
import software.amazon.smithy.rust.codegen.core.rustlang.writable
1520
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
1621
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope
1722
import software.amazon.smithy.rust.codegen.core.util.findMemberWithTrait
1823
import software.amazon.smithy.rust.codegen.core.util.inputShape
1924

20-
class IdempotencyTokenGenerator(codegenContext: CodegenContext, operationShape: OperationShape) :
21-
OperationCustomization() {
25+
class IdempotencyTokenGenerator(
26+
codegenContext: CodegenContext,
27+
operationShape: OperationShape,
28+
) : OperationCustomization() {
2229
private val model = codegenContext.model
30+
private val runtimeConfig = codegenContext.runtimeConfig
2331
private val symbolProvider = codegenContext.symbolProvider
24-
private val idempotencyTokenMember = operationShape.inputShape(model).findMemberWithTrait<IdempotencyTokenTrait>(model)
32+
private val inputShape = operationShape.inputShape(model)
33+
private val idempotencyTokenMember = inputShape.findMemberWithTrait<IdempotencyTokenTrait>(model)
34+
2535
override fun section(section: OperationSection): Writable {
2636
if (idempotencyTokenMember == null) {
2737
return emptySection
2838
}
2939
val memberName = symbolProvider.toMemberName(idempotencyTokenMember)
3040
return when (section) {
41+
is OperationSection.AdditionalRuntimePlugins -> writable {
42+
section.addOperationRuntimePlugin(this) {
43+
rustTemplate(
44+
"""
45+
#{IdempotencyTokenRuntimePlugin}::new(|token_provider, input| {
46+
let input: &mut #{Input} = input.downcast_mut().expect("correct type");
47+
if input.$memberName.is_none() {
48+
input.$memberName = #{Some}(token_provider.make_idempotency_token());
49+
}
50+
})
51+
""",
52+
*preludeScope,
53+
"Input" to symbolProvider.toSymbol(inputShape),
54+
"IdempotencyTokenRuntimePlugin" to
55+
InlineDependency.forRustFile(
56+
RustModule.pubCrate("client_idempotency_token", parent = ClientRustModule.root),
57+
"/inlineable/src/client_idempotency_token.rs",
58+
CargoDependency.smithyRuntimeApi(runtimeConfig),
59+
CargoDependency.smithyTypes(runtimeConfig),
60+
).toType().resolve("IdempotencyTokenRuntimePlugin"),
61+
)
62+
}
63+
}
3164
is OperationSection.MutateInput -> writable {
3265
rustTemplate(
3366
"""

rust-runtime/inlineable/Cargo.toml

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,24 @@ default = ["gated-tests"]
1818

1919

2020
[dependencies]
21-
"bytes" = "1"
22-
"http" = "0.2.1"
23-
"aws-smithy-types" = { path = "../aws-smithy-types" }
24-
"aws-smithy-json" = { path = "../aws-smithy-json" }
25-
"aws-smithy-xml" = { path = "../aws-smithy-xml" }
26-
"aws-smithy-http-server" = { path = "../aws-smithy-http-server" }
27-
"fastrand" = "1"
28-
"futures-util" = "0.3"
29-
"pin-project-lite" = "0.2"
30-
"tower" = { version = "0.4.11", default-features = false }
31-
"async-trait" = "0.1"
32-
percent-encoding = "2.2.0"
21+
async-trait = "0.1"
22+
aws-smithy-http = { path = "../aws-smithy-http" }
23+
aws-smithy-http-server = { path = "../aws-smithy-http-server" }
24+
aws-smithy-json = { path = "../aws-smithy-json" }
25+
aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" }
26+
aws-smithy-types = { path = "../aws-smithy-types" }
27+
aws-smithy-xml = { path = "../aws-smithy-xml" }
28+
bytes = "1"
29+
fastrand = "1"
30+
futures-util = "0.3"
31+
http = "0.2.1"
32+
md-5 = "0.10.0"
3333
once_cell = "1.16.0"
34+
percent-encoding = "2.2.0"
35+
pin-project-lite = "0.2"
3436
regex = "1.5.5"
35-
"url" = "2.2.2"
36-
aws-smithy-http = { path = "../aws-smithy-http" }
37+
tower = { version = "0.4.11", default-features = false }
38+
url = "2.2.2"
3739

3840
[dev-dependencies]
3941
proptest = "1"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use aws_smithy_runtime_api::box_error::BoxError;
7+
use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextMut;
8+
use aws_smithy_runtime_api::client::interceptors::{
9+
Interceptor, InterceptorRegistrar, SharedInterceptor,
10+
};
11+
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
12+
use aws_smithy_types::base64;
13+
use aws_smithy_types::config_bag::ConfigBag;
14+
use http::header::HeaderName;
15+
16+
#[derive(Debug)]
17+
pub(crate) struct HttpChecksumRequiredRuntimePlugin;
18+
19+
impl RuntimePlugin for HttpChecksumRequiredRuntimePlugin {
20+
fn interceptors(&self, interceptors: &mut InterceptorRegistrar) {
21+
interceptors.register(SharedInterceptor::new(HttpChecksumRequiredInterceptor));
22+
}
23+
}
24+
25+
#[derive(Debug)]
26+
struct HttpChecksumRequiredInterceptor;
27+
28+
impl Interceptor for HttpChecksumRequiredInterceptor {
29+
fn modify_before_signing(
30+
&self,
31+
context: &mut BeforeTransmitInterceptorContextMut<'_>,
32+
_cfg: &mut ConfigBag,
33+
) -> Result<(), BoxError> {
34+
let request = context.request_mut();
35+
let body_bytes = request
36+
.body()
37+
.bytes()
38+
.expect("checksum can only be computed for non-streaming operations");
39+
let checksum = <md5::Md5 as md5::Digest>::digest(body_bytes);
40+
request.headers_mut().insert(
41+
HeaderName::from_static("content-md5"),
42+
base64::encode(&checksum[..])
43+
.parse()
44+
.expect("checksum is a valid header value"),
45+
);
46+
Ok(())
47+
}
48+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use crate::idempotency_token::IdempotencyTokenProvider;
7+
use aws_smithy_runtime_api::box_error::BoxError;
8+
use aws_smithy_runtime_api::client::interceptors::context::{
9+
BeforeSerializationInterceptorContextMut, Input,
10+
};
11+
use aws_smithy_runtime_api::client::interceptors::{
12+
Interceptor, InterceptorRegistrar, SharedInterceptor,
13+
};
14+
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
15+
use aws_smithy_types::config_bag::ConfigBag;
16+
use std::fmt;
17+
18+
#[derive(Debug)]
19+
pub(crate) struct IdempotencyTokenRuntimePlugin {
20+
interceptor: SharedInterceptor,
21+
}
22+
23+
impl IdempotencyTokenRuntimePlugin {
24+
pub(crate) fn new<S>(set_token: S) -> Self
25+
where
26+
S: Fn(IdempotencyTokenProvider, &mut Input) + Send + Sync + 'static,
27+
{
28+
Self {
29+
interceptor: SharedInterceptor::new(IdempotencyTokenInterceptor { set_token }),
30+
}
31+
}
32+
}
33+
34+
impl RuntimePlugin for IdempotencyTokenRuntimePlugin {
35+
fn interceptors(&self, interceptors: &mut InterceptorRegistrar) {
36+
interceptors.register(self.interceptor.clone());
37+
}
38+
}
39+
40+
struct IdempotencyTokenInterceptor<S> {
41+
set_token: S,
42+
}
43+
44+
impl<S> fmt::Debug for IdempotencyTokenInterceptor<S> {
45+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46+
f.debug_struct("IdempotencyTokenInterceptor").finish()
47+
}
48+
}
49+
50+
impl<S> Interceptor for IdempotencyTokenInterceptor<S>
51+
where
52+
S: Fn(IdempotencyTokenProvider, &mut Input) + Send + Sync,
53+
{
54+
fn modify_before_serialization(
55+
&self,
56+
context: &mut BeforeSerializationInterceptorContextMut<'_>,
57+
cfg: &mut ConfigBag,
58+
) -> Result<(), BoxError> {
59+
let token_provider = cfg
60+
.load::<IdempotencyTokenProvider>()
61+
.expect("the idempotency provider must be set")
62+
.clone();
63+
(self.set_token)(token_provider, context.input_mut());
64+
Ok(())
65+
}
66+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use aws_smithy_runtime_api::box_error::BoxError;
7+
use aws_smithy_runtime_api::client::interceptors::context::BeforeTransmitInterceptorContextMut;
8+
use aws_smithy_runtime_api::client::interceptors::{
9+
Interceptor, InterceptorRegistrar, SharedInterceptor,
10+
};
11+
use aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin;
12+
use aws_smithy_types::base64;
13+
use aws_smithy_types::config_bag::ConfigBag;
14+
use http::header::HeaderName;
15+
16+
#[derive(Debug)]
17+
pub(crate) struct HttpChecksumRequiredRuntimePlugin;
18+
19+
impl RuntimePlugin for HttpChecksumRequiredRuntimePlugin {
20+
fn interceptors(&self, interceptors: &mut InterceptorRegistrar) {
21+
interceptors.register(SharedInterceptor::new(HttpChecksumRequiredInterceptor));
22+
}
23+
}
24+
25+
#[derive(Debug)]
26+
struct HttpChecksumRequiredInterceptor;
27+
28+
impl Interceptor for HttpChecksumRequiredInterceptor {
29+
fn modify_before_signing(
30+
&self,
31+
context: &mut BeforeTransmitInterceptorContextMut<'_>,
32+
_cfg: &mut ConfigBag,
33+
) -> Result<(), BoxError> {
34+
let request = context.request_mut();
35+
let body_bytes = request
36+
.body()
37+
.bytes()
38+
.expect("checksum can only be computed for non-streaming operations");
39+
let checksum = <md5::Md5 as md5::Digest>::digest(body_bytes);
40+
request.headers_mut().insert(
41+
HeaderName::from_static("content-md5"),
42+
base64::encode(&checksum[..])
43+
.parse()
44+
.expect("checksum is a valid header value"),
45+
);
46+
Ok(())
47+
}
48+
}

rust-runtime/inlineable/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
#[allow(dead_code)]
77
mod aws_query_compatible_errors;
88
#[allow(unused)]
9+
mod client_http_checksum_required;
10+
#[allow(dead_code)]
11+
mod client_idempotency_token;
12+
#[allow(unused)]
913
mod constrained;
1014
#[allow(dead_code)]
1115
mod ec2_query_errors;

tools/ci-scripts/check-client-codegen-integration-tests

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66

77
set -eux
88
cd smithy-rs
9-
./gradlew codegen-client-test:test
9+
10+
# TODO(enableNewSmithyRuntimeCleanup): Only run the orchestrator version of this
11+
./gradlew codegen-client-test:test -Psmithy.runtime.mode=middleware
12+
./gradlew codegen-client-test:test -Psmithy.runtime.mode=orchestrator

0 commit comments

Comments
 (0)