Skip to content

Commit a460bfc

Browse files
Merge 'smithy-rs-release-0.56.x' into main (#2967)
2 parents 784fbdb + 33a1c48 commit a460bfc

File tree

6 files changed

+251
-28
lines changed

6 files changed

+251
-28
lines changed

CHANGELOG.next.toml

+18
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,21 @@ message = "Allow `no_credentials` to be used with all S3 operations."
6666
references = ["smithy-rs#2955", "aws-sdk-rust#878"]
6767
meta = { "breaking" = false, "tada" = false, "bug" = true }
6868
author = "jdisanti"
69+
70+
[[aws-sdk-rust]]
71+
message = "`CustomizableOperation`, created as a result of calling the `.customize` method on a fluent builder, ceased to be `Send` and `Sync` in the previous releases. It is now `Send` and `Sync` again."
72+
references = ["smithy-rs#2944", "smithy-rs#2951"]
73+
meta = { "breaking" = false, "tada" = false, "bug" = true }
74+
author = "ysaito1001"
75+
76+
[[smithy-rs]]
77+
message = "`CustomizableOperation`, created as a result of calling the `.customize` method on a fluent builder, ceased to be `Send` and `Sync` in the previous releases. It is now `Send` and `Sync` again."
78+
references = ["smithy-rs#2944", "smithy-rs#2951"]
79+
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" }
80+
author = "ysaito1001"
81+
82+
[[smithy-rs]]
83+
message = "Generate a region setter when a model uses SigV4."
84+
references = ["smithy-rs#2960"]
85+
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" }
86+
author = "jdisanti"

aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/RegionDecorator.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package software.amazon.smithy.rustsdk
77

8+
import software.amazon.smithy.aws.traits.auth.SigV4Trait
9+
import software.amazon.smithy.model.knowledge.ServiceIndex
810
import software.amazon.smithy.model.node.Node
911
import software.amazon.smithy.rulesengine.language.syntax.parameters.Builtins
1012
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter
@@ -79,7 +81,11 @@ class RegionDecorator : ClientCodegenDecorator {
7981
override val name: String = "Region"
8082
override val order: Byte = 0
8183

82-
private fun usesRegion(codegenContext: ClientCodegenContext) = codegenContext.getBuiltIn(Builtins.REGION) != null
84+
// Services that have an endpoint ruleset that references the SDK::Region built in, or
85+
// that use SigV4, both need a configurable region.
86+
private fun usesRegion(codegenContext: ClientCodegenContext) =
87+
codegenContext.getBuiltIn(Builtins.REGION) != null || ServiceIndex.of(codegenContext.model)
88+
.getEffectiveAuthSchemes(codegenContext.serviceShape).containsKey(SigV4Trait.ID)
8389

8490
override fun configCustomizations(
8591
codegenContext: ClientCodegenContext,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.rustsdk
6+
7+
import org.junit.jupiter.api.Assertions.assertFalse
8+
import org.junit.jupiter.api.Assertions.assertTrue
9+
import org.junit.jupiter.api.Test
10+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
11+
import kotlin.io.path.readText
12+
13+
class RegionDecoratorTest {
14+
private val modelWithoutRegionParamOrSigV4AuthScheme = """
15+
namespace test
16+
17+
use aws.api#service
18+
use aws.protocols#awsJson1_0
19+
use smithy.rules#endpointRuleSet
20+
21+
@awsJson1_0
22+
@endpointRuleSet({
23+
"version": "1.0",
24+
"rules": [{ "type": "endpoint", "conditions": [], "endpoint": { "url": "https://example.com" } }],
25+
"parameters": {}
26+
})
27+
@service(sdkId: "dontcare")
28+
service TestService { version: "2023-01-01", operations: [SomeOperation] }
29+
structure SomeOutput { something: String }
30+
operation SomeOperation { output: SomeOutput }
31+
""".asSmithyModel()
32+
33+
private val modelWithRegionParam = """
34+
namespace test
35+
36+
use aws.api#service
37+
use aws.protocols#awsJson1_0
38+
use smithy.rules#endpointRuleSet
39+
40+
@awsJson1_0
41+
@endpointRuleSet({
42+
"version": "1.0",
43+
"rules": [{ "type": "endpoint", "conditions": [], "endpoint": { "url": "https://example.com" } }],
44+
"parameters": {
45+
"Region": { "required": false, "type": "String", "builtIn": "AWS::Region" },
46+
}
47+
})
48+
@service(sdkId: "dontcare")
49+
service TestService { version: "2023-01-01", operations: [SomeOperation] }
50+
structure SomeOutput { something: String }
51+
operation SomeOperation { output: SomeOutput }
52+
""".asSmithyModel()
53+
54+
private val modelWithSigV4AuthScheme = """
55+
namespace test
56+
57+
use aws.auth#sigv4
58+
use aws.api#service
59+
use aws.protocols#awsJson1_0
60+
use smithy.rules#endpointRuleSet
61+
62+
@auth([sigv4])
63+
@sigv4(name: "dontcare")
64+
@awsJson1_0
65+
@endpointRuleSet({
66+
"version": "1.0",
67+
"rules": [{ "type": "endpoint", "conditions": [], "endpoint": { "url": "https://example.com" } }],
68+
"parameters": {}
69+
})
70+
@service(sdkId: "dontcare")
71+
service TestService { version: "2023-01-01", operations: [SomeOperation] }
72+
structure SomeOutput { something: String }
73+
operation SomeOperation { output: SomeOutput }
74+
""".asSmithyModel()
75+
76+
@Test
77+
fun `models without region built-in params or SigV4 should not have configurable regions`() {
78+
val path = awsSdkIntegrationTest(modelWithoutRegionParamOrSigV4AuthScheme) { _, _ ->
79+
// it should generate and compile successfully
80+
}
81+
val configContents = path.resolve("src/config.rs").readText()
82+
assertFalse(configContents.contains("fn set_region("))
83+
}
84+
85+
@Test
86+
fun `models with region built-in params should have configurable regions`() {
87+
val path = awsSdkIntegrationTest(modelWithRegionParam) { _, _ ->
88+
// it should generate and compile successfully
89+
}
90+
val configContents = path.resolve("src/config.rs").readText()
91+
assertTrue(configContents.contains("fn set_region("))
92+
}
93+
94+
@Test
95+
fun `models with SigV4 should have configurable regions`() {
96+
val path = awsSdkIntegrationTest(modelWithSigV4AuthScheme) { _, _ ->
97+
// it should generate and compile successfully
98+
}
99+
val configContents = path.resolve("src/config.rs").readText()
100+
assertTrue(configContents.contains("fn set_region("))
101+
}
102+
}

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGenerator.kt

+29-15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class CustomizableOperationGenerator(
4444
.resolve("client::interceptors::MapRequestInterceptor"),
4545
"MutateRequestInterceptor" to RuntimeType.smithyRuntime(runtimeConfig)
4646
.resolve("client::interceptors::MutateRequestInterceptor"),
47+
"PhantomData" to RuntimeType.Phantom,
4748
"RuntimePlugin" to RuntimeType.runtimePlugin(runtimeConfig),
4849
"SharedRuntimePlugin" to RuntimeType.sharedRuntimePlugin(runtimeConfig),
4950
"SendResult" to ClientRustModule.Client.customize.toType()
@@ -61,14 +62,28 @@ class CustomizableOperationGenerator(
6162
rustTemplate(
6263
"""
6364
/// `CustomizableOperation` allows for configuring a single operation invocation before it is sent.
64-
pub struct CustomizableOperation<T, E> {
65-
pub(crate) customizable_send: #{Box}<dyn #{CustomizableSend}<T, E>>,
66-
pub(crate) config_override: #{Option}<crate::config::Builder>,
67-
pub(crate) interceptors: Vec<#{SharedInterceptor}>,
68-
pub(crate) runtime_plugins: Vec<#{SharedRuntimePlugin}>,
65+
pub struct CustomizableOperation<T, E, B> {
66+
customizable_send: B,
67+
config_override: #{Option}<crate::config::Builder>,
68+
interceptors: Vec<#{SharedInterceptor}>,
69+
runtime_plugins: Vec<#{SharedRuntimePlugin}>,
70+
_output: #{PhantomData}<T>,
71+
_error: #{PhantomData}<E>,
6972
}
7073
71-
impl<T, E> CustomizableOperation<T, E> {
74+
impl<T, E, B> CustomizableOperation<T, E, B> {
75+
/// Creates a new `CustomizableOperation` from `customizable_send`.
76+
pub(crate) fn new(customizable_send: B) -> Self {
77+
Self {
78+
customizable_send,
79+
config_override: #{None},
80+
interceptors: vec![],
81+
runtime_plugins: vec![],
82+
_output: #{PhantomData},
83+
_error: #{PhantomData}
84+
}
85+
}
86+
7287
/// Adds an [`Interceptor`](#{Interceptor}) that runs at specific stages of the request execution pipeline.
7388
///
7489
/// Note that interceptors can also be added to `CustomizableOperation` by `config_override`,
@@ -142,6 +157,7 @@ class CustomizableOperationGenerator(
142157
) -> #{SendResult}<T, E>
143158
where
144159
E: std::error::Error + #{Send} + #{Sync} + 'static,
160+
B: #{CustomizableSend}<T, E>,
145161
{
146162
let mut config_override = self.config_override.unwrap_or_default();
147163
self.interceptors.into_iter().for_each(|interceptor| {
@@ -151,7 +167,7 @@ class CustomizableOperationGenerator(
151167
config_override.push_runtime_plugin(plugin);
152168
});
153169
154-
(self.customizable_send)(config_override).await
170+
self.customizable_send.send(config_override).await
155171
}
156172
157173
#{additional_methods}
@@ -182,14 +198,12 @@ class CustomizableOperationGenerator(
182198
>,
183199
>;
184200
185-
pub trait CustomizableSend<T, E>:
186-
#{FnOnce}(crate::config::Builder) -> BoxFuture<SendResult<T, E>>
187-
{}
188-
189-
impl<F, T, E> CustomizableSend<T, E> for F
190-
where
191-
F: #{FnOnce}(crate::config::Builder) -> BoxFuture<SendResult<T, E>>
192-
{}
201+
pub trait CustomizableSend<T, E>: #{Send} + #{Sync} {
202+
// Takes an owned `self` as the implementation will internally call methods that take `self`.
203+
// If it took `&self`, that would make this trait object safe, but some implementing types do not
204+
// derive `Clone`, unable to yield `self` from `&self`.
205+
fn send(self, config_override: crate::config::Builder) -> BoxFuture<SendResult<T, E>>;
206+
}
193207
""",
194208
*preludeScope,
195209
"HttpResponse" to RuntimeType.smithyRuntimeApi(runtimeConfig)

codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt

+29-12
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,33 @@ class FluentClientGenerator(
299299
rustTemplate("config_override: #{Option}<crate::config::Builder>,", *preludeScope)
300300
}
301301

302+
rustTemplate(
303+
"""
304+
impl
305+
crate::client::customize::internal::CustomizableSend<
306+
#{OperationOutput},
307+
#{OperationError},
308+
> for $builderName
309+
{
310+
fn send(
311+
self,
312+
config_override: crate::config::Builder,
313+
) -> crate::client::customize::internal::BoxFuture<
314+
crate::client::customize::internal::SendResult<
315+
#{OperationOutput},
316+
#{OperationError},
317+
>,
318+
> {
319+
#{Box}::pin(async move { self.config_override(config_override).send().await })
320+
}
321+
}
322+
""",
323+
*preludeScope,
324+
"OperationError" to errorType,
325+
"OperationOutput" to outputType,
326+
"SdkError" to RuntimeType.sdkError(runtimeConfig),
327+
)
328+
302329
rustBlockTemplate(
303330
"impl $builderName",
304331
"client" to RuntimeType.smithyClient(runtimeConfig),
@@ -369,22 +396,12 @@ class FluentClientGenerator(
369396
#{CustomizableOperation}<
370397
#{OperationOutput},
371398
#{OperationError},
399+
Self,
372400
>,
373401
#{SdkError}<#{OperationError}>,
374402
>
375403
{
376-
#{Ok}(#{CustomizableOperation} {
377-
customizable_send: #{Box}::new(move |config_override| {
378-
#{Box}::pin(async {
379-
self.config_override(config_override)
380-
.send()
381-
.await
382-
})
383-
}),
384-
config_override: None,
385-
interceptors: vec![],
386-
runtime_plugins: vec![],
387-
})
404+
#{Ok}(#{CustomizableOperation}::new(self))
388405
}
389406
""",
390407
*orchestratorScope,
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+
package software.amazon.smithy.rust.codegen.client.smithy.generators.client
7+
8+
import org.junit.jupiter.api.Test
9+
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
10+
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
11+
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
12+
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
13+
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
14+
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
15+
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
16+
import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
17+
18+
class CustomizableOperationGeneratorTest {
19+
val model = """
20+
namespace com.example
21+
use aws.protocols#awsJson1_0
22+
23+
@awsJson1_0
24+
service HelloService {
25+
operations: [SayHello],
26+
version: "1"
27+
}
28+
29+
@optionalAuth
30+
operation SayHello { input: TestInput }
31+
structure TestInput {
32+
foo: String,
33+
}
34+
""".asSmithyModel()
35+
36+
@Test
37+
fun `CustomizableOperation is send and sync`() {
38+
val test: (ClientCodegenContext, RustCrate) -> Unit = { codegenContext, rustCrate ->
39+
rustCrate.integrationTest("customizable_operation_is_send_and_sync") {
40+
val moduleName = codegenContext.moduleUseName()
41+
rustTemplate(
42+
"""
43+
fn check_send_and_sync<T: Send + Sync>(_: T) {}
44+
45+
##[test]
46+
fn test() {
47+
let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new());
48+
let config = $moduleName::Config::builder()
49+
.http_connector(connector.clone())
50+
.endpoint_resolver("http://localhost:1234")
51+
.build();
52+
let client = $moduleName::Client::from_conf(config);
53+
check_send_and_sync(client.say_hello().customize());
54+
}
55+
""",
56+
"TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig)
57+
.toDevDependency()
58+
.withFeature("test-util").toType()
59+
.resolve("test_connection::TestConnection"),
60+
"SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig),
61+
)
62+
}
63+
}
64+
clientIntegrationTest(model, test = test)
65+
}
66+
}

0 commit comments

Comments
 (0)