Skip to content

Commit 8b5096f

Browse files
authored
fix: encoding of lists of maps of lists which fixes ssm service (#349)
1 parent a9160be commit 8b5096f

File tree

4 files changed

+228
-2
lines changed

4 files changed

+228
-2
lines changed

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/json/MemberShapeEncodeGenerator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ abstract class MemberShapeEncodeGenerator(
164164
when (targetShape) {
165165
is CollectionShape -> {
166166
val topLevelContainerName = "${memberName}Container"
167-
writer.write("var \$L = $containerName.nestedContainer(keyedBy: \$N.self)", topLevelContainerName, ClientRuntimeTypes.Serde.Key)
168-
renderEncodeMap(ctx, memberName, topLevelContainerName, targetShape, level)
167+
writer.write("var \$L = $containerName.nestedUnkeyedContainer(forKey: \$N($dictKey${level - 1}))", topLevelContainerName, ClientRuntimeTypes.Serde.Key)
168+
renderEncodeList(ctx, memberName, topLevelContainerName, targetShape, level)
169169
}
170170
is MapShape -> {
171171
val topLevelContainerName = "${memberName}Container"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
package mocks
6+
7+
import TestHttpProtocolClientGeneratorFactory
8+
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait
9+
import software.amazon.smithy.model.pattern.UriPattern
10+
import software.amazon.smithy.model.shapes.MemberShape
11+
import software.amazon.smithy.model.shapes.OperationShape
12+
import software.amazon.smithy.model.shapes.Shape
13+
import software.amazon.smithy.model.shapes.ShapeId
14+
import software.amazon.smithy.model.traits.HttpTrait
15+
import software.amazon.smithy.model.traits.TimestampFormatTrait
16+
import software.amazon.smithy.swift.codegen.SwiftWriter
17+
import software.amazon.smithy.swift.codegen.integration.DefaultHttpProtocolCustomizations
18+
import software.amazon.smithy.swift.codegen.integration.HttpBindingProtocolGenerator
19+
import software.amazon.smithy.swift.codegen.integration.HttpBindingResolver
20+
import software.amazon.smithy.swift.codegen.integration.HttpProtocolTestGenerator
21+
import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestErrorGenerator
22+
import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestGenerator
23+
import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestRequestGenerator
24+
import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestResponseGenerator
25+
import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator
26+
import software.amazon.smithy.swift.codegen.integration.codingKeys.CodingKeysCustomizationJsonName
27+
import software.amazon.smithy.swift.codegen.integration.codingKeys.CodingKeysGenerator
28+
import software.amazon.smithy.swift.codegen.integration.codingKeys.DefaultCodingKeysGenerator
29+
import software.amazon.smithy.swift.codegen.integration.httpResponse.HttpResponseGeneratable
30+
import software.amazon.smithy.swift.codegen.integration.httpResponse.HttpResponseGenerator
31+
import software.amazon.smithy.swift.codegen.integration.protocols.core.StaticHttpBindingResolver
32+
import software.amazon.smithy.swift.codegen.integration.serde.json.StructDecodeGenerator
33+
import software.amazon.smithy.swift.codegen.integration.serde.json.StructEncodeGenerator
34+
import software.amazon.smithy.swift.codegen.model.ShapeMetadata
35+
36+
class MockJsonHttpBindingResolver(
37+
private val context: ProtocolGenerator.GenerationContext,
38+
private val defaultContentType: String
39+
) : StaticHttpBindingResolver(context, awsJsonHttpTrait, defaultContentType) {
40+
41+
companion object {
42+
private val awsJsonHttpTrait: HttpTrait = HttpTrait
43+
.builder()
44+
.code(200)
45+
.method("POST")
46+
.uri(UriPattern.parse("/"))
47+
.build()
48+
}
49+
}
50+
class MockAWSJson11HttpProtocolCustomizations() : DefaultHttpProtocolCustomizations()
51+
class MockHttpAWSJson11ProtocolGenerator : HttpBindingProtocolGenerator() {
52+
override val defaultContentType: String = "application/json"
53+
override val defaultTimestampFormat: TimestampFormatTrait.Format = TimestampFormatTrait.Format.DATE_TIME
54+
override val protocol: ShapeId = AwsJson1_1Trait.ID
55+
override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext): Int {
56+
val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder()
57+
val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder()
58+
val errorTestBuilder = HttpProtocolUnitTestErrorGenerator.Builder()
59+
60+
return HttpProtocolTestGenerator(
61+
ctx,
62+
requestTestBuilder,
63+
responseTestBuilder,
64+
errorTestBuilder,
65+
httpProtocolCustomizable,
66+
operationMiddleware,
67+
HttpProtocolUnitTestGenerator.SerdeContext("JSONEncoder()", "JSONDecoder()", ".secondsSince1970")
68+
).generateProtocolTests()
69+
}
70+
71+
override val httpProtocolClientGeneratorFactory = TestHttpProtocolClientGeneratorFactory()
72+
override val httpProtocolCustomizable = MockAWSJson11HttpProtocolCustomizations()
73+
override val codingKeysGenerator: CodingKeysGenerator = DefaultCodingKeysGenerator(CodingKeysCustomizationJsonName())
74+
override val httpResponseGenerator: HttpResponseGeneratable = HttpResponseGenerator(
75+
unknownServiceErrorSymbol,
76+
defaultTimestampFormat,
77+
MockHttpResponseBindingErrorGenerator()
78+
)
79+
override val shouldRenderDecodableBodyStructForInputShapes = true
80+
override val shouldRenderCodingKeysForEncodable = true
81+
82+
override fun renderStructEncode(
83+
ctx: ProtocolGenerator.GenerationContext,
84+
shapeContainingMembers: Shape,
85+
shapeMetadata: Map<ShapeMetadata, Any>,
86+
members: List<MemberShape>,
87+
writer: SwiftWriter,
88+
defaultTimestampFormat: TimestampFormatTrait.Format,
89+
) {
90+
val encodeGenerator = StructEncodeGenerator(ctx, members, writer, defaultTimestampFormat)
91+
encodeGenerator.render()
92+
}
93+
override fun renderStructDecode(
94+
ctx: ProtocolGenerator.GenerationContext,
95+
shapeMetadata: Map<ShapeMetadata, Any>,
96+
members: List<MemberShape>,
97+
writer: SwiftWriter,
98+
defaultTimestampFormat: TimestampFormatTrait.Format,
99+
) {
100+
val decodeGenerator = StructDecodeGenerator(ctx, members, writer, defaultTimestampFormat)
101+
decodeGenerator.render()
102+
}
103+
104+
override fun addProtocolSpecificMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) {
105+
// Intentionally empty
106+
}
107+
108+
override fun getProtocolHttpBindingResolver(ctx: ProtocolGenerator.GenerationContext, defaultContentType: String):
109+
HttpBindingResolver = MockJsonHttpBindingResolver(ctx, defaultContentType)
110+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package serde.awsjson11
2+
3+
import TestContext
4+
import defaultSettings
5+
import getFileContents
6+
import io.kotest.matchers.string.shouldContainOnlyOnce
7+
import mocks.MockHttpAWSJson11ProtocolGenerator
8+
import org.junit.jupiter.api.Test
9+
import shouldSyntacticSanityCheck
10+
11+
class NestedListEncodeJSONGenerationTests {
12+
13+
@Test
14+
fun `list of maps of lists`() {
15+
val context = setupTests("Isolated/json11/lists-of-maps-of-lists.smithy", "aws.protocoltests.json#JsonProtocol")
16+
val contents = getFileContents(context.manifest, "/Example/models/ListOfMapsOperationInput.swift")
17+
contents.shouldSyntacticSanityCheck()
18+
val expectedContents =
19+
"""
20+
public struct ListOfMapsOperationInput: Swift.Equatable {
21+
public let targetMaps: [[Swift.String:[Swift.String]]]?
22+
23+
public init (
24+
targetMaps: [[Swift.String:[Swift.String]]]? = nil
25+
)
26+
{
27+
self.targetMaps = targetMaps
28+
}
29+
}
30+
""".trimIndent()
31+
contents.shouldContainOnlyOnce(expectedContents)
32+
}
33+
34+
@Test
35+
fun `encode list of maps of lists`() {
36+
val context = setupTests("Isolated/json11/lists-of-maps-of-lists.smithy", "aws.protocoltests.json#JsonProtocol")
37+
val contents = getFileContents(context.manifest, "/Example/models/ListOfMapsOperationInput+Encodable.swift")
38+
contents.shouldSyntacticSanityCheck()
39+
val expectedContents =
40+
"""
41+
extension ListOfMapsOperationInput: Swift.Encodable, ClientRuntime.Reflection {
42+
enum CodingKeys: Swift.String, Swift.CodingKey {
43+
case targetMaps
44+
}
45+
46+
public func encode(to encoder: Swift.Encoder) throws {
47+
var encodeContainer = encoder.container(keyedBy: CodingKeys.self)
48+
if let targetMaps = targetMaps {
49+
var targetMapsContainer = encodeContainer.nestedUnkeyedContainer(forKey: .targetMaps)
50+
for targetmaps0 in targetMaps {
51+
var targetmaps0Container = targetMapsContainer.nestedContainer(keyedBy: ClientRuntime.Key.self)
52+
for (dictKey1, targetmap1) in targetmaps0 {
53+
var targetmap1Container = targetmaps0Container.nestedUnkeyedContainer(forKey: ClientRuntime.Key(dictKey1))
54+
for targetmapvaluelist2 in targetmap1 {
55+
try targetmap1Container.encode(targetmapvaluelist2)
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
""".trimIndent()
63+
contents.shouldContainOnlyOnce(expectedContents)
64+
}
65+
private fun setupTests(smithyFile: String, serviceShapeId: String): TestContext {
66+
val context = TestContext.initContextFrom(smithyFile, serviceShapeId, MockHttpAWSJson11ProtocolGenerator()) { model ->
67+
model.defaultSettings(serviceShapeId, "Example", "2014-11-06", "aws json 11")
68+
}
69+
context.generator.generateCodableConformanceForNestedTypes(context.generationCtx)
70+
context.generator.generateSerializers(context.generationCtx)
71+
context.generationCtx.delegator.flushWriters()
72+
return context
73+
}
74+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
$version: "1.0"
3+
4+
namespace aws.protocoltests.json
5+
6+
use aws.api#service
7+
use aws.auth#sigv4
8+
use aws.protocols#awsJson1_1
9+
use smithy.test#httpRequestTests
10+
use smithy.test#httpResponseTests
11+
12+
13+
@awsJson1_1
14+
@title("Sample Json 1.1 Protocol Service")
15+
service JsonProtocol {
16+
version: "2018-01-01",
17+
operations: [
18+
ListOfMapsOperation
19+
]
20+
}
21+
22+
operation ListOfMapsOperation {
23+
input: ListOfMapsInputOutput,
24+
output: ListOfMapsInputOutput
25+
}
26+
27+
structure ListOfMapsInputOutput {
28+
targetMaps: TargetMaps
29+
}
30+
31+
list TargetMaps {
32+
member: TargetMap
33+
}
34+
35+
map TargetMap {
36+
key: String,
37+
value: TargetMapValueList
38+
}
39+
40+
list TargetMapValueList {
41+
member: String
42+
}

0 commit comments

Comments
 (0)