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/rustlang/Writable.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt index e1b5ee64c28..a0c2c2f2452 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt @@ -30,6 +30,11 @@ fun Writable.map(f: RustWriter.(Writable) -> Unit): Writable { return writable { f(self) } } +/** Returns Some(..arg) */ +fun Writable.some(): Writable { + return this.map { rust("Some(#T)", it) } +} + fun Writable.isNotEmpty(): Boolean = !this.isEmpty() operator fun Writable.plus(other: Writable): Writable { 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..9a952ebe405 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 @@ -32,7 +32,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter import software.amazon.smithy.rust.codegen.core.rustlang.withBlock import software.amazon.smithy.rust.codegen.core.rustlang.writable import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext -import software.amazon.smithy.rust.codegen.core.smithy.Default import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope @@ -41,7 +40,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.canUseDefault import software.amazon.smithy.rust.codegen.core.smithy.customize.NamedCustomization import software.amazon.smithy.rust.codegen.core.smithy.customize.Section import software.amazon.smithy.rust.codegen.core.smithy.customize.writeCustomizations -import software.amazon.smithy.rust.codegen.core.smithy.defaultValue import software.amazon.smithy.rust.codegen.core.smithy.expectRustMetadata import software.amazon.smithy.rust.codegen.core.smithy.isOptional import software.amazon.smithy.rust.codegen.core.smithy.makeOptional @@ -385,15 +383,22 @@ class BuilderGenerator( members.forEach { member -> val memberName = symbolProvider.toMemberName(member) val memberSymbol = symbolProvider.toSymbol(member) - 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) } + val generator = DefaultValueGenerator(runtimeConfig, symbolProvider, model) + val default = generator.defaultValue(member) + if (!memberSymbol.isOptional()) { + if (default != null) { + if (default.isRustDefault) { + rust(".unwrap_or_default()") + } else { + rust(".unwrap_or_else(#T)", default.expr) + } + } else { + withBlock( + ".ok_or_else(||", + ")?", + ) { missingRequiredField(memberName) } + } } } } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/DefaultValueGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/DefaultValueGenerator.kt new file mode 100644 index 00000000000..38001bf102e --- /dev/null +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/DefaultValueGenerator.kt @@ -0,0 +1,40 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.rust.codegen.core.smithy.generators + +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.SimpleShape +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.Default +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig +import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider +import software.amazon.smithy.rust.codegen.core.smithy.defaultValue + +class DefaultValueGenerator( + runtimeConfig: RuntimeConfig, + private val symbolProvider: RustSymbolProvider, + private val model: Model, +) { + private val instantiator = PrimitiveInstantiator(runtimeConfig, symbolProvider) + + data class DefaultValue(val isRustDefault: Boolean, val expr: Writable) + + /** Returns the default value as set by the defaultValue trait */ + fun defaultValue(member: MemberShape): DefaultValue? { + val target = model.expectShape(member.target) + return when (val default = symbolProvider.toSymbol(member).defaultValue()) { + is Default.NoDefault -> null + is Default.RustDefault -> DefaultValue(isRustDefault = true, writable("Default::default")) + is Default.NonZeroDefault -> { + val instantiation = instantiator.instantiate(target as SimpleShape, default.value) + DefaultValue(isRustDefault = false, writable { rust("||#T", instantiation) }) + } + } + } +} diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt index a84820ad263..b2732c9d5c8 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt @@ -232,6 +232,20 @@ open class EnumGenerator( }, ) + // Add an infallible FromStr implementation for uniformity + rustTemplate( + """ + impl ::std::str::FromStr for ${context.enumName} { + type Err = ::std::convert::Infallible; + + fn from_str(s: &str) -> #{Result}::Err> { + #{Ok}(${context.enumName}::from(s)) + } + } + """, + *preludeScope, + ) + rustTemplate( """ impl #{From} for ${context.enumName} where T: #{AsRef} { @@ -239,6 +253,7 @@ open class EnumGenerator( ${context.enumName}(s.as_ref().to_owned()) } } + """, *preludeScope, ) 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..e9eeefc3d2b 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,12 +18,14 @@ 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 @@ -84,11 +86,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 +128,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)(writer) else -> writer.writeWithNoFormatting("todo!() /* $shape $data */") } @@ -214,7 +154,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 +182,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 +262,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 +357,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.hasTrait() || shape is EnumShape) { + val enumSymbol = symbolProvider.toSymbol(shape) + rust("$data.parse::<#T>().unwrap()", enumSymbol) + } else { + rust("$data.to_owned()") + } + } +} 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/BuilderGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGeneratorTest.kt index fcec11601ec..2cce4515845 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,17 +7,23 @@ 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.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.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.WrappingSymbolProvider import software.amazon.smithy.rust.codegen.core.smithy.setDefault +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 import software.amazon.smithy.rust.codegen.core.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.testutil.unitTest +import software.amazon.smithy.rust.codegen.core.util.lookup internal class BuilderGeneratorTest { private val model = StructureGeneratorTest.model @@ -140,4 +146,69 @@ internal class BuilderGeneratorTest { } project.compileAndTest() } + + @Test + fun `it supports nonzero defaults`() { + 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 = StructureGeneratorTest.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) + unitTest("test_defaults") { + rustTemplate( + """ + 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-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-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)), )