From e7424f01400a0e14c3467c5884c8f474adabe2d5 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 21 Sep 2023 12:52:40 -0400 Subject: [PATCH 1/5] Produce `&[T]` instead of `Option<&[T]>` for list fields This improves API ergonomics when the output is a list --- .../smithy/generators/StructureGenerator.kt | 20 +++++++++++++++++-- .../src/rfcs/rfc0035_collection_defaults.md | 6 +++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt index 80d89e341f..993914b97c 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt @@ -16,12 +16,14 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.asDeref import software.amazon.smithy.rust.codegen.core.rustlang.asRef 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.isCopy import software.amazon.smithy.rust.codegen.core.rustlang.isDeref 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 +import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization @@ -131,15 +133,26 @@ open class StructureGenerator( writer.rustBlock("impl $name") { // Render field accessor methods forEachMember(accessorMembers) { member, memberName, memberSymbol -> - writer.renderMemberDoc(member, memberSymbol) - writer.deprecatedShape(member) val memberType = memberSymbol.rustType() + var unwrapOrDefault = false val returnType = when { + // Automatically flatten vecs + memberType is RustType.Option && memberType.stripOuter() is RustType.Vec -> { + unwrapOrDefault = true + memberType.stripOuter().asDeref().asRef() + } memberType.isCopy() -> memberType memberType is RustType.Option && memberType.member.isDeref() -> memberType.asDeref() memberType.isDeref() -> memberType.asDeref().asRef() else -> memberType.asRef() } + writer.renderMemberDoc(member, memberSymbol) + if (unwrapOrDefault) { + // Add a newline + writer.docs("") + writer.docs("If no value was sent for this field, a default will be set. If you want to determine if no value was sent, use `.$memberName.is_none()`.") + } + writer.deprecatedShape(member) writer.rustBlock("pub fn $memberName(&self) -> ${returnType.render()}") { when { memberType.isCopy() -> rust("self.$memberName") @@ -148,6 +161,9 @@ open class StructureGenerator( memberType.isDeref() -> rust("use std::ops::Deref; self.$memberName.deref()") else -> rust("&self.$memberName") } + if (unwrapOrDefault) { + rust(".unwrap_or_default()") + } } } } diff --git a/design/src/rfcs/rfc0035_collection_defaults.md b/design/src/rfcs/rfc0035_collection_defaults.md index 218782f789..2237c02b2b 100644 --- a/design/src/rfcs/rfc0035_collection_defaults.md +++ b/design/src/rfcs/rfc0035_collection_defaults.md @@ -3,7 +3,7 @@ RFC: Collection Defaults ============= -> Status: Accepted +> Status: Implemented > > Applies to: client @@ -78,5 +78,5 @@ No, many existing APIs don't have the default trait. Changes checklist ----------------- Estimated total work: 2 days -- [ ] Update accessor method generation to auto flatten lists -- [ ] Update docs for accessors to guide users to `.field.is_some()` if they MUST determine if the field was set. +- [x] Update accessor method generation to auto flatten lists +- [x] Update docs for accessors to guide users to `.field.is_some()` if they MUST determine if the field was set. From 103f35e32913af0593a4e74efb3611dd9eedba00 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Thu, 21 Sep 2023 15:42:41 -0400 Subject: [PATCH 2/5] Fix various examples --- .../dynamodb/tests/retries-with-client-rate-limiting.rs | 8 ++++---- aws/sdk/integration-tests/webassembly/src/list_buckets.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs index f811d8cf24..211e8630a0 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs @@ -86,14 +86,14 @@ async fn test_adaptive_retries_with_no_throttling_errors() { let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3)); - assert_eq!(res.table_names(), Some(expected_table_names.as_slice())); + assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success assert_eq!(conn.requests().len(), 3); let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3 + 1)); - assert_eq!(res.table_names(), Some(expected_table_names.as_slice())); + assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) assert_eq!(conn.requests().len(), 5); @@ -141,7 +141,7 @@ async fn test_adaptive_retries_with_throttling_errors() { let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(40)); - assert_eq!(res.table_names(), Some(expected_table_names.as_slice())); + assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success assert_eq!(conn.requests().len(), 3); @@ -149,7 +149,7 @@ async fn test_adaptive_retries_with_throttling_errors() { let res = client.list_tables().send().await.unwrap(); assert!(Duration::from_secs(48) < sleep_impl.total_duration()); assert!(Duration::from_secs(49) > sleep_impl.total_duration()); - assert_eq!(res.table_names(), Some(expected_table_names.as_slice())); + assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) assert_eq!(conn.requests().len(), 5); } diff --git a/aws/sdk/integration-tests/webassembly/src/list_buckets.rs b/aws/sdk/integration-tests/webassembly/src/list_buckets.rs index 53504e6644..692be0e071 100644 --- a/aws/sdk/integration-tests/webassembly/src/list_buckets.rs +++ b/aws/sdk/integration-tests/webassembly/src/list_buckets.rs @@ -11,7 +11,7 @@ pub async fn s3_list_buckets() { let shared_config = get_default_config().await; let client = Client::new(&shared_config); let result = client.list_buckets().send().await.unwrap(); - assert_eq!(result.buckets().unwrap().len(), 2) + assert_eq!(result.buckets().len(), 2) } #[tokio::test] From ada8fcbf3f590977411621d99ed3a0ffeab32f05 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 22 Sep 2023 11:46:38 -0400 Subject: [PATCH 3/5] Make list flattening configurable --- CHANGELOG.next.toml | 22 +++++++++++++++++++ .../client/smithy/ClientCodegenVisitor.kt | 2 ++ .../client/smithy/ClientRustSettings.kt | 7 +++++- .../codegen/core/smithy/CoreRustSettings.kt | 3 +++ .../smithy/generators/StructureGenerator.kt | 5 ++++- 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index b08ebefd4e..3526760cfe 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -177,3 +177,25 @@ message = "Source defaults from the default trait instead of implicitly based on references = ["smithy-rs#2985"] meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" } author = "rcoh" + +[[smithy-rs]] +message = """ +Structure members with the type `Option>` now produce an accessor with the type `&[T]` instead of `Option<&[T]>`. This is enabled by default for clients and can be disabled by updating your smithy-build.json with the following setting: +```json +{ + "codegen": { + "flattenCollectionAccessors": false, + ... + } +} +``` +""" +references = ["smithy-rs#2995"] +author = "rcoh" +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "all" } + +[[aws-sdk-rust]] +message = "Structure members with the type `Option>` now produce an accessor with the type `&[T]` instead of `Option<&[T]>`. To determine if the field was actually set use `..is_some()`." +references = ["smithy-rs#2995"] +author = "rcoh" +meta = { "breaking" = true, "tada" = false, "bug" = false } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt index efeef74e4b..aef854f5b8 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt @@ -38,6 +38,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig import software.amazon.smithy.rust.codegen.core.smithy.contextName import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory @@ -223,6 +224,7 @@ class ClientCodegenVisitor( this, shape, codegenDecorator.structureCustomizations(codegenContext, emptyList()), + structSettings = StructSettings(settings.codegenConfig.flattenCollectionAccessors), ).render() implBlock(symbolProvider.toSymbol(shape)) { diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt index acc0a59184..67b76f0862 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientRustSettings.kt @@ -82,6 +82,7 @@ data class ClientRustSettings( data class ClientCodegenConfig( override val formatTimeoutSeconds: Int = defaultFormatTimeoutSeconds, override val debugMode: Boolean = defaultDebugMode, + override val flattenCollectionAccessors: Boolean = defaultFlattenAccessors, val nullabilityCheckMode: NullableIndex.CheckMode = NullableIndex.CheckMode.CLIENT, val renameExceptions: Boolean = defaultRenameExceptions, val includeFluentClient: Boolean = defaultIncludeFluentClient, @@ -92,7 +93,7 @@ data class ClientCodegenConfig( val includeEndpointUrlConfig: Boolean = defaultIncludeEndpointUrlConfig, val enableUserConfigurableRuntimePlugins: Boolean = defaultEnableUserConfigurableRuntimePlugins, ) : CoreCodegenConfig( - formatTimeoutSeconds, debugMode, + formatTimeoutSeconds, debugMode, defaultFlattenAccessors, ) { companion object { private const val defaultRenameExceptions = true @@ -103,10 +104,14 @@ data class ClientCodegenConfig( private const val defaultEnableUserConfigurableRuntimePlugins = true private const val defaultNullabilityCheckMode = "CLIENT" + // Note: only clients default to true, servers default to false + private const val defaultFlattenAccessors = true + fun fromCodegenConfigAndNode(coreCodegenConfig: CoreCodegenConfig, node: Optional) = if (node.isPresent) { ClientCodegenConfig( formatTimeoutSeconds = coreCodegenConfig.formatTimeoutSeconds, + flattenCollectionAccessors = node.get().getBooleanMemberOrDefault("flattenCollectionAccessors", defaultFlattenAccessors), debugMode = coreCodegenConfig.debugMode, eventStreamAllowList = node.get().getArrayMember("eventStreamAllowList").map { array -> array.toList().mapNotNull { node -> diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CoreRustSettings.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CoreRustSettings.kt index b477ab5607..5e103f1462 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CoreRustSettings.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CoreRustSettings.kt @@ -41,16 +41,19 @@ const val CODEGEN_SETTINGS = "codegen" open class CoreCodegenConfig( open val formatTimeoutSeconds: Int = defaultFormatTimeoutSeconds, open val debugMode: Boolean = defaultDebugMode, + open val flattenCollectionAccessors: Boolean = defaultFlattenMode, ) { companion object { const val defaultFormatTimeoutSeconds = 20 const val defaultDebugMode = false + const val defaultFlattenMode = false fun fromNode(node: Optional): CoreCodegenConfig = if (node.isPresent) { CoreCodegenConfig( formatTimeoutSeconds = node.get().getNumberMemberOrDefault("formatTimeoutSeconds", defaultFormatTimeoutSeconds).toInt(), debugMode = node.get().getBooleanMemberOrDefault("debugMode", defaultDebugMode), + flattenCollectionAccessors = node.get().getBooleanMemberOrDefault("flattenCollectionAccessors", defaultFlattenMode), ) } else { CoreCodegenConfig( diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt index 993914b97c..9ae7566089 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt @@ -55,12 +55,15 @@ sealed class StructureSection(name: String) : Section(name) { /** Customizations for StructureGenerator */ abstract class StructureCustomization : NamedCustomization() +data class StructSettings(val flattenVecAccessors: Boolean) + open class StructureGenerator( val model: Model, private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StructureShape, private val customizations: List, + private val structSettings: StructSettings = StructSettings(flattenVecAccessors = true), ) { companion object { /** Reserved struct member names */ @@ -137,7 +140,7 @@ open class StructureGenerator( var unwrapOrDefault = false val returnType = when { // Automatically flatten vecs - memberType is RustType.Option && memberType.stripOuter() is RustType.Vec -> { + structSettings.flattenVecAccessors && memberType is RustType.Option && memberType.stripOuter() is RustType.Vec -> { unwrapOrDefault = true memberType.stripOuter().asDeref().asRef() } From 83ea856a06fe2acffbefb5d5240aebe55069dd5e Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 22 Sep 2023 12:57:00 -0400 Subject: [PATCH 4/5] Fix defaults for servers --- .../smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt | 4 ++-- .../codegen/client/smithy/generators/error/ErrorGenerator.kt | 3 +++ .../amazon/smithy/rust/codegen/core/smithy/CodegenContext.kt | 3 +++ .../smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt index aef854f5b8..ab26c7ae9e 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/ClientCodegenVisitor.kt @@ -38,7 +38,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig import software.amazon.smithy.rust.codegen.core.smithy.contextName import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator -import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolGeneratorFactory @@ -224,7 +223,7 @@ class ClientCodegenVisitor( this, shape, codegenDecorator.structureCustomizations(codegenContext, emptyList()), - structSettings = StructSettings(settings.codegenConfig.flattenCollectionAccessors), + structSettings = codegenContext.structSettings(), ).render() implBlock(symbolProvider.toSymbol(shape)) { @@ -248,6 +247,7 @@ class ClientCodegenVisitor( shape, errorTrait, codegenDecorator.errorImplCustomizations(codegenContext, emptyList()), + codegenContext.structSettings(), ) errorGenerator::renderStruct to errorGenerator::renderBuilder } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt index 21d4f24aef..d90fd44152 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/error/ErrorGenerator.kt @@ -22,6 +22,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderSection +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureCustomization import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureSection @@ -34,6 +35,7 @@ class ErrorGenerator( private val shape: StructureShape, private val error: ErrorTrait, private val implCustomizations: List, + private val structSettings: StructSettings, ) { private val runtimeConfig = symbolProvider.config.runtimeConfig private val symbol = symbolProvider.toSymbol(shape) @@ -59,6 +61,7 @@ class ErrorGenerator( } }, ), + structSettings, ).render() ErrorImplGenerator( diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenContext.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenContext.kt index ea427fc65f..a08eddb5ed 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenContext.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/CodegenContext.kt @@ -9,6 +9,7 @@ import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderInstantiator +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings /** * [CodegenContext] contains code-generation context that is _common to all_ smithy-rs plugins. @@ -91,5 +92,7 @@ abstract class CodegenContext( "A ModuleDocProvider must be set on the CodegenContext" } + fun structSettings() = StructSettings(settings.codegenConfig.flattenCollectionAccessors) + abstract fun builderInstantiator(): BuilderInstantiator } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index 156cc455eb..a9d93d2b4f 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -294,6 +294,7 @@ open class ServerCodegenVisitor( this, shape, codegenDecorator.structureCustomizations(codegenContext, emptyList()), + structSettings = codegenContext.structSettings(), ).render() shape.getTrait()?.also { errorTrait -> From 7a939a9b3021bdb38dfa66417679a276b0c146a4 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Fri, 22 Sep 2023 14:29:11 -0400 Subject: [PATCH 5/5] Remove default for parameter and ensure its specified everywhere --- .../smithy/generators/StructureGenerator.kt | 2 +- .../rust/codegen/core/testutil/TestHelpers.kt | 3 +- .../smithy/generators/BuilderGeneratorTest.kt | 19 +++++--- .../generators/StructureGeneratorTest.kt | 48 ++++++++++--------- .../RecursiveShapesIntegrationTest.kt | 3 +- .../PythonServerStructureGenerator.kt | 2 +- .../smithy/testutil/ServerTestHelpers.kt | 3 +- .../ServerBuilderDefaultValuesTest.kt | 4 +- .../generators/ServerBuilderGeneratorTest.kt | 2 +- .../generators/TsServerStructureGenerator.kt | 3 +- 10 files changed, 51 insertions(+), 38 deletions(-) diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt index 9ae7566089..cd18547094 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt @@ -63,7 +63,7 @@ open class StructureGenerator( private val writer: RustWriter, private val shape: StructureShape, private val customizations: List, - private val structSettings: StructSettings = StructSettings(flattenVecAccessors = true), + private val structSettings: StructSettings, ) { companion object { /** Reserved struct member names */ diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt index 87ebb679cc..1b2dc7a943 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt @@ -37,6 +37,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig import software.amazon.smithy.rust.codegen.core.smithy.SymbolVisitor import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderInstantiator +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.module import software.amazon.smithy.rust.codegen.core.smithy.traits.SyntheticInputTrait @@ -194,7 +195,7 @@ fun StructureShape.renderWithModelBuilder( ) { val struct = this rustCrate.withModule(symbolProvider.moduleForShape(struct)) { - StructureGenerator(model, symbolProvider, this, struct, emptyList()).render() + StructureGenerator(model, symbolProvider, this, struct, emptyList(), StructSettings(true)).render() implBlock(symbolProvider.toSymbol(struct)) { BuilderGenerator.renderConvenienceMethod(this, symbolProvider, struct) } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt index 06888429e6..2220a6f10f 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt @@ -7,14 +7,17 @@ package software.amazon.smithy.rust.codegen.core.smithy.generators import org.junit.jupiter.api.Test import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.NullableIndex import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.AllowDeprecated +import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.implBlock import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.Default +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.setDefault import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace @@ -37,8 +40,8 @@ internal class BuilderGeneratorTest { val project = TestWorkspace.testProject(provider) project.moduleFor(inner) { rust("##![allow(deprecated)]") - StructureGenerator(model, provider, this, inner, emptyList()).render() - StructureGenerator(model, provider, this, struct, emptyList()).render() + generator(model, provider, this, inner).render() + generator(model, provider, this, struct).render() implBlock(provider.toSymbol(struct)) { BuilderGenerator.renderConvenienceMethod(this, provider, struct) } @@ -73,8 +76,8 @@ internal class BuilderGeneratorTest { project.moduleFor(StructureGeneratorTest.struct) { AllowDeprecated.render(this) - StructureGenerator(model, provider, this, inner, emptyList()).render() - StructureGenerator(model, provider, this, struct, emptyList()).render() + generator(model, provider, this, inner).render() + generator(model, provider, this, struct).render() implBlock(provider.toSymbol(struct)) { BuilderGenerator.renderConvenienceMethod(this, provider, struct) } @@ -94,12 +97,14 @@ internal class BuilderGeneratorTest { project.compileAndTest() } + private fun generator(model: Model, provider: RustSymbolProvider, writer: RustWriter, shape: StructureShape) = StructureGenerator(model, provider, writer, shape, emptyList(), StructSettings(flattenVecAccessors = true)) + @Test fun `builder for a struct with sensitive fields should implement the debug trait as such`() { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.moduleFor(credentials) { - StructureGenerator(model, provider, this, credentials, emptyList()).render() + generator(model, provider, this, credentials).render() implBlock(provider.toSymbol(credentials)) { BuilderGenerator.renderConvenienceMethod(this, provider, credentials) } @@ -126,7 +131,7 @@ internal class BuilderGeneratorTest { val provider = testSymbolProvider(model) val project = TestWorkspace.testProject(provider) project.moduleFor(secretStructure) { - StructureGenerator(model, provider, this, secretStructure, emptyList()).render() + generator(model, provider, this, secretStructure).render() implBlock(provider.toSymbol(secretStructure)) { BuilderGenerator.renderConvenienceMethod(this, provider, secretStructure) } @@ -188,7 +193,7 @@ internal class BuilderGeneratorTest { val project = TestWorkspace.testProject(provider) val shape: StructureShape = model.lookup("com.test#MyStruct") project.useShapeWriter(shape) { - StructureGenerator(model, provider, this, shape, listOf()).render() + generator(model, provider, this, shape).render() BuilderGenerator(model, provider, shape, listOf()).render(this) unitTest("test_defaults") { rustTemplate( diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt index af7ff639b5..f31fd538e8 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt @@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.core.smithy.generators import io.kotest.matchers.string.shouldContainInOrder import io.kotest.matchers.string.shouldNotContain import org.junit.jupiter.api.Test +import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute import software.amazon.smithy.rust.codegen.core.rustlang.RustModule @@ -15,6 +16,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordConfig import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.rust import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel @@ -92,13 +94,15 @@ class StructureGeneratorTest { ) } + private fun structureGenerator(model: Model, provider: RustSymbolProvider, writer: RustWriter, shape: StructureShape) = StructureGenerator(model, provider, writer, shape, emptyList(), StructSettings(flattenVecAccessors = true)) + @Test fun `generate basic structures`() { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - StructureGenerator(model, provider, this, inner, emptyList()).render() - StructureGenerator(model, provider, this, struct, emptyList()).render() + structureGenerator(model, provider, this, inner).render() + structureGenerator(model, provider, this, struct).render() unitTest( "struct_fields_optional", """ @@ -120,11 +124,11 @@ class StructureGeneratorTest { project.lib { Attribute.AllowDeprecated.render(this) } project.moduleFor(inner) { - val innerGenerator = StructureGenerator(model, provider, this, inner, emptyList()) + val innerGenerator = structureGenerator(model, provider, this, inner) innerGenerator.render() } project.withModule(RustModule.public("structs")) { - val generator = StructureGenerator(model, provider, this, struct, emptyList()) + val generator = structureGenerator(model, provider, this, struct) generator.render() } // By putting the test in another module, it can't access the struct @@ -147,7 +151,7 @@ class StructureGeneratorTest { fun `generate a custom debug implementation when the sensitive trait is applied to some members`() { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) TestWorkspace.testProject().unitTest { - StructureGenerator(model, provider, this, credentials, emptyList()).render() + structureGenerator(model, provider, this, credentials).render() this.unitTest( "sensitive_fields_redacted", @@ -167,7 +171,7 @@ class StructureGeneratorTest { fun `generate a custom debug implementation when the sensitive trait is applied to the struct`() { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) TestWorkspace.testProject().unitTest { - StructureGenerator(model, provider, this, secretStructure, emptyList()).render() + structureGenerator(model, provider, this, secretStructure).render() this.unitTest( "sensitive_structure_redacted", @@ -186,8 +190,8 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - val secretGenerator = StructureGenerator(model, provider, this, secretStructure, emptyList()) - val generator = StructureGenerator(model, provider, this, structWithInnerSecretStructure, emptyList()) + val secretGenerator = structureGenerator(model, provider, this, secretStructure) + val generator = structureGenerator(model, provider, this, structWithInnerSecretStructure) secretGenerator.render() generator.render() unitTest( @@ -230,8 +234,8 @@ class StructureGeneratorTest { Attribute.DenyMissingDocs.render(this) } project.moduleFor(model.lookup("com.test#Inner")) { - StructureGenerator(model, provider, this, model.lookup("com.test#Inner"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("com.test#MyStruct"), emptyList()).render() + structureGenerator(model, provider, this, model.lookup("com.test#Inner")).render() + structureGenerator(model, provider, this, model.lookup("com.test#MyStruct")).render() } project.compileAndTest() @@ -241,7 +245,7 @@ class StructureGeneratorTest { fun `documents are optional in structs`() { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) TestWorkspace.testProject().unitTest { - StructureGenerator(model, provider, this, structWithDoc, emptyList()).render() + structureGenerator(model, provider, this, structWithDoc).render() rust( """ let _struct = StructWithDoc { @@ -274,10 +278,10 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.lib { rust("##![allow(deprecated)]") } project.moduleFor(model.lookup("test#Foo")) { - StructureGenerator(model, provider, this, model.lookup("test#Foo"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("test#Bar"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("test#Baz"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("test#Qux"), emptyList()).render() + structureGenerator(model, provider, this, model.lookup("test#Foo")).render() + structureGenerator(model, provider, this, model.lookup("test#Bar")).render() + structureGenerator(model, provider, this, model.lookup("test#Baz")).render() + structureGenerator(model, provider, this, model.lookup("test#Qux")).render() } // turn on clippy to check the semver-compliant version of `since`. @@ -307,9 +311,9 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.lib { rust("##![allow(deprecated)]") } project.moduleFor(model.lookup("test#Nested")) { - StructureGenerator(model, provider, this, model.lookup("test#Nested"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("test#Foo"), emptyList()).render() - StructureGenerator(model, provider, this, model.lookup("test#Bar"), emptyList()).render() + structureGenerator(model, provider, this, model.lookup("test#Nested")).render() + structureGenerator(model, provider, this, model.lookup("test#Foo")).render() + structureGenerator(model, provider, this, model.lookup("test#Bar")).render() } project.compileAndTest() @@ -356,8 +360,8 @@ class StructureGeneratorTest { val project = TestWorkspace.testProject(provider) project.useShapeWriter(inner) { - StructureGenerator(testModel, provider, this, testModel.lookup("test#One"), emptyList()).render() - StructureGenerator(testModel, provider, this, testModel.lookup("test#Two"), emptyList()).render() + structureGenerator(testModel, provider, this, testModel.lookup("test#One")).render() + structureGenerator(testModel, provider, this, testModel.lookup("test#Two")).render() rustBlock("fn compile_test_one(one: &crate::test_model::One)") { rust( @@ -414,7 +418,7 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) RustWriter.forModule("test").let { writer -> - StructureGenerator(model, provider, writer, struct, emptyList()).render() + structureGenerator(model, provider, writer, struct).render() writer.toString().shouldNotContain("#[doc(hidden)]") } } @@ -430,7 +434,7 @@ class StructureGeneratorTest { val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig) RustWriter.forModule("test").let { writer -> - StructureGenerator(model, provider, writer, struct, emptyList()).render() + structureGenerator(model, provider, writer, struct).render() writer.toString().shouldNotContain("#[doc(hidden)]") } } diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt index d204a605c4..110836fdad 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/transformers/RecursiveShapesIntegrationTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.assertThrows import software.amazon.smithy.model.Model import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.UnionShape +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.UnionGenerator import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace @@ -50,7 +51,7 @@ class RecursiveShapesIntegrationTest { val structures = listOf("Expr", "SecondTree").map { input.lookup("com.example#$it") } structures.forEach { struct -> project.moduleFor(struct) { - StructureGenerator(input, symbolProvider, this, struct, emptyList()).render() + StructureGenerator(input, symbolProvider, this, struct, emptyList(), StructSettings(true)).render() } } input.lookup("com.example#Atom").also { atom -> diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt index d9d2df8cc2..9357383e6b 100644 --- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt +++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt @@ -40,7 +40,7 @@ class PythonServerStructureGenerator( private val codegenContext: ServerCodegenContext, private val writer: RustWriter, private val shape: StructureShape, -) : StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList()) { +) : StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList(), codegenContext.structSettings()) { private val symbolProvider = codegenContext.symbolProvider private val libName = codegenContext.settings.moduleName.toSnakeCase() diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt index 82b8af20ea..60978c65ee 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/testutil/ServerTestHelpers.kt @@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProviderConfig +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.testutil.TestModuleDocProvider import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig @@ -145,7 +146,7 @@ fun StructureShape.serverRenderWithModelBuilder( writer: RustWriter, protocol: ServerProtocol? = null, ) { - StructureGenerator(model, symbolProvider, writer, this, emptyList()).render() + StructureGenerator(model, symbolProvider, writer, this, emptyList(), StructSettings(false)).render() val serverCodegenContext = serverTestCodegenContext(model) // Note that this always uses `ServerBuilderGenerator` and _not_ `ServerBuilderGeneratorWithoutPublicConstrainedTypes`, // regardless of the `publicConstrainedTypes` setting. diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt index e8021ad9e7..172ad5f4d9 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderDefaultValuesTest.kt @@ -197,7 +197,7 @@ class ServerBuilderDefaultValuesTest { model.lookup("com.test#Language"), SmithyValidationExceptionConversionGenerator(codegenContext), ).render(writer) - StructureGenerator(model, symbolProvider, writer, struct, emptyList()).render() + StructureGenerator(model, symbolProvider, writer, struct, emptyList(), codegenContext.structSettings()).render() } private fun writeServerBuilderGenerator(rustCrate: RustCrate, writer: RustWriter, model: Model, symbolProvider: RustSymbolProvider) { @@ -220,7 +220,7 @@ class ServerBuilderDefaultValuesTest { model.lookup("com.test#Language"), SmithyValidationExceptionConversionGenerator(codegenContext), ).render(writer) - StructureGenerator(model, symbolProvider, writer, struct, emptyList()).render() + StructureGenerator(model, symbolProvider, writer, struct, emptyList(), codegenContext.structSettings()).render() } private fun structSetters(values: Map, optional: Boolean) = writable { diff --git a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt index 0ddbc1c542..27e45c373c 100644 --- a/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt +++ b/codegen-server/src/test/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorTest.kt @@ -45,7 +45,7 @@ class ServerBuilderGeneratorTest { val writer = this val shape = model.lookup("test#Credentials") - StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList()).render() + StructureGenerator(model, codegenContext.symbolProvider, writer, shape, emptyList(), codegenContext.structSettings()).render() val builderGenerator = ServerBuilderGenerator( codegenContext, shape, diff --git a/codegen-server/typescript/src/main/kotlin/software/amazon/smithy/rust/codegen/server/typescript/smithy/generators/TsServerStructureGenerator.kt b/codegen-server/typescript/src/main/kotlin/software/amazon/smithy/rust/codegen/server/typescript/smithy/generators/TsServerStructureGenerator.kt index 87d8300ca7..cf08892bc9 100644 --- a/codegen-server/typescript/src/main/kotlin/software/amazon/smithy/rust/codegen/server/typescript/smithy/generators/TsServerStructureGenerator.kt +++ b/codegen-server/typescript/src/main/kotlin/software/amazon/smithy/rust/codegen/server/typescript/smithy/generators/TsServerStructureGenerator.kt @@ -13,6 +13,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter import software.amazon.smithy.rust.codegen.core.rustlang.rustInlineTemplate import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.generators.StructSettings import software.amazon.smithy.rust.codegen.core.smithy.generators.StructureGenerator import software.amazon.smithy.rust.codegen.core.util.hasTrait import software.amazon.smithy.rust.codegen.server.typescript.smithy.TsServerCargoDependency @@ -27,7 +28,7 @@ class TsServerStructureGenerator( private val symbolProvider: RustSymbolProvider, private val writer: RustWriter, private val shape: StructureShape, -) : StructureGenerator(model, symbolProvider, writer, shape, listOf()) { +) : StructureGenerator(model, symbolProvider, writer, shape, listOf(), StructSettings(flattenVecAccessors = false)) { private val napiDerive = TsServerCargoDependency.NapiDerive.toType()