From 841afc2953dcf36f2dcbe3340d1802576f6ab740 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Tue, 12 Sep 2023 15:36:50 -0400 Subject: [PATCH] Use the default(..) trait to source default information --- .../smithy/endpoint/EndpointTypesGenerator.kt | 2 - .../smithy/generators/ClientInstantiator.kt | 8 - .../rust/codegen/core/smithy/SymbolExt.kt | 3 + .../rust/codegen/core/smithy/SymbolVisitor.kt | 36 +++- .../smithy/generators/BuilderGenerator.kt | 20 ++- .../core/smithy/generators/Instantiator.kt | 170 ++++++++++-------- .../rust/codegen/core/testutil/TestHelpers.kt | 3 +- .../smithy/generators/InstantiatorTest.kt | 17 +- .../generators/StructureGeneratorTest.kt | 76 ++++++++ .../smithy/generators/ServerInstantiator.kt | 14 -- 10 files changed, 220 insertions(+), 129 deletions(-) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt index 889b741ebbc..8a1de0151dc 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointTypesGenerator.kt @@ -27,7 +27,6 @@ class EndpointTypesGenerator( val tests: List, ) { val params: Parameters = rules?.parameters ?: Parameters.builder().build() - private val runtimeConfig = codegenContext.runtimeConfig private val customizations = codegenContext.rootDecorator.endpointCustomizations(codegenContext) private val stdlib = customizations .flatMap { it.customRuntimeFunctions(codegenContext) } @@ -41,7 +40,6 @@ class EndpointTypesGenerator( } fun paramsStruct(): RuntimeType = EndpointParamsGenerator(codegenContext, params).paramsStruct() - fun paramsBuilder(): RuntimeType = EndpointParamsGenerator(codegenContext, params).paramsBuilder() fun defaultResolver(): RuntimeType? = rules?.let { EndpointResolverGenerator(codegenContext, stdlib).defaultEndpointResolver(it) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiator.kt index a065fe3b96d..ca39264a1c0 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ClientInstantiator.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators -import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.node.ObjectNode import software.amazon.smithy.model.shapes.MemberShape @@ -14,18 +13,12 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter -import software.amazon.smithy.rust.codegen.core.rustlang.Writable import software.amazon.smithy.rust.codegen.core.rustlang.rust -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.generators.BuilderGenerator import software.amazon.smithy.rust.codegen.core.smithy.generators.Instantiator import software.amazon.smithy.rust.codegen.core.smithy.generators.setterName -private fun enumFromStringFn(enumSymbol: Symbol, data: String): Writable = writable { - rust("#T::from($data)", enumSymbol) -} - class ClientBuilderKindBehavior(val codegenContext: CodegenContext) : Instantiator.BuilderKindBehavior { override fun hasFallibleBuilder(shape: StructureShape): Boolean = BuilderGenerator.hasFallibleBuilder(shape, codegenContext.symbolProvider) @@ -40,7 +33,6 @@ class ClientInstantiator(private val codegenContext: ClientCodegenContext) : Ins codegenContext.model, codegenContext.runtimeConfig, ClientBuilderKindBehavior(codegenContext), - ::enumFromStringFn, ) { fun renderFluentCall( writer: RustWriter, diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolExt.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolExt.kt index 3b9307ab7ee..68f4c157645 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolExt.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolExt.kt @@ -6,6 +6,7 @@ package software.amazon.smithy.rust.codegen.core.smithy import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.rust.codegen.core.rustlang.RustModule import software.amazon.smithy.rust.codegen.core.rustlang.RustType @@ -102,6 +103,8 @@ sealed class Default { * This symbol should use the Rust `std::default::Default` when unset */ object RustDefault : Default() + + data class NonZeroDefault(val value: Node) : Default() } /** diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt index b2426c602c0..9677cd7f0af 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolVisitor.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.codegen.core.SymbolProvider import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.NullableIndex import software.amazon.smithy.model.knowledge.NullableIndex.CheckMode +import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.shapes.BigDecimalShape import software.amazon.smithy.model.shapes.BigIntegerShape import software.amazon.smithy.model.shapes.BlobShape @@ -37,6 +38,7 @@ import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape +import software.amazon.smithy.model.traits.DefaultTrait import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.rust.codegen.core.rustlang.Attribute @@ -48,6 +50,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.traits.RustBoxTrait import software.amazon.smithy.rust.codegen.core.util.PANIC 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.orNull import software.amazon.smithy.rust.codegen.core.util.toPascalCase import software.amazon.smithy.rust.codegen.core.util.toSnakeCase import kotlin.reflect.KClass @@ -79,16 +82,18 @@ data class MaybeRenamed(val name: String, val renamedFrom: String?) /** * Make the return [value] optional if the [member] symbol is as well optional. */ -fun SymbolProvider.wrapOptional(member: MemberShape, value: String): String = value.letIf(toSymbol(member).isOptional()) { - "Some($value)" -} +fun SymbolProvider.wrapOptional(member: MemberShape, value: String): String = + value.letIf(toSymbol(member).isOptional()) { + "Some($value)" + } /** * Make the return [value] optional if the [member] symbol is not optional. */ -fun SymbolProvider.toOptional(member: MemberShape, value: String): String = value.letIf(!toSymbol(member).isOptional()) { - "Some($value)" -} +fun SymbolProvider.toOptional(member: MemberShape, value: String): String = + value.letIf(!toSymbol(member).isOptional()) { + "Some($value)" + } /** * Services can rename their contained shapes. See https://awslabs.github.io/smithy/1.0/spec/core/model.html#service @@ -170,7 +175,7 @@ open class SymbolVisitor( } private fun simpleShape(shape: SimpleShape): Symbol { - return symbolBuilder(shape, SimpleShapes.getValue(shape::class)).setDefault(Default.RustDefault).build() + return symbolBuilder(shape, SimpleShapes.getValue(shape::class)).build() } override fun booleanShape(shape: BooleanShape): Symbol = simpleShape(shape) @@ -263,13 +268,21 @@ open class SymbolVisitor( override fun memberShape(shape: MemberShape): Symbol { val target = model.expectShape(shape.target) + val defaultValue = shape.getMemberTrait(model, DefaultTrait::class.java).orNull()?.let { trait -> + when (val value = trait.toNode()) { + Node.from(""), Node.from(0), Node.from(false), Node.arrayNode(), Node.objectNode() -> Default.RustDefault + Node.nullNode() -> Default.NoDefault + else -> { Default.NonZeroDefault(value) + } + } + } ?: Default.NoDefault // Handle boxing first, so we end up with Option>, not Box>. return handleOptionality( handleRustBoxing(toSymbol(target), shape), shape, nullableIndex, config.nullabilityCheckMode, - ) + ).toBuilder().setDefault(defaultValue).build() } override fun timestampShape(shape: TimestampShape?): Symbol { @@ -297,7 +310,12 @@ fun symbolBuilder(shape: Shape?, rustType: RustType): Symbol.Builder = // If we ever generate a `thisisabug.rs`, there is a bug in our symbol generation .definitionFile("thisisabug.rs") -fun handleOptionality(symbol: Symbol, member: MemberShape, nullableIndex: NullableIndex, nullabilityCheckMode: CheckMode): Symbol = +fun handleOptionality( + symbol: Symbol, + member: MemberShape, + nullableIndex: NullableIndex, + nullabilityCheckMode: CheckMode, +): Symbol = symbol.letIf(nullableIndex.isMemberNullable(member, nullabilityCheckMode)) { symbol.makeOptional() } /** 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 9697cc624f7..09f7849ca2f 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.SimpleShape import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Attribute @@ -52,6 +53,7 @@ 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 +import java.util.logging.Logger // TODO(https://github.com/awslabs/smithy-rs/issues/1401) This builder generator is only used by the client. // Move this entire file, and its tests, to `codegen-client`. @@ -385,15 +387,25 @@ class BuilderGenerator( members.forEach { member -> val memberName = symbolProvider.toMemberName(member) val memberSymbol = symbolProvider.toSymbol(member) + val target = model.expectShape(member.target) val default = memberSymbol.defaultValue() withBlock("$memberName: self.$memberName", ",") { // Write the modifier when { !memberSymbol.isOptional() && default == Default.RustDefault -> rust(".unwrap_or_default()") - !memberSymbol.isOptional() -> withBlock( - ".ok_or_else(||", - ")?", - ) { missingRequiredField(memberName) } + !memberSymbol.isOptional() && default is Default.NonZeroDefault -> { + val instantiation = PrimitiveInstantiator(runtimeConfig, symbolProvider).instantiate(target as SimpleShape, default.value) + rust(".unwrap_or_else(||#T)", instantiation) + } + !memberSymbol.isOptional() -> { + if (default is Default.NonZeroDefault) { + Logger.getLogger("BuilderGenerator").warning("Shape had a non zero default. Non-zero defaults are not currently supported") + } + withBlock( + ".ok_or_else(||", + ")?", + ) { missingRequiredField(memberName) } + } } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/Instantiator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/Instantiator.kt index eed196e4891..ff47e5f5d8e 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/Instantiator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/Instantiator.kt @@ -6,7 +6,7 @@ package software.amazon.smithy.rust.codegen.core.smithy.generators import software.amazon.smithy.codegen.core.CodegenException -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.node.ArrayNode import software.amazon.smithy.model.node.Node @@ -18,17 +18,18 @@ import software.amazon.smithy.model.shapes.BlobShape import software.amazon.smithy.model.shapes.BooleanShape import software.amazon.smithy.model.shapes.CollectionShape import software.amazon.smithy.model.shapes.DocumentShape +import software.amazon.smithy.model.shapes.EnumShape import software.amazon.smithy.model.shapes.ListShape import software.amazon.smithy.model.shapes.MapShape import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.NumberShape import software.amazon.smithy.model.shapes.SetShape import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.SimpleShape import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape -import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.model.traits.HttpPayloadTrait import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait @@ -84,11 +85,6 @@ open class Instantiator( private val runtimeConfig: RuntimeConfig, /** Behavior of the builder type used for structure shapes. */ private val builderKindBehavior: BuilderKindBehavior, - /** - * A function that given a symbol for an enum shape and a string, returns a writable to instantiate the enum with - * the string value. - **/ - private val enumFromStringFn: (Symbol, String) -> Writable, /** Fill out required fields with a default value. **/ private val defaultsForRequiredFields: Boolean = false, private val customizations: List = listOf(), @@ -131,64 +127,7 @@ open class Instantiator( // Members, supporting potentially optional members is MemberShape -> renderMember(writer, shape, data, ctx) - // Wrapped Shapes - is TimestampShape -> { - val node = (data as NumberNode) - val num = BigDecimal(node.toString()) - val wholePart = num.toInt() - val fractionalPart = num.remainder(BigDecimal.ONE) - writer.rust( - "#T::from_fractional_secs($wholePart, ${fractionalPart}_f64)", - RuntimeType.dateTime(runtimeConfig), - ) - } - - /** - * ```rust - * Blob::new("arg") - * ``` - */ - is BlobShape -> if (shape.hasTrait()) { - writer.rust( - "#T::from_static(b${(data as StringNode).value.dq()})", - RuntimeType.byteStream(runtimeConfig), - ) - } else { - writer.rust( - "#T::new(${(data as StringNode).value.dq()})", - RuntimeType.blob(runtimeConfig), - ) - } - - // Simple Shapes - is StringShape -> renderString(writer, shape, data as StringNode) - is NumberShape -> when (data) { - is StringNode -> { - val numberSymbol = symbolProvider.toSymbol(shape) - // support Smithy custom values, such as Infinity - writer.rust( - """<#T as #T>::parse_smithy_primitive(${data.value.dq()}).expect("invalid string for number")""", - numberSymbol, - RuntimeType.smithyTypes(runtimeConfig).resolve("primitive::Parse"), - ) - } - - is NumberNode -> writer.write(data.value) - } - - is BooleanShape -> writer.rust(data.asBooleanNode().get().toString()) - is DocumentShape -> writer.rustBlock("") { - val smithyJson = CargoDependency.smithyJson(runtimeConfig).toType() - rustTemplate( - """ - let json_bytes = br##"${Node.prettyPrintJson(data)}"##; - let mut tokens = #{json_token_iter}(json_bytes).peekable(); - #{expect_document}(&mut tokens).expect("well formed json") - """, - "expect_document" to smithyJson.resolve("deserialize::token::expect_document"), - "json_token_iter" to smithyJson.resolve("deserialize::json_token_iter"), - ) - } + is SimpleShape -> PrimitiveInstantiator(runtimeConfig, symbolProvider).instantiate(shape, data) else -> writer.writeWithNoFormatting("todo!() /* $shape $data */") } @@ -214,7 +153,11 @@ open class Instantiator( ")", // The conditions are not commutative: note client builders always take in `Option`. conditional = symbol.isOptional() || - (model.expectShape(memberShape.container) is StructureShape && builderKindBehavior.doesSetterTakeInOption(memberShape)), + ( + model.expectShape(memberShape.container) is StructureShape && builderKindBehavior.doesSetterTakeInOption( + memberShape, + ) + ), *preludeScope, ) { writer.conditionalBlockTemplate( @@ -238,7 +181,8 @@ open class Instantiator( } } - private fun renderSet(writer: RustWriter, shape: SetShape, data: ArrayNode, ctx: Ctx) = renderList(writer, shape, data, ctx) + private fun renderSet(writer: RustWriter, shape: SetShape, data: ArrayNode, ctx: Ctx) = + renderList(writer, shape, data, ctx) /** * ```rust @@ -317,22 +261,18 @@ open class Instantiator( } } - private fun renderString(writer: RustWriter, shape: StringShape, arg: StringNode) { - val data = writer.escape(arg.value).dq() - if (!shape.hasTrait()) { - writer.rust("$data.to_owned()") - } else { - val enumSymbol = symbolProvider.toSymbol(shape) - writer.rustTemplate("#{EnumFromStringFn:W}", "EnumFromStringFn" to enumFromStringFn(enumSymbol, data)) - } - } - /** * ```rust * MyStruct::builder().field_1("hello").field_2(5).build() * ``` */ - private fun renderStructure(writer: RustWriter, shape: StructureShape, data: ObjectNode, headers: Map, ctx: Ctx) { + private fun renderStructure( + writer: RustWriter, + shape: StructureShape, + data: ObjectNode, + headers: Map, + ctx: Ctx, + ) { writer.rust("#T::builder()", symbolProvider.toSymbol(shape)) renderStructureMembers(writer, shape, data, headers, ctx) @@ -416,3 +356,77 @@ open class Instantiator( else -> throw CodegenException("Unrecognized shape `$shape`") } } + +class PrimitiveInstantiator(private val runtimeConfig: RuntimeConfig, private val symbolProvider: SymbolProvider) { + fun instantiate(shape: SimpleShape, data: Node): Writable = writable { + when (shape) { + // Simple Shapes + is TimestampShape -> { + val node = (data as NumberNode) + val num = BigDecimal(node.toString()) + val wholePart = num.toInt() + val fractionalPart = num.remainder(BigDecimal.ONE) + rust( + "#T::from_fractional_secs($wholePart, ${fractionalPart}_f64)", + RuntimeType.dateTime(runtimeConfig), + ) + } + + /** + * ```rust + * Blob::new("arg") + * ``` + */ + is BlobShape -> if (shape.hasTrait()) { + rust( + "#T::from_static(b${(data as StringNode).value.dq()})", + RuntimeType.byteStream(runtimeConfig), + ) + } else { + rust( + "#T::new(${(data as StringNode).value.dq()})", + RuntimeType.blob(runtimeConfig), + ) + } + + is StringShape -> renderString(shape, data as StringNode)(this) + is NumberShape -> when (data) { + is StringNode -> { + val numberSymbol = symbolProvider.toSymbol(shape) + // support Smithy custom values, such as Infinity + rust( + """<#T as #T>::parse_smithy_primitive(${data.value.dq()}).expect("invalid string for number")""", + numberSymbol, + RuntimeType.smithyTypes(runtimeConfig).resolve("primitive::Parse"), + ) + } + + is NumberNode -> write(data.value) + } + + is BooleanShape -> rust(data.asBooleanNode().get().toString()) + is DocumentShape -> rustBlock("") { + val smithyJson = CargoDependency.smithyJson(runtimeConfig).toType() + rustTemplate( + """ + let json_bytes = br##"${Node.prettyPrintJson(data)}"##; + let mut tokens = #{json_token_iter}(json_bytes).peekable(); + #{expect_document}(&mut tokens).expect("well formed json") + """, + "expect_document" to smithyJson.resolve("deserialize::token::expect_document"), + "json_token_iter" to smithyJson.resolve("deserialize::json_token_iter"), + ) + } + } + } + + private fun renderString(shape: StringShape, arg: StringNode): Writable = { + val data = escape(arg.value).dq() + if (shape !is EnumShape) { + rust("$data.to_owned()") + } else { + val enumSymbol = symbolProvider.toSymbol(shape) + rust("$data.parse::<#T>().unwrap()", enumSymbol) + } + } +} 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 b80f211c766..3ab9f9fd22e 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 @@ -142,11 +142,12 @@ fun String.asSmithyModel(sourceLocation: String? = null, smithyVersion: String = internal fun testSymbolProvider( model: Model, rustReservedWordConfig: RustReservedWordConfig? = null, + config: RustSymbolProviderConfig = TestRustSymbolProviderConfig, ): RustSymbolProvider = SymbolVisitor( testRustSettings(), model, ServiceShape.builder().version("test").id("test#Service").build(), - TestRustSymbolProviderConfig, + config, ).let { BaseSymbolMetadataProvider(it, additionalAttributes = listOf(Attribute.NonExhaustive)) } .let { RustReservedWordSymbolProvider( diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt index 5232fb1df21..aa89acbb2bf 100644 --- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt +++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/InstantiatorTest.kt @@ -6,7 +6,6 @@ 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.node.Node import software.amazon.smithy.model.node.NumberNode import software.amazon.smithy.model.node.StringNode @@ -19,7 +18,6 @@ import software.amazon.smithy.model.shapes.UnionShape 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.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.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer @@ -102,14 +100,11 @@ class InstantiatorTest { override fun doesSetterTakeInOption(memberShape: MemberShape) = true } - // This can be empty since the actual behavior is tested in `ClientInstantiatorTest` and `ServerInstantiatorTest`. - private fun enumFromStringFn(symbol: Symbol, data: String) = writable { } - @Test fun `generate unions`() { val union = model.lookup("com.test#MyUnion") val sut = - Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext), ::enumFromStringFn) + Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext)) val data = Node.parse("""{ "stringVariant": "ok!" }""") val project = TestWorkspace.testProject(model) @@ -129,7 +124,7 @@ class InstantiatorTest { fun `generate struct builders`() { val structure = model.lookup("com.test#MyStruct") val sut = - Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext), ::enumFromStringFn) + Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext)) val data = Node.parse("""{ "bar": 10, "foo": "hello" }""") val project = TestWorkspace.testProject(model) @@ -154,7 +149,7 @@ class InstantiatorTest { fun `generate builders for boxed structs`() { val structure = model.lookup("com.test#WithBox") val sut = - Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext), ::enumFromStringFn) + Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext)) val data = Node.parse( """ { @@ -193,7 +188,7 @@ class InstantiatorTest { fun `generate lists`() { val data = Node.parse("""["bar", "foo"]""") val sut = - Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext), ::enumFromStringFn) + Instantiator(symbolProvider, model, runtimeConfig, BuilderKindBehavior(codegenContext)) val project = TestWorkspace.testProject() project.lib { @@ -214,7 +209,6 @@ class InstantiatorTest { model, runtimeConfig, BuilderKindBehavior(codegenContext), - ::enumFromStringFn, ) val project = TestWorkspace.testProject(model) @@ -245,7 +239,6 @@ class InstantiatorTest { model, runtimeConfig, BuilderKindBehavior(codegenContext), - ::enumFromStringFn, ) val inner = model.lookup("com.test#Inner") @@ -278,7 +271,6 @@ class InstantiatorTest { model, runtimeConfig, BuilderKindBehavior(codegenContext), - ::enumFromStringFn, ) val project = TestWorkspace.testProject(model) @@ -306,7 +298,6 @@ class InstantiatorTest { model, runtimeConfig, BuilderKindBehavior(codegenContext), - ::enumFromStringFn, ) val project = TestWorkspace.testProject(model) project.testModule { 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 77932ddfac6..d4278987921 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.knowledge.NullableIndex 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,7 +16,9 @@ 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.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.transformers.RecursiveShapeBoxer +import software.amazon.smithy.rust.codegen.core.testutil.TestRustSymbolProviderConfig import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest @@ -435,4 +438,77 @@ class StructureGeneratorTest { writer.toString().shouldNotContain("#[doc(hidden)]") } } + + @Test + fun `it supports nonzero defaults`() { + // TODO + val model = """ + ${"$"}version: "2.0" + namespace com.test + structure MyStruct { + @default(0) + @required + zeroDefault: Integer + + @required + @default(1) + oneDefault: OneDefault + + @required + @default("") + defaultEmpty: String + + @required + @default("some-value") + defaultValue: String + + @required + anActuallyRequiredField: Integer + + @required + @default([]) + emptyList: StringList + + noDefault: String + + @default(true) + @required + defaultDocument: Document + } + + list StringList { + member: String + } + + @default(1) + integer OneDefault + """.asSmithyModel() + + val provider = testSymbolProvider(model, rustReservedWordConfig = rustReservedWordConfig, config = TestRustSymbolProviderConfig.copy(nullabilityCheckMode = NullableIndex.CheckMode.CLIENT_CAREFUL)) + val project = TestWorkspace.testProject(provider) + val shape: StructureShape = model.lookup("com.test#MyStruct") + project.useShapeWriter(shape) { + StructureGenerator(model, provider, this, shape, listOf()).render() + BuilderGenerator(model, provider, shape, listOf()).render(this) + rustTemplate( + """ + ##[test] + fn test_defaults() { + let s = Builder::default().an_actually_required_field(5).build().unwrap(); + assert_eq!(s.zero_default(), 0); + assert_eq!(s.default_empty(), ""); + assert_eq!(s.default_value(), "some-value"); + assert_eq!(s.one_default(), 1); + assert!(s.empty_list().is_empty()); + assert_eq!(s.an_actually_required_field(), 5); + assert_eq!(s.no_default(), None); + assert_eq!(s.default_document().as_bool().unwrap(), true); + } + + """, + "Struct" to provider.toSymbol(shape), + ) + } + project.compileAndTest() + } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt index 3d3105f9c70..09cc9cdc713 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerInstantiator.kt @@ -5,7 +5,6 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators -import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.rust.codegen.core.rustlang.Writable @@ -20,18 +19,6 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext import software.amazon.smithy.rust.codegen.server.smithy.isDirectlyConstrained import software.amazon.smithy.rust.codegen.server.smithy.traits.isReachableFromOperationInput -/** - * Server enums do not have an `Unknown` variant like client enums do, so constructing an enum from - * a string is a fallible operation (hence `try_from`). It's ok to panic here if construction fails, - * since this is only used in protocol tests. - */ -private fun enumFromStringFn(enumSymbol: Symbol, data: String): Writable = writable { - rust( - """#T::try_from($data).expect("this is only used in tests")""", - enumSymbol, - ) -} - class ServerAfterInstantiatingValueConstrainItIfNecessary(val codegenContext: CodegenContext) : InstantiatorCustomization() { @@ -82,7 +69,6 @@ fun serverInstantiator(codegenContext: CodegenContext) = codegenContext.model, codegenContext.runtimeConfig, ServerBuilderKindBehavior(codegenContext), - ::enumFromStringFn, defaultsForRequiredFields = true, customizations = listOf(ServerAfterInstantiatingValueConstrainItIfNecessary(codegenContext)), )