-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for error-correcting builders
- Loading branch information
Showing
6 changed files
with
251 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
...in/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ErrorCorrection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package software.amazon.smithy.rust.codegen.client.smithy.generators | ||
|
||
import software.amazon.smithy.model.node.Node | ||
import software.amazon.smithy.model.shapes.BlobShape | ||
import software.amazon.smithy.model.shapes.BooleanShape | ||
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.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.rust.codegen.client.smithy.ClientCodegenContext | ||
import software.amazon.smithy.rust.codegen.core.rustlang.RustModule | ||
import software.amazon.smithy.rust.codegen.core.rustlang.Writable | ||
import software.amazon.smithy.rust.codegen.core.rustlang.isEmpty | ||
import software.amazon.smithy.rust.codegen.core.rustlang.map | ||
import software.amazon.smithy.rust.codegen.core.rustlang.plus | ||
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.some | ||
import software.amazon.smithy.rust.codegen.core.rustlang.writable | ||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType | ||
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.preludeScope | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.BuilderGenerator | ||
import software.amazon.smithy.rust.codegen.core.smithy.generators.PrimitiveInstantiator | ||
import software.amazon.smithy.rust.codegen.core.smithy.isRustBoxed | ||
import software.amazon.smithy.rust.codegen.core.smithy.protocols.shapeFunctionName | ||
import software.amazon.smithy.rust.codegen.core.util.isEventStream | ||
import software.amazon.smithy.rust.codegen.core.util.isStreaming | ||
import software.amazon.smithy.rust.codegen.core.util.letIf | ||
|
||
/** | ||
* For AWS-services, the spec defines error correction semantics to recover from missing default values for required members: | ||
* https://smithy.io/2.0/spec/aggregate-types.html?highlight=error%20correction#client-error-correction | ||
*/ | ||
|
||
private fun ClientCodegenContext.errorCorrectedDefault(member: MemberShape): Writable? { | ||
if (!member.isRequired) { | ||
return null | ||
} | ||
symbolProvider.toSymbol(member) | ||
val target = model.expectShape(member.target) | ||
val memberSymbol = symbolProvider.toSymbol(member) | ||
val targetSymbol = symbolProvider.toSymbol(target) | ||
if (member.isEventStream(model) || member.isStreaming(model)) { | ||
return null | ||
} | ||
val instantiator = PrimitiveInstantiator(runtimeConfig, symbolProvider) | ||
return writable { | ||
when (target) { | ||
is EnumShape -> rustTemplate(""""no value was set".parse::<#{Shape}>().ok()""", "Shape" to targetSymbol) | ||
is BooleanShape, is NumberShape, is StringShape, is DocumentShape, is ListShape, is MapShape -> rust("Some(Default::default())") | ||
is StructureShape -> rustTemplate( | ||
"{ let builder = #{Builder}::default(); #{instantiate} }", | ||
"Builder" to symbolProvider.symbolForBuilder(target), | ||
"instantiate" to builderInstantiator().finalizeBuilder("builder", target).map { | ||
if (BuilderGenerator.hasFallibleBuilder(target, symbolProvider)) { | ||
rust("#T.ok()", it) | ||
} else { | ||
it.some()(this) | ||
} | ||
}.letIf(memberSymbol.isRustBoxed()) { | ||
it.plus { rustTemplate(".map(#{Box}::new)", *preludeScope) } | ||
}, | ||
) | ||
|
||
is TimestampShape -> instantiator.instantiate(target, Node.from(0)).some()(this) | ||
is BlobShape -> instantiator.instantiate(target, Node.from("")).some()(this) | ||
is UnionShape -> rust("Some(#T::Unknown)", targetSymbol) | ||
} | ||
} | ||
} | ||
|
||
fun ClientCodegenContext.correctErrors(shape: StructureShape): RuntimeType? { | ||
val name = symbolProvider.shapeFunctionName(serviceShape, shape) + "_correct_errors" | ||
val corrections = writable { | ||
shape.members().forEach { member -> | ||
val memberName = symbolProvider.toMemberName(member) | ||
errorCorrectedDefault(member)?.also { default -> | ||
rustTemplate( | ||
"""if builder.$memberName.is_none() { builder.$memberName = #{default} }""", | ||
"default" to default, | ||
) | ||
} | ||
} | ||
} | ||
|
||
if (corrections.isEmpty()) { | ||
return null | ||
} | ||
|
||
return RuntimeType.forInlineFun(name, RustModule.private("serde_util")) { | ||
rustTemplate( | ||
""" | ||
pub(crate) fn $name(mut builder: #{Builder}) -> #{Builder} { | ||
#{corrections} | ||
builder | ||
} | ||
""", | ||
"Builder" to symbolProvider.symbolForBuilder(shape), | ||
"corrections" to corrections, | ||
) | ||
} | ||
} |
116 changes: 116 additions & 0 deletions
116
...otlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ErrorCorrectionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package software.amazon.smithy.rust.codegen.client.smithy.generators | ||
|
||
import org.junit.jupiter.api.Test | ||
import software.amazon.smithy.model.shapes.StructureShape | ||
import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest | ||
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate | ||
import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel | ||
import software.amazon.smithy.rust.codegen.core.testutil.unitTest | ||
import software.amazon.smithy.rust.codegen.core.util.lookup | ||
|
||
class ErrorCorrectionTest { | ||
private val model = """ | ||
namespace com.example | ||
use aws.protocols#awsJson1_0 | ||
@awsJson1_0 | ||
service HelloService { | ||
operations: [SayHello], | ||
version: "1" | ||
} | ||
operation SayHello { input: TestInput } | ||
structure TestInput { nested: TestStruct } | ||
structure TestStruct { | ||
@required | ||
foo: String, | ||
@required | ||
byteValue: Byte, | ||
@required | ||
listValue: StringList, | ||
@required | ||
mapValue: ListMap, | ||
@required | ||
doubleListValue: DoubleList | ||
@required | ||
document: Document | ||
@required | ||
nested: Nested | ||
@required | ||
blob: Blob | ||
@required | ||
enum: Enum | ||
@required | ||
union: U | ||
notRequired: String | ||
} | ||
enum Enum { | ||
A, | ||
B, | ||
C | ||
} | ||
union U { | ||
A: Integer, | ||
B: String, | ||
C: Unit | ||
} | ||
structure Nested { | ||
@required | ||
a: String | ||
} | ||
list StringList { | ||
member: String | ||
} | ||
list DoubleList { | ||
member: StringList | ||
} | ||
map ListMap { | ||
key: String, | ||
value: StringList | ||
} | ||
""".asSmithyModel(smithyVersion = "2.0") | ||
|
||
@Test | ||
fun correctMissingFields() { | ||
val shape = model.lookup<StructureShape>("com.example#TestStruct") | ||
clientIntegrationTest(model) { ctx, crate -> | ||
crate.lib { | ||
val codegenCtx = | ||
arrayOf("correct_errors" to ctx.correctErrors(shape), "Shape" to ctx.symbolProvider.toSymbol(shape)) | ||
rustTemplate( | ||
""" | ||
/// docs | ||
pub fn use_fn_publicly() { #{correct_errors}(#{Shape}::builder()); } """, | ||
*codegenCtx, | ||
) | ||
unitTest("test_default_builder") { | ||
rustTemplate( | ||
""" | ||
let builder = #{correct_errors}(#{Shape}::builder().foo("abcd")); | ||
let shape = builder.build(); | ||
// don't override a field already set | ||
assert_eq!(shape.foo(), Some("abcd")); | ||
// set nested fields | ||
assert_eq!(shape.nested().unwrap().a(), Some("")); | ||
// don't default non-required fields | ||
assert_eq!(shape.not_required(), None); | ||
assert_eq!(shape.blob().unwrap().as_ref(), &[]); | ||
""", | ||
*codegenCtx, | ||
|
||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters