From 0a2156e80ae38090e4bc400751762dce3b150c42 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 30 Aug 2023 11:27:21 -0400 Subject: [PATCH 1/5] Make required context parameters required --- .../s3/tests/bucket-required.rs | 28 +++++++++++++++ .../EndpointParamsInterceptorGenerator.kt | 9 +++-- .../smithy/endpoint/EndpointsDecoratorTest.kt | 5 +++ .../smithy/generators/BuilderGenerator.kt | 34 ++++++++++++++++++- 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 aws/sdk/integration-tests/s3/tests/bucket-required.rs diff --git a/aws/sdk/integration-tests/s3/tests/bucket-required.rs b/aws/sdk/integration-tests/s3/tests/bucket-required.rs new file mode 100644 index 0000000000..7ef7731066 --- /dev/null +++ b/aws/sdk/integration-tests/s3/tests/bucket-required.rs @@ -0,0 +1,28 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_config::SdkConfig; +use aws_credential_types::provider::SharedCredentialsProvider; +use aws_sdk_s3::config::{Credentials, Region}; +use aws_sdk_s3::Client; +use aws_smithy_client::test_connection::capture_request; + +#[tokio::test] +async fn dont_dispatch_when_bucket_is_unset() { + let (conn, rcvr) = capture_request(None); + let sdk_config = SdkConfig::builder() + .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) + .region(Region::new("us-east-1")) + .http_connector(conn.clone()) + .build(); + let client = Client::new(&sdk_config); + let err = client + .list_objects_v2() + .send() + .await + .expect_err("bucket not set"); + assert_eq!(format!("{}", err), "failed to construct request"); + rcvr.expect_no_request(); +} diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt index a2c8a581b7..c458a03706 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt @@ -28,9 +28,11 @@ import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope +import software.amazon.smithy.rust.codegen.core.smithy.generators.OperationBuildError import software.amazon.smithy.rust.codegen.core.util.PANIC import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.inputShape +import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.toPascalCase @@ -134,8 +136,11 @@ class EndpointParamsInterceptorGenerator( // lastly, allow these to be overridden by members memberParams.forEach { (memberShape, param) -> val memberName = codegenContext.symbolProvider.toMemberName(memberShape) - rust( - ".${EndpointParamsGenerator.setterName(param.name)}(_input.$memberName.clone())", + val member = writable("_input.$memberName.clone()").letIf(memberShape.isRequired) { ref -> + OperationBuildError(codegenContext.runtimeConfig).emptyOrUnset(symbolProvider, ref, memberShape) + } + rustTemplate( + ".${EndpointParamsGenerator.setterName(param.name)}(dbg!(#{member}))", "member" to member, ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt index 1836a090bb..d6878f1b96 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt @@ -111,6 +111,7 @@ class EndpointsDecoratorTest { structure TestOperationInput { @contextParam(name: "Bucket") + @required bucket: String, nested: NestedStructure } @@ -210,6 +211,10 @@ class EndpointsDecoratorTest { interceptor.called.load(Ordering::Relaxed), "the interceptor should have been called" ); + + // bucket_name is unset and marked as required on the model, so we'll refuse to construct this request + let err = client.test_operation().send().await.expect_err("param missing"); + assert_eq!(format!("{}", err), "failed to construct request"); } """, ) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt index e86061c4c3..0fa8dfa919 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive +import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable @@ -77,9 +78,39 @@ fun RuntimeConfig.operationBuildError() = RuntimeType.operationModule(this).reso fun RuntimeConfig.serializationError() = RuntimeType.operationModule(this).resolve("error::SerializationError") class OperationBuildError(private val runtimeConfig: RuntimeConfig) { + + fun emptyOrUnset(symbolProvider: SymbolProvider, ref: Writable, member: MemberShape): Writable { + val checkSet = RuntimeType.forInlineFun("check_set", RustModule.private("serde_util")) { + rustTemplate( + """ + pub(crate) fn check_set(field: Option, name: &'static str) -> Result { + if let Some(field) = field { + if field != Default::default() { + return Ok(field) + } + } + Err(#{BuildError}::missing_field(name, "field was required")) + } + + """, + "BuildError" to runtimeConfig.operationBuildError(), *preludeScope, + ) + } + + val fieldName = symbolProvider.toMemberName(member) + return writable { + rustTemplate( + "Some(#{checkSet}(#{ref}, ${fieldName.dq()})?)", + "checkSet" to checkSet, + "ref" to ref, + ) + } + } + fun missingField(field: String, details: String) = writable { rust("#T::missing_field(${field.dq()}, ${details.dq()})", runtimeConfig.operationBuildError()) } + fun invalidField(field: String, details: String) = invalidField(field) { rust(details.dq()) } fun invalidField(field: String, details: Writable) = writable { rustTemplate( @@ -164,7 +195,8 @@ class BuilderGenerator( } private fun RustWriter.missingRequiredField(field: String) { - val detailedMessage = "$field was not specified but it is required when building ${symbolProvider.toSymbol(shape).name}" + val detailedMessage = + "$field was not specified but it is required when building ${symbolProvider.toSymbol(shape).name}" OperationBuildError(runtimeConfig).missingField(field, detailedMessage)(this) } From 83fb11e18a7fe100587538e66b636c409c69b852 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 30 Aug 2023 12:20:53 -0400 Subject: [PATCH 2/5] Fix S3 integration tests that did not specify a bucket --- aws/sdk/integration-tests/s3/tests/request_id.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aws/sdk/integration-tests/s3/tests/request_id.rs b/aws/sdk/integration-tests/s3/tests/request_id.rs index d81da66b42..1df48038f2 100644 --- a/aws/sdk/integration-tests/s3/tests/request_id.rs +++ b/aws/sdk/integration-tests/s3/tests/request_id.rs @@ -36,6 +36,7 @@ async fn get_request_id_from_modeled_error() { let err = client .get_object() .key("dontcare") + .bucket("dontcare") .send() .await .expect_err("status was 404, this is an error") @@ -83,6 +84,7 @@ async fn get_request_id_from_unmodeled_error() { let client = Client::from_conf(config); let err = client .get_object() + .bucket("dontcare") .key("dontcare") .send() .await @@ -156,6 +158,7 @@ async fn get_request_id_from_successful_streaming_response() { let output = client .get_object() .key("dontcare") + .bucket("dontcare") .send() .await .expect("valid successful response"); @@ -194,6 +197,7 @@ async fn conversion_to_service_error_maintains_request_id() { let client = Client::from_conf(config); let err = client .get_object() + .bucket("dontcare") .key("dontcare") .send() .await From cc1f0d858f216ccda2d05484dca451e2d192d1c4 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Wed, 30 Aug 2023 12:25:27 -0400 Subject: [PATCH 3/5] Update changelog --- CHANGELOG.next.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index 5f3224f695..63c22df5e8 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -90,3 +90,15 @@ message = "Fix code generation for union members with the `@httpPayload` trait." references = ["smithy-rs#2969", "smithy-rs#1896"] meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "all" } author = "jdisanti" + +[[aws-sdk-rust]] +message = "Make `bucket` required for request construction for S3. When `bucket` is not set, a **different** operation than intended can be triggered." +references = ["smithy-rs#1668", "aws-sdk-rust#873", "smithy-rs#2964"] +meta = { "breaking" = false, "tada" = false, "bug" = true } +author = "rcoh" + +[[smithy-rs]] +message = "Required members with @contextParam are now treated as client-side required." +references = ["smithy-rs#2964"] +meta = { "breaking" = false, "tada" = false, "bug" = false, target = "client" } +author = "rcoh" From 86c8806eef079c3410d45293c84b1281e145c6b5 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 31 Aug 2023 11:47:11 -0400 Subject: [PATCH 4/5] Refactors --- .../EndpointParamsInterceptorGenerator.kt | 10 ++- .../smithy/generators/BuilderGenerator.kt | 68 ++++++++++++------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt index c458a03706..e7114433b4 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsInterceptorGenerator.kt @@ -28,11 +28,10 @@ import software.amazon.smithy.rust.codegen.core.rustlang.withBlockTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope -import software.amazon.smithy.rust.codegen.core.smithy.generators.OperationBuildError +import software.amazon.smithy.rust.codegen.core.smithy.generators.enforceRequired import software.amazon.smithy.rust.codegen.core.util.PANIC import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.inputShape -import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rust.codegen.core.util.orNull import software.amazon.smithy.rust.codegen.core.util.toPascalCase @@ -136,11 +135,10 @@ class EndpointParamsInterceptorGenerator( // lastly, allow these to be overridden by members memberParams.forEach { (memberShape, param) -> val memberName = codegenContext.symbolProvider.toMemberName(memberShape) - val member = writable("_input.$memberName.clone()").letIf(memberShape.isRequired) { ref -> - OperationBuildError(codegenContext.runtimeConfig).emptyOrUnset(symbolProvider, ref, memberShape) - } + val member = memberShape.enforceRequired(writable("_input.$memberName.clone()"), codegenContext) + rustTemplate( - ".${EndpointParamsGenerator.setterName(param.name)}(dbg!(#{member}))", "member" to member, + ".${EndpointParamsGenerator.setterName(param.name)}(#{member})", "member" to member, ) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt index 0fa8dfa919..cc0a1c19d2 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt @@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive @@ -30,6 +31,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.writable +import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext import software.amazon.smithy.rust.codegen.core.smithy.Default import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -47,6 +49,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.rustType import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait import software.amazon.smithy.rust.codegen.core.util.dq import software.amazon.smithy.rust.codegen.core.util.hasTrait +import software.amazon.smithy.rust.codegen.core.util.letIf import software.amazon.smithy.rust.codegen.core.util.redactIfNecessary import software.amazon.smithy.rust.codegen.core.util.toSnakeCase @@ -77,35 +80,54 @@ abstract class BuilderCustomization : NamedCustomization() fun RuntimeConfig.operationBuildError() = RuntimeType.operationModule(this).resolve("error::BuildError") fun RuntimeConfig.serializationError() = RuntimeType.operationModule(this).resolve("error::SerializationError") -class OperationBuildError(private val runtimeConfig: RuntimeConfig) { - - fun emptyOrUnset(symbolProvider: SymbolProvider, ref: Writable, member: MemberShape): Writable { - val checkSet = RuntimeType.forInlineFun("check_set", RustModule.private("serde_util")) { +fun MemberShape.enforceRequired( + field: Writable, + codegenContext: CodegenContext, + produceOption: Boolean = true, +): Writable { + if (!this.isRequired) { + return field + } + val shape = this + val ctx = arrayOf( + "checkSetString" to checkSetString, + "error" to OperationBuildError(codegenContext.runtimeConfig).missingField( + codegenContext.symbolProvider.toMemberName(shape), "A required field was not set", + ), + "field" to field, + ) + val unwrapped = when (codegenContext.model.expectShape(this.target)) { + is StringShape -> writable { rustTemplate( - """ - pub(crate) fn check_set(field: Option, name: &'static str) -> Result { - if let Some(field) = field { - if field != Default::default() { - return Ok(field) - } - } - Err(#{BuildError}::missing_field(name, "field was required")) - } - - """, - "BuildError" to runtimeConfig.operationBuildError(), *preludeScope, + "#{checkSetString}(#{field}).ok_or_else(||#{error})?", + *ctx, ) } - val fieldName = symbolProvider.toMemberName(member) - return writable { - rustTemplate( - "Some(#{checkSet}(#{ref}, ${fieldName.dq()})?)", - "checkSet" to checkSet, - "ref" to ref, - ) + else -> writable { + rustTemplate("#{field}.ok_or_else(||#{error})?", *ctx) } } + return unwrapped.letIf(produceOption) { writable { rust("Some(#T)", it) } } +} + +private val checkSetString = RuntimeType.forInlineFun("non_empty_str", RustModule.private("serde_util")) { + rustTemplate( + """ + pub (crate) fn non_empty_str>(field: Option) -> Option { + if let Some(field) = field { + if field.as_ref() != "" { + return Some(field) + } + } + None + } + + """, + ) +} + +class OperationBuildError(private val runtimeConfig: RuntimeConfig) { fun missingField(field: String, details: String) = writable { rust("#T::missing_field(${field.dq()}, ${details.dq()})", runtimeConfig.operationBuildError()) From de61ad789907597889ac908ed4e87f8bbbc78f55 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 31 Aug 2023 13:43:35 -0400 Subject: [PATCH 5/5] Refactor --- build.gradle.kts | 2 +- .../rust/codegen/core/rustlang/Writable.kt | 9 ++++- .../smithy/generators/BuilderGenerator.kt | 38 ++++--------------- .../codegen/core/rustlang/WritableTest.kt | 10 +++++ settings.gradle.kts | 2 +- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a2d6385db4..2a4758345f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { } allprojects { repositories { - mavenLocal() + /* mavenLocal() */ mavenCentral() google() } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt index b17eb3b030..e1b5ee64c2 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt @@ -25,6 +25,11 @@ fun Writable.isEmpty(): Boolean { return writer.toString() == RustWriter.root().toString() } +fun Writable.map(f: RustWriter.(Writable) -> Unit): Writable { + val self = this + return writable { f(self) } +} + fun Writable.isNotEmpty(): Boolean = !this.isEmpty() operator fun Writable.plus(other: Writable): Writable { @@ -108,10 +113,12 @@ fun rustTypeParameters( "#{gg:W}", "gg" to typeParameter.declaration(withAngleBrackets = false), ) + else -> { // Check if it's a writer. If it is, invoke it; Else, throw a codegen error. @Suppress("UNCHECKED_CAST") - val func = typeParameter as? Writable ?: throw CodegenException("Unhandled type '$typeParameter' encountered by rustTypeParameters writer") + val func = typeParameter as? Writable + ?: throw CodegenException("Unhandled type '$typeParameter' encountered by rustTypeParameters writer") func.invoke(this) } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt index cc0a1c19d2..9697cc624f 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt @@ -13,7 +13,6 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive -import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.Writable @@ -23,6 +22,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlockTemplat import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape import software.amazon.smithy.rust.codegen.core.rustlang.docs import software.amazon.smithy.rust.codegen.core.rustlang.documentShape +import software.amazon.smithy.rust.codegen.core.rustlang.map import software.amazon.smithy.rust.codegen.core.rustlang.render import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock @@ -89,42 +89,20 @@ fun MemberShape.enforceRequired( return field } val shape = this - val ctx = arrayOf( - "checkSetString" to checkSetString, - "error" to OperationBuildError(codegenContext.runtimeConfig).missingField( - codegenContext.symbolProvider.toMemberName(shape), "A required field was not set", - ), - "field" to field, + val error = OperationBuildError(codegenContext.runtimeConfig).missingField( + codegenContext.symbolProvider.toMemberName(shape), "A required field was not set", ) val unwrapped = when (codegenContext.model.expectShape(this.target)) { is StringShape -> writable { rustTemplate( - "#{checkSetString}(#{field}).ok_or_else(||#{error})?", - *ctx, + "#{field}.filter(|f|!AsRef::::as_ref(f).trim().is_empty())", + "field" to field, ) } - else -> writable { - rustTemplate("#{field}.ok_or_else(||#{error})?", *ctx) - } - } - return unwrapped.letIf(produceOption) { writable { rust("Some(#T)", it) } } -} - -private val checkSetString = RuntimeType.forInlineFun("non_empty_str", RustModule.private("serde_util")) { - rustTemplate( - """ - pub (crate) fn non_empty_str>(field: Option) -> Option { - if let Some(field) = field { - if field.as_ref() != "" { - return Some(field) - } - } - None - } - - """, - ) + else -> field + }.map { base -> rustTemplate("#{base}.ok_or_else(||#{error})?", "base" to base, "error" to error) } + return unwrapped.letIf(produceOption) { w -> w.map { rust("Some(#T)", it) } } } class OperationBuildError(private val runtimeConfig: RuntimeConfig) { diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/WritableTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/WritableTest.kt index 04c5ff2f1a..a9b45582ef 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/WritableTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/WritableTest.kt @@ -6,6 +6,7 @@ package software.amazon.smithy.rust.codegen.core.rustlang import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldEndWith import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -106,4 +107,13 @@ internal class RustTypeParametersTest { }.join(writable("+"))(writer) writer.toString() shouldContain "A-B-CD+E+F" } + + @Test + fun `test map`() { + val writer = RustWriter.forModule("model") + val a = writable { rust("a") } + val b = a.map { rust("b(#T)", it) } + b(writer) + writer.toString().trim() shouldEndWith "b(a)" + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6c920d7217..d168d72c0d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,7 +29,7 @@ pluginManagement { buildscript { repositories { - mavenLocal() + /* mavenLocal() */ mavenCentral() google() }