From c66af3dfc5753cc37b6aed4775e83886568e1ece Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Tue, 6 Sep 2022 14:14:00 -0500 Subject: [PATCH 1/4] remove: need for operation type aliasing rename: FluentClientGenerics.sendBounds params to be more accurate update: FlexibleClientGenerics.sendBounds impl for readability update: type of FluentClientGenerator input param `retryPolicyType` to be `Any` with a default of `RustType.Unit` update: PaginatorGenerator to take retryPolicy as an input chore: fix some spelling and grammar issues remove: redundant `nextTokenEmpty` function from PaginatorGenerator --- CHANGELOG.next.toml | 8 ++++ .../rustsdk/AwsFluentClientDecorator.kt | 2 +- .../smithy/generators/PaginatorGenerator.kt | 20 +++++----- .../client/FluentClientGenerator.kt | 9 ++--- .../generators/client/FluentClientGenerics.kt | 19 ++++----- .../generators/protocol/ProtocolGenerator.kt | 40 ++----------------- 6 files changed, 37 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index ddb541e1d5..cec0fb6dc6 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -169,3 +169,11 @@ let response = client.some_operation() references = ["smithy-rs#1647", "smithy-rs#1112"] meta = { "breaking" = false, "tada" = true, "bug" = false, "target" = "client"} author = "Velfi" + +[[smithy-rs]] +message = """ +Removed the need to generate operation output and retry aliases in codegen. +""" +references = ["smithy-rs#967"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "Velfi" \ No newline at end of file diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index 3d7d809795..98a0d1dee3 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -74,7 +74,7 @@ private class AwsClientGenerics(private val types: Types) : FluentClientGenerics override val bounds = writable { } /** Bounds for generated `send()` functions */ - override fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType): Writable = writable { } + override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Any): Writable = writable { } override fun toGenericsGenerator(): GenericsGenerator { return GenericsGenerator() diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt index 5b817edc93..bb2062b546 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt @@ -48,6 +48,7 @@ class PaginatorGenerator private constructor( service: ServiceShape, operation: OperationShape, private val generics: FluentClientGenerics, + retryPolicyType: Any = RustType.Unit, ) { companion object { @@ -55,6 +56,7 @@ class PaginatorGenerator private constructor( coreCodegenContext: CoreCodegenContext, generics: FluentClientGenerics, operationShape: OperationShape, + retryPolicyType: Any, ): RuntimeType? { return if (operationShape.isPaginated(coreCodegenContext.model)) { PaginatorGenerator( @@ -63,6 +65,7 @@ class PaginatorGenerator private constructor( coreCodegenContext.serviceShape, operationShape, generics, + retryPolicyType, ).paginatorType() } else { null @@ -82,7 +85,8 @@ class PaginatorGenerator private constructor( ) private val inputType = symbolProvider.toSymbol(operation.inputShape(model)) - private val outputType = operation.outputShape(model) + private val outputShape = operation.outputShape(model) + private val outputType = symbolProvider.toSymbol(outputShape) private val errorType = operation.errorSymbol(model, symbolProvider, CodegenTarget.CLIENT) private fun paginatorType(): RuntimeType = RuntimeType.forInlineFun( @@ -95,12 +99,12 @@ class PaginatorGenerator private constructor( "generics" to generics.decl, "bounds" to generics.bounds, "page_size_setter" to pageSizeSetter(), - "send_bounds" to generics.sendBounds(inputType, symbolProvider.toSymbol(outputType), errorType), + "send_bounds" to generics.sendBounds(symbolProvider.toSymbol(operation), outputType, errorType, retryPolicyType), // Operation Types "operation" to symbolProvider.toSymbol(operation), "Input" to inputType, - "Output" to symbolProvider.toSymbol(outputType), + "Output" to outputType, "Error" to errorType, "Builder" to operation.inputShape(model).builderSymbol(symbolProvider), @@ -118,7 +122,7 @@ class PaginatorGenerator private constructor( /** Generate the paginator struct & impl **/ private fun generate() = writable { val outputTokenLens = NestedAccessorGenerator(symbolProvider).generateBorrowingAccessor( - outputType, + outputShape, paginationInfo.outputTokenMemberPath, ) val inputTokenMember = symbolProvider.toMemberName(paginationInfo.inputTokenMember) @@ -173,7 +177,7 @@ class PaginatorGenerator private constructor( let done = match resp { Ok(ref resp) => { let new_token = #{output_token}(resp); - let is_empty = ${nextTokenEmpty("new_token")}; + let is_empty = new_token.map(|token| token.is_empty()).unwrap_or(true); if !is_empty && new_token == input.$inputTokenMember.as_ref() { let _ = tx.send(Err(#{SdkError}::ConstructionFailure("next token did not change, aborting paginator. This indicates an SDK or AWS service bug.".into()))).await; return; @@ -259,7 +263,7 @@ class PaginatorGenerator private constructor( """, "extract_items" to NestedAccessorGenerator(symbolProvider).generateOwnedAccessor( - outputType, + outputShape, paginationInfo.itemsMemberPath, ), *codegenScope, @@ -267,10 +271,6 @@ class PaginatorGenerator private constructor( } } - private fun nextTokenEmpty(token: String): String { - return "$token.map(|token|token.is_empty()).unwrap_or(true)" - } - private fun pageSizeSetter() = writable { paginationInfo.pageSizeMember.orNull()?.also { val memberName = symbolProvider.toMemberName(it) diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt index e4f7e8ca88..1351ecc6fa 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt @@ -66,7 +66,7 @@ class FluentClientGenerator( client = CargoDependency.SmithyClient(codegenContext.runtimeConfig).asType(), ), private val customizations: List = emptyList(), - private val retryPolicyType: RuntimeType? = null, + private val retryPolicyType: Any = RustType.Unit, ) { companion object { fun clientOperationFnName(operationShape: OperationShape, symbolProvider: RustSymbolProvider): String = @@ -274,7 +274,6 @@ class FluentClientGenerator( "client" to clientDep.asType(), "bounds" to generics.bounds, ) { - val inputType = symbolProvider.toSymbol(operation.inputShape(model)) val outputType = symbolProvider.toSymbol(operation.outputShape(model)) val errorType = operation.errorSymbol(model, symbolProvider, CodegenTarget.CLIENT) @@ -322,14 +321,14 @@ class FluentClientGenerator( "OperationOutput" to outputType, "SdkError" to runtimeConfig.smithyHttp().member("result::SdkError"), "SdkSuccess" to runtimeConfig.smithyHttp().member("result::SdkSuccess"), - "send_bounds" to generics.sendBounds(inputType, outputType, errorType), + "send_bounds" to generics.sendBounds(operationSymbol, outputType, errorType, retryPolicyType), "customizable_op_type_params" to rustTypeParameters( symbolProvider.toSymbol(operation), - retryPolicyType ?: RustType.Unit, + retryPolicyType, generics.toGenericsGenerator(), ), ) - PaginatorGenerator.paginatorType(codegenContext, generics, operation)?.also { paginatorType -> + PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryPolicyType)?.also { paginatorType -> rustTemplate( """ /// Create a paginator for this request diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt index 9684c33285..eae93f69da 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt @@ -28,7 +28,7 @@ interface FluentClientGenerics { val bounds: Writable /** Bounds for generated `send()` functions */ - fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType): Writable + fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType, retryPolicy: Any): Writable /** Convert this `FluentClientGenerics` into the more general `GenericsGenerator` */ fun toGenericsGenerator(): GenericsGenerator @@ -70,21 +70,22 @@ data class FlexibleClientGenerics( } /** Bounds for generated `send()` functions */ - override fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType): Writable = writable { + override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Any): Writable = writable { rustTemplate( """ where R::Policy: #{client}::bounds::SmithyRetryPolicy< - #{Input}OperationOutputAlias, - #{Output}, - #{Error}, - #{Input}OperationRetryAlias + #{Operation}, + #{OperationOutput}, + #{OperationError}, + #{RetryPolicy} > """, "client" to client, - "Input" to input, - "Output" to output, - "Error" to error, + "Operation" to operation, + "OperationOutput" to operationOutput, + "OperationError" to operationError, + "RetryPolicy" to retryPolicy, ) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/protocol/ProtocolGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/protocol/ProtocolGenerator.kt index bb139bd40c..c629d26836 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/protocol/ProtocolGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/protocol/ProtocolGenerator.kt @@ -6,9 +6,7 @@ package software.amazon.smithy.rust.codegen.smithy.generators.protocol import software.amazon.smithy.model.shapes.OperationShape -import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.rustlang.Attribute -import software.amazon.smithy.rust.codegen.rustlang.RustType import software.amazon.smithy.rust.codegen.rustlang.RustWriter import software.amazon.smithy.rust.codegen.rustlang.docLink import software.amazon.smithy.rust.codegen.rustlang.rust @@ -30,7 +28,7 @@ import software.amazon.smithy.rust.codegen.util.inputShape * Used to generate payloads that will go into HTTP bodies for HTTP requests (used by clients) * and responses (used by servers). * - * **Note:** There is only one real implementation of this interface. The other implementation is test only. + * **Note:** There is only one real implementation of this interface. The other implementation is test-only. * All protocols use the same class. * * Different protocols (e.g. JSON vs. XML) need to use different functionality to generate payload bodies. @@ -39,7 +37,7 @@ interface ProtocolPayloadGenerator { data class PayloadMetadata(val takesOwnership: Boolean) /** - * Code generation needs to handle whether or not [generatePayload] takes ownership of the input or output + * Code generation needs to handle whether [generatePayload] takes ownership of the input or output * for a given operation shape. * * Most operations will use the HTTP payload as a reference, but for operations that will consume the entire stream @@ -62,7 +60,7 @@ interface ProtocolPayloadGenerator { /** * Protocol Trait implementation generator * - * **Note:** There is only one real implementation of this interface. The other implementation is test only. + * **Note:** There is only one real implementation of this interface. The other implementation is test-only. * All protocols use the same class. * * Protocols implement one of two traits to enable parsing HTTP responses: @@ -82,7 +80,7 @@ open class ProtocolGenerator( /** * `Protocol` contains all protocol specific information. Each smithy protocol, e.g. RestJson, RestXml, etc. will * have their own implementation of the protocol interface which defines how an input shape becomes and http::Request - * and an output shape is build from an http::Response. + * and an output shape is build from an `http::Response`. */ private val protocol: Protocol, /** @@ -117,9 +115,6 @@ open class ProtocolGenerator( val builderGenerator = BuilderGenerator(model, symbolProvider, operationShape.inputShape(model)) builderGenerator.render(inputWriter) - // generate type aliases for the fluent builders - renderTypeAliases(inputWriter, operationShape, customizations, inputShape) - // impl OperationInputShape { ... } val operationName = symbolProvider.toSymbol(operationShape).name inputWriter.implBlock(inputShape, symbolProvider) { @@ -172,31 +167,4 @@ open class ProtocolGenerator( ) { traitGenerator.generateTraitImpls(operationWriter, operationShape, emptyList()) } - - private fun renderTypeAliases( - inputWriter: RustWriter, - operationShape: OperationShape, - customizations: List, - inputShape: StructureShape, - ) { - // TODO(https://github.com/awslabs/smithy-rs/issues/976): Callers should be able to invoke - // buildOperationType* directly to get the type rather than depending on these aliases. - // These are used in fluent clients. - val operationTypeOutput = buildOperationTypeOutput(inputWriter, operationShape) - val operationTypeRetry = buildOperationTypeRetry(inputWriter, customizations) - val inputPrefix = symbolProvider.toSymbol(inputShape).name - - inputWriter.rust( - """ - ##[doc(hidden)] pub type ${inputPrefix}OperationOutputAlias = $operationTypeOutput; - ##[doc(hidden)] pub type ${inputPrefix}OperationRetryAlias = $operationTypeRetry; - """, - ) - } - - private fun buildOperationTypeOutput(writer: RustWriter, shape: OperationShape): String = - writer.format(symbolProvider.toSymbol(shape)) - - private fun buildOperationTypeRetry(writer: RustWriter, customizations: List): String = - (customizations.firstNotNullOfOrNull { it.retryType() } ?: RustType.Unit).let { writer.format(it) } } From 66e901d0e34afc0074742ae07904767f42cb1c77 Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Tue, 6 Sep 2022 14:41:24 -0500 Subject: [PATCH 2/4] Update CHANGELOG.next.toml Co-authored-by: John DiSanti --- CHANGELOG.next.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index cec0fb6dc6..0921562b20 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -174,6 +174,6 @@ author = "Velfi" message = """ Removed the need to generate operation output and retry aliases in codegen. """ -references = ["smithy-rs#967"] +references = ["smithy-rs#976", "smithy-rs#1710"] meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } author = "Velfi" \ No newline at end of file From 15142a638c93d327267c252c0995a00545d41bfb Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Wed, 7 Sep 2022 10:47:20 -0500 Subject: [PATCH 3/4] add: `writable` property to RustType that returns the type as a Writable add: test for RustType writable add: `writable` property to RuntimeType that returns the type as a Writable update: FluentClientGenerator to take a writable for retry --- .../rustsdk/AwsFluentClientDecorator.kt | 4 +- .../smithy/rust/codegen/rustlang/RustTypes.kt | 48 ++++++- .../rust/codegen/rustlang/RustWriter.kt | 3 +- .../smithy/rust/codegen/rustlang/Writable.kt | 14 +- .../rust/codegen/smithy/RuntimeTypes.kt | 7 + .../smithy/generators/PaginatorGenerator.kt | 11 +- .../client/FluentClientGenerator.kt | 9 +- .../generators/client/FluentClientGenerics.kt | 6 +- .../rust/codegen/rustlang/RustTypesTest.kt | 135 ++++++++++++++++++ .../rust/codegen/rustlang/WritableTest.kt | 29 +++- 10 files changed, 238 insertions(+), 28 deletions(-) create mode 100644 codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index 98a0d1dee3..768387569b 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -74,7 +74,7 @@ private class AwsClientGenerics(private val types: Types) : FluentClientGenerics override val bounds = writable { } /** Bounds for generated `send()` functions */ - override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Any): Writable = writable { } + override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Writable): Writable = writable { } override fun toGenericsGenerator(): GenericsGenerator { return GenericsGenerator() @@ -98,7 +98,7 @@ class AwsFluentClientDecorator : RustCodegenDecorator { AwsPresignedFluentBuilderMethod(runtimeConfig), AwsFluentClientDocs(codegenContext), ), - retryPolicyType = runtimeConfig.awsHttp().asType().member("retry::AwsErrorRetryPolicy"), + retryPolicy = runtimeConfig.awsHttp().asType().member("retry::AwsErrorRetryPolicy").writable, ).render(rustCrate) rustCrate.withModule(FluentClientGenerator.customizableOperationModule) { writer -> renderCustomizableOperationSendMethod(runtimeConfig, generics, writer) diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt index 67b61e37cd..cdc6a9a123 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypes.kt @@ -42,6 +42,46 @@ sealed class RustType { open val namespace: kotlin.String? = null + /** + * Get a writable for this `RustType` + * + * ```kotlin + * // Declare a RustType + * val t = RustType.Unit.writable + * // Then, invoke the writable directly + * t.invoke(writer) + * // OR template it out + * writer.rustInlineTemplate("#{t:W}", "t" to t) + * ``` + * + * When formatted, the converted type will appear as such: + * + * | Type | Formatted | + * | -------------------------------------------------- | ------------------------------------------------------------------- | + * | RustType.Unit | () | + * | RustType.Bool | bool | + * | RustType.Float(32) | f32 | + * | RustType.Float(64) | f64 | + * | RustType.Integer(8) | i8 | + * | RustType.Integer(16) | i16 | + * | RustType.Integer(32) | i32 | + * | RustType.Integer(64) | i64 | + * | RustType.String | std::string::String | + * | RustType.Vec(RustType.String) | std::vec::Vec | + * | RustType.Slice(RustType.String) | [std::string::String] | + * | RustType.HashMap(RustType.String, RustType.String) | std::collections::HashMap | + * | RustType.HashSet(RustType.String) | std::vec::Vec | + * | RustType.Reference("&", RustType.String) | &std::string::String | + * | RustType.Reference("&mut", RustType.String) | &mut std::string::String | + * | RustType.Reference("&'static", RustType.String) | &'static std::string::String | + * | RustType.Option(RustType.String) | std::option::Option | + * | RustType.Box(RustType.String) | std::boxed::Box | + * | RustType.Opaque("SoCool", "zelda_is") | zelda_is::SoCool | + * | RustType.Opaque("SoCool") | SoCool | + * | RustType.Dyn(RustType.Opaque("Foo", "foo")) | dyn foo::Foo | + */ + val writable = writable { rustInlineTemplate("#{this}", "this" to this@RustType) } + object Unit : RustType() { override val name: kotlin.String = "()" } @@ -186,7 +226,13 @@ fun RustType.render(fullyQualified: Boolean = true): String { is RustType.Slice -> "[${this.member.render(fullyQualified)}]" is RustType.HashMap -> "${this.name}<${this.key.render(fullyQualified)}, ${this.member.render(fullyQualified)}>" is RustType.HashSet -> "${this.name}<${this.member.render(fullyQualified)}>" - is RustType.Reference -> "&${this.lifetime?.let { "'$it" } ?: ""} ${this.member.render(fullyQualified)}" + is RustType.Reference -> { + if (this.lifetime == "&") { + "&${this.member.render(fullyQualified)}" + } else { + "&${this.lifetime?.let { "'$it" } ?: ""} ${this.member.render(fullyQualified)}" + } + } is RustType.Option -> "${this.name}<${this.member.render(fullyQualified)}>" is RustType.Box -> "${this.name}<${this.member.render(fullyQualified)}>" is RustType.Dyn -> "${this.name} ${this.member.render(fullyQualified)}" diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt index ff74001dd4..01a729bfab 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustWriter.kt @@ -544,9 +544,8 @@ class RustWriter private constructor( * Formatter to enable formatting any [writable] with the #W formatter. */ inner class RustWriteableInjector : BiFunction { - @Suppress("UNCHECKED_CAST") override fun apply(t: Any, u: String): String { - val func = t as RustWriter.() -> Unit + val func = t as? RustWriter.() -> Unit ?: throw CodegenException("RustWriteableInjector.apply choked on non-function t ($t)") val innerWriter = RustWriter(filename, namespace, printWarning = false) func(innerWriter) innerWriter.dependencies.forEach { addDependency(it) } diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/Writable.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/Writable.kt index 333b83b0a7..bf2db33e94 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/Writable.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/rustlang/Writable.kt @@ -5,10 +5,10 @@ package software.amazon.smithy.rust.codegen.rustlang +import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.rust.codegen.smithy.RuntimeType import software.amazon.smithy.rust.codegen.smithy.generators.GenericsGenerator -import software.amazon.smithy.rust.codegen.util.PANIC typealias Writable = RustWriter.() -> Unit @@ -56,13 +56,21 @@ fun rustTypeParameters( val iterator: Iterator = typeParameters.iterator() while (iterator.hasNext()) { when (val typeParameter = iterator.next()) { - is Symbol, is RustType.Unit, is RuntimeType -> rustInlineTemplate("#{it}", "it" to typeParameter) + is Symbol, is RuntimeType, is RustType -> rustInlineTemplate("#{it}", "it" to typeParameter) is String -> rustInlineTemplate(typeParameter) is GenericsGenerator -> rustInlineTemplate( "#{gg:W}", "gg" to typeParameter.declaration(withAngleBrackets = false), ) - else -> PANIC("Unhandled type '$typeParameter' encountered by rustTypeParameters writer") + else -> { + // Check if it's a writer. If it is, invoke it; Else, throw a codegen error. + val func = typeParameter as? RustWriter.() -> Unit + if (func != null) { + func.invoke(this) + } else { + throw CodegenException("Unhandled type '$typeParameter' encountered by rustTypeParameters writer") + } + } } if (iterator.hasNext()) { diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt index 96f4bdd020..7b4ca0dfd2 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/RuntimeTypes.kt @@ -21,6 +21,8 @@ import software.amazon.smithy.rust.codegen.rustlang.RustModule import software.amazon.smithy.rust.codegen.rustlang.RustType import software.amazon.smithy.rust.codegen.rustlang.RustWriter import software.amazon.smithy.rust.codegen.rustlang.asType +import software.amazon.smithy.rust.codegen.rustlang.rustInlineTemplate +import software.amazon.smithy.rust.codegen.rustlang.writable import software.amazon.smithy.rust.codegen.util.orNull import java.util.Optional @@ -122,6 +124,11 @@ data class RuntimeConfig( * name, but also ensure that we automatically add any dependencies **as they are used**. */ data class RuntimeType(val name: String?, val dependency: RustDependency?, val namespace: String) { + /** + * Get a writable for this `RuntimeType` + */ + val writable = writable { rustInlineTemplate("#{this:T}", "this" to this@RuntimeType) } + /** * Convert this [RuntimeType] into a [Symbol]. * diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt index bb2062b546..60e70c38bf 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt @@ -48,16 +48,15 @@ class PaginatorGenerator private constructor( service: ServiceShape, operation: OperationShape, private val generics: FluentClientGenerics, - retryPolicyType: Any = RustType.Unit, + retryPolicy: Writable = RustType.Unit.writable, ) { - companion object { fun paginatorType( coreCodegenContext: CoreCodegenContext, generics: FluentClientGenerics, operationShape: OperationShape, - retryPolicyType: Any, - ): RuntimeType? { + retryPolicy: Writable = RustType.Unit.writable, + ): RuntimeType? { return if (operationShape.isPaginated(coreCodegenContext.model)) { PaginatorGenerator( coreCodegenContext.model, @@ -65,7 +64,7 @@ class PaginatorGenerator private constructor( coreCodegenContext.serviceShape, operationShape, generics, - retryPolicyType, + retryPolicy, ).paginatorType() } else { null @@ -99,7 +98,7 @@ class PaginatorGenerator private constructor( "generics" to generics.decl, "bounds" to generics.bounds, "page_size_setter" to pageSizeSetter(), - "send_bounds" to generics.sendBounds(symbolProvider.toSymbol(operation), outputType, errorType, retryPolicyType), + "send_bounds" to generics.sendBounds(symbolProvider.toSymbol(operation), outputType, errorType, retryPolicy), // Operation Types "operation" to symbolProvider.toSymbol(operation), diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt index 1351ecc6fa..e50c67d2fc 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerator.kt @@ -17,6 +17,7 @@ import software.amazon.smithy.rust.codegen.rustlang.RustModule import software.amazon.smithy.rust.codegen.rustlang.RustReservedWords import software.amazon.smithy.rust.codegen.rustlang.RustType import software.amazon.smithy.rust.codegen.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.rustlang.Writable import software.amazon.smithy.rust.codegen.rustlang.asArgumentType import software.amazon.smithy.rust.codegen.rustlang.asOptional import software.amazon.smithy.rust.codegen.rustlang.asType @@ -66,7 +67,7 @@ class FluentClientGenerator( client = CargoDependency.SmithyClient(codegenContext.runtimeConfig).asType(), ), private val customizations: List = emptyList(), - private val retryPolicyType: Any = RustType.Unit, + private val retryPolicy: Writable = RustType.Unit.writable, ) { companion object { fun clientOperationFnName(operationShape: OperationShape, symbolProvider: RustSymbolProvider): String = @@ -321,14 +322,14 @@ class FluentClientGenerator( "OperationOutput" to outputType, "SdkError" to runtimeConfig.smithyHttp().member("result::SdkError"), "SdkSuccess" to runtimeConfig.smithyHttp().member("result::SdkSuccess"), - "send_bounds" to generics.sendBounds(operationSymbol, outputType, errorType, retryPolicyType), + "send_bounds" to generics.sendBounds(operationSymbol, outputType, errorType, retryPolicy), "customizable_op_type_params" to rustTypeParameters( symbolProvider.toSymbol(operation), - retryPolicyType, + retryPolicy, generics.toGenericsGenerator(), ), ) - PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryPolicyType)?.also { paginatorType -> + PaginatorGenerator.paginatorType(codegenContext, generics, operation, retryPolicy)?.also { paginatorType -> rustTemplate( """ /// Create a paginator for this request diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt index eae93f69da..a0c6ffdeea 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/client/FluentClientGenerics.kt @@ -28,7 +28,7 @@ interface FluentClientGenerics { val bounds: Writable /** Bounds for generated `send()` functions */ - fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType, retryPolicy: Any): Writable + fun sendBounds(input: Symbol, output: Symbol, error: RuntimeType, retryPolicy: Writable): Writable /** Convert this `FluentClientGenerics` into the more general `GenericsGenerator` */ fun toGenericsGenerator(): GenericsGenerator @@ -70,7 +70,7 @@ data class FlexibleClientGenerics( } /** Bounds for generated `send()` functions */ - override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Any): Writable = writable { + override fun sendBounds(operation: Symbol, operationOutput: Symbol, operationError: RuntimeType, retryPolicy: Writable): Writable = writable { rustTemplate( """ where @@ -78,7 +78,7 @@ data class FlexibleClientGenerics( #{Operation}, #{OperationOutput}, #{OperationError}, - #{RetryPolicy} + #{RetryPolicy:W} > """, "client" to client, diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt new file mode 100644 index 0000000000..d043ebd48b --- /dev/null +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.rustlang + +import io.kotest.matchers.string.shouldContain +import org.junit.jupiter.api.Test + +internal class RustTypesTest { + private fun forInputExpectOutput(t: Writable, expectedOutput: String) { + val writer = RustWriter.forModule("rust_types") + writer.rustInlineTemplate("'") + t.invoke(writer) + writer.rustInlineTemplate("'") + + writer.toString() shouldContain expectedOutput + } + + @Test + fun `RustType_Unit_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput(RustType.Unit.writable, "'()'") + } + + @Test + fun `RustType_Bool_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput(RustType.Bool.writable, "'bool'") + } + + @Test + fun `RustType_Float_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput(RustType.Float(32).writable, "'f32'") + forInputExpectOutput(RustType.Float(64).writable, "'f64'") + } + + @Test + fun `RustType_Integer_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput(RustType.Integer(8).writable, "'i8'") + forInputExpectOutput(RustType.Integer(16).writable, "'i16'") + forInputExpectOutput(RustType.Integer(32).writable, "'i32'") + forInputExpectOutput(RustType.Integer(64).writable, "'i64'") + } + + @Test + fun `RustType_String_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput(RustType.String.writable, "'std::string::String'") + } + + @Test + fun `RustType_Vec_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Vec(RustType.String).writable, + "'std::vec::Vec'" + ) + } + + @Test + fun `RustType_Slice_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Slice(RustType.String).writable, + "'[std::string::String]'" + ) + } + + @Test + fun `RustType_HashMap_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.HashMap(RustType.String, RustType.String).writable, + "'std::collections::HashMap'" + ) + } + + @Test + fun `RustType_HashSet_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.HashSet(RustType.String).writable, + // Rust doesn't guarantee that `HashSet`s are insertion ordered, so we use a `Vec` instead. + // This is called out in a comment in the RustType.HashSet declaration + "'std::vec::Vec'" + ) + } + + @Test + fun `RustType_Reference_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Reference("&", RustType.String).writable, + "'&std::string::String'" + ) + forInputExpectOutput( + RustType.Reference("&mut", RustType.String).writable, + "'&mut std::string::String'" + ) + forInputExpectOutput( + RustType.Reference("&'static", RustType.String).writable, + "&'static std::string::String'" + ) + } + + @Test + fun `RustType_Option_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Option(RustType.String).writable, + "'std::option::Option'" + ) + } + + @Test + fun `RustType_Box_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Box(RustType.String).writable, + "'std::boxed::Box'" + ) + } + + @Test + fun `RustType_Opaque_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Opaque("SoCool", "zelda_is").writable, + "'zelda_is::SoCool'" + ) + forInputExpectOutput( + RustType.Opaque("SoCool").writable, + "'SoCool'" + ) + } + + @Test + fun `RustType_Dyn_writable produces a template-compatible RuntimeType`() { + forInputExpectOutput( + RustType.Dyn(RustType.Opaque("Foo", "foo")).writable, + "'dyn foo::Foo'" + ) + } +} diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/WritableTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/WritableTest.kt index d6fcc71cba..a96861447c 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/WritableTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/WritableTest.kt @@ -14,7 +14,9 @@ import software.amazon.smithy.rust.codegen.smithy.generators.GenericsGenerator internal class RustTypeParametersTest { private fun forInputExpectOutput(input: Any, expectedOutput: String) { val writer = RustWriter.forModule("model") - writer.rustTemplate("#{typeParameters:W}", "typeParameters" to rustTypeParameters(input)) + writer.rustInlineTemplate("'") + writer.rustInlineTemplate("#{typeParameters:W}", "typeParameters" to rustTypeParameters(input)) + writer.rustInlineTemplate("'") writer.toString() shouldContain expectedOutput } @@ -27,23 +29,23 @@ internal class RustTypeParametersTest { @Test fun `rustTypeParameters accepts Symbol`() { val symbol = RuntimeType("Operation", namespace = "crate::operation", dependency = null).toSymbol() - forInputExpectOutput(symbol, "") + forInputExpectOutput(symbol, "''") } @Test fun `rustTypeParameters accepts RuntimeType`() { val runtimeType = RuntimeType("String", namespace = "std::string", dependency = null) - forInputExpectOutput(runtimeType, "") + forInputExpectOutput(runtimeType, "''") } @Test fun `rustTypeParameters accepts String`() { - forInputExpectOutput("Option>", ">>") + forInputExpectOutput("Option>", "'>>'") } @Test fun `rustTypeParameters accepts GenericsGenerator`() { - forInputExpectOutput(GenericsGenerator(GenericTypeArg("A"), GenericTypeArg("B")), "") + forInputExpectOutput(GenericsGenerator(GenericTypeArg("A"), GenericTypeArg("B")), "''") } @Test @@ -56,8 +58,21 @@ internal class RustTypeParametersTest { "T", GenericsGenerator(GenericTypeArg("A"), GenericTypeArg("B")), ) - writer.rustTemplate("#{typeParameters:W}", "typeParameters" to tps) + writer.rustInlineTemplate("'") + writer.rustInlineTemplate("#{tps:W}", "tps" to tps) + writer.rustInlineTemplate("'") - writer.toString() shouldContain "" + writer.toString() shouldContain "''" + } + + @Test + fun `rustTypeParameters accepts writables`() { + val writer = RustWriter.forModule("model") + val tp = rustTypeParameters(RustType.Unit.writable) + writer.rustInlineTemplate("'") + writer.rustInlineTemplate("#{tp:W}", "tp" to tp) + writer.rustInlineTemplate("'") + + writer.toString() shouldContain "'<()>'" } } From 6a65dc88f550e9060b44cc5641a66e44f585d3b3 Mon Sep 17 00:00:00 2001 From: Zelda Hessler Date: Wed, 7 Sep 2022 11:00:56 -0500 Subject: [PATCH 4/4] format: run formatter --- .../smithy/generators/PaginatorGenerator.kt | 2 +- .../rust/codegen/rustlang/RustTypesTest.kt | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt index 60e70c38bf..727c803eb0 100644 --- a/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt +++ b/codegen/src/main/kotlin/software/amazon/smithy/rust/codegen/smithy/generators/PaginatorGenerator.kt @@ -56,7 +56,7 @@ class PaginatorGenerator private constructor( generics: FluentClientGenerics, operationShape: OperationShape, retryPolicy: Writable = RustType.Unit.writable, - ): RuntimeType? { + ): RuntimeType? { return if (operationShape.isPaginated(coreCodegenContext.model)) { PaginatorGenerator( coreCodegenContext.model, diff --git a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt index d043ebd48b..e699f083e6 100644 --- a/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt +++ b/codegen/src/test/kotlin/software/amazon/smithy/rust/codegen/rustlang/RustTypesTest.kt @@ -51,7 +51,7 @@ internal class RustTypesTest { fun `RustType_Vec_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Vec(RustType.String).writable, - "'std::vec::Vec'" + "'std::vec::Vec'", ) } @@ -59,7 +59,7 @@ internal class RustTypesTest { fun `RustType_Slice_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Slice(RustType.String).writable, - "'[std::string::String]'" + "'[std::string::String]'", ) } @@ -67,7 +67,7 @@ internal class RustTypesTest { fun `RustType_HashMap_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.HashMap(RustType.String, RustType.String).writable, - "'std::collections::HashMap'" + "'std::collections::HashMap'", ) } @@ -77,7 +77,7 @@ internal class RustTypesTest { RustType.HashSet(RustType.String).writable, // Rust doesn't guarantee that `HashSet`s are insertion ordered, so we use a `Vec` instead. // This is called out in a comment in the RustType.HashSet declaration - "'std::vec::Vec'" + "'std::vec::Vec'", ) } @@ -85,15 +85,15 @@ internal class RustTypesTest { fun `RustType_Reference_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Reference("&", RustType.String).writable, - "'&std::string::String'" + "'&std::string::String'", ) forInputExpectOutput( RustType.Reference("&mut", RustType.String).writable, - "'&mut std::string::String'" + "'&mut std::string::String'", ) forInputExpectOutput( RustType.Reference("&'static", RustType.String).writable, - "&'static std::string::String'" + "&'static std::string::String'", ) } @@ -101,7 +101,7 @@ internal class RustTypesTest { fun `RustType_Option_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Option(RustType.String).writable, - "'std::option::Option'" + "'std::option::Option'", ) } @@ -109,7 +109,7 @@ internal class RustTypesTest { fun `RustType_Box_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Box(RustType.String).writable, - "'std::boxed::Box'" + "'std::boxed::Box'", ) } @@ -117,11 +117,11 @@ internal class RustTypesTest { fun `RustType_Opaque_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Opaque("SoCool", "zelda_is").writable, - "'zelda_is::SoCool'" + "'zelda_is::SoCool'", ) forInputExpectOutput( RustType.Opaque("SoCool").writable, - "'SoCool'" + "'SoCool'", ) } @@ -129,7 +129,7 @@ internal class RustTypesTest { fun `RustType_Dyn_writable produces a template-compatible RuntimeType`() { forInputExpectOutput( RustType.Dyn(RustType.Opaque("Foo", "foo")).writable, - "'dyn foo::Foo'" + "'dyn foo::Foo'", ) } }