Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: paginator generator nullability bug caused by documents #1155

Merged
merged 9 commits into from
Sep 27, 2024
5 changes: 5 additions & 0 deletions .changes/f71f083b-6e5f-4a3c-9069-464b1f9f6d36.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "f71f083b-6e5f-4a3c-9069-464b1f9f6d36",
"type": "bugfix",
"description": "Fix paginator generator `List<*>` nullability"
}
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,22 @@ private fun getItemDescriptorOrNull(paginationInfo: PaginationInfo, ctx: Codegen
val itemMember = ctx.model.expectShape(itemMemberId)
val isSparse = itemMember.isSparse
val (collectionLiteral, targetMember) = when (itemMember) {
is MapShape ->
ctx.symbolProvider.toSymbol(itemMember)
.expectProperty(SymbolProperty.ENTRY_EXPRESSION) as String to itemMember
is CollectionShape ->
ctx.symbolProvider.toSymbol(ctx.model.expectShape(itemMember.member.target)).name to ctx.model.expectShape(
itemMember.member.target,
)
is MapShape -> {
val symbol = ctx.symbolProvider.toSymbol(itemMember)
val entryExpression = symbol.expectProperty(SymbolProperty.ENTRY_EXPRESSION) as String + if (isSparse) "?" else ""
entryExpression to itemMember
}
is CollectionShape -> {
val target = ctx.model.expectShape(itemMember.member.target)
val symbol = ctx.symbolProvider.toSymbol(target)
val literal = symbol.name + if (symbol.isNullable || isSparse) "?" else ""
literal to target
}
else -> error("Unexpected shape type ${itemMember.type}")
}

return ItemDescriptor(
collectionLiteral + if (isSparse) "?" else "",
collectionLiteral,
targetMember,
itemLiteral,
itemPathLiteral,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,4 +762,308 @@ class PaginatorGeneratorTest {

actual.shouldContainOnlyOnceWithDiff(expected)
}

@Test
fun testRenderPaginatorWithDocumentList() {
Comment on lines +766 to +767
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let add a comment to each linking the issue that spawned them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll create one, we don't have an external issue for this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh maybe not required if there wasn't an external issue...that's just busywork.

val testModelWithItems = """
namespace com.test

use aws.protocols#restJson1

service Lambda {
operations: [ListFunctions]
}

@paginated(
inputToken: "Marker",
outputToken: "NextMarker",
pageSize: "MaxItems",
items: "Functions"
)
@readonly
@http(method: "GET", uri: "/functions", code: 200)
operation ListFunctions {
input: ListFunctionsRequest,
output: ListFunctionsResponse
}

structure ListFunctionsRequest {
@httpQuery("FunctionVersion")
FunctionVersion: String,
@httpQuery("Marker")
Marker: String,
@httpQuery("MasterRegion")
MasterRegion: String,
@httpQuery("MaxItems")
MaxItems: Integer
}

structure ListFunctionsResponse {
Functions: FunctionConfigurationList,
NextMarker: String
}

list FunctionConfigurationList {
member: FunctionConfiguration
}

document FunctionConfiguration
""".toSmithyModel()
val testContextWithItems = testModelWithItems.newTestContext("Lambda", "com.test")

val codegenContextWithItems = object : CodegenContext {
override val model: Model = testContextWithItems.generationCtx.model
override val symbolProvider: SymbolProvider = testContextWithItems.generationCtx.symbolProvider
override val settings: KotlinSettings = testContextWithItems.generationCtx.settings
override val protocolGenerator: ProtocolGenerator = testContextWithItems.generator
override val integrations: List<KotlinIntegration> = testContextWithItems.generationCtx.integrations
}

val unit = PaginatorGenerator()
unit.writeAdditionalFiles(codegenContextWithItems, testContextWithItems.generationCtx.delegator)

testContextWithItems.generationCtx.delegator.flushWriters()
val testManifest = testContextWithItems.generationCtx.delegator.fileManifest as MockManifest
val actual = testManifest.expectFileString("src/main/kotlin/com/test/paginators/Paginators.kt")

val expectedCode = """
@JvmName("listFunctionsResponseFunctionConfiguration")
public fun Flow<ListFunctionsResponse>.functions(): Flow<Document?> =
transform() { response ->
response.functions?.forEach {
emit(it)
}
}
""".trimIndent()
actual.shouldContainOnlyOnceWithDiff(expectedCode)
}

@Test
fun testRenderPaginatorWithSparseDocumentList() {
val testModelWithItems = """
namespace com.test

use aws.protocols#restJson1

service Lambda {
operations: [ListFunctions]
}

@paginated(
inputToken: "Marker",
outputToken: "NextMarker",
pageSize: "MaxItems",
items: "Functions"
)
@readonly
@http(method: "GET", uri: "/functions", code: 200)
operation ListFunctions {
input: ListFunctionsRequest,
output: ListFunctionsResponse
}

structure ListFunctionsRequest {
@httpQuery("FunctionVersion")
FunctionVersion: String,
@httpQuery("Marker")
Marker: String,
@httpQuery("MasterRegion")
MasterRegion: String,
@httpQuery("MaxItems")
MaxItems: Integer
}

structure ListFunctionsResponse {
Functions: FunctionConfigurationList,
NextMarker: String
}

@sparse
list FunctionConfigurationList {
member: FunctionConfiguration
}

document FunctionConfiguration
""".toSmithyModel()
val testContextWithItems = testModelWithItems.newTestContext("Lambda", "com.test")

val codegenContextWithItems = object : CodegenContext {
override val model: Model = testContextWithItems.generationCtx.model
override val symbolProvider: SymbolProvider = testContextWithItems.generationCtx.symbolProvider
override val settings: KotlinSettings = testContextWithItems.generationCtx.settings
override val protocolGenerator: ProtocolGenerator = testContextWithItems.generator
override val integrations: List<KotlinIntegration> = testContextWithItems.generationCtx.integrations
}

val unit = PaginatorGenerator()
unit.writeAdditionalFiles(codegenContextWithItems, testContextWithItems.generationCtx.delegator)

testContextWithItems.generationCtx.delegator.flushWriters()
val testManifest = testContextWithItems.generationCtx.delegator.fileManifest as MockManifest
val actual = testManifest.expectFileString("src/main/kotlin/com/test/paginators/Paginators.kt")

val expectedCode = """
@JvmName("listFunctionsResponseFunctionConfiguration")
public fun Flow<ListFunctionsResponse>.functions(): Flow<Document?> =
transform() { response ->
response.functions?.forEach {
emit(it)
}
}
""".trimIndent()
actual.shouldContainOnlyOnceWithDiff(expectedCode)
}

@Test
fun testRenderPaginatorWithDocumentMap() {
val testModelWithItems = """
namespace com.test

use aws.protocols#restJson1

service Lambda {
operations: [ListFunctions]
}

@paginated(
inputToken: "Marker",
outputToken: "NextMarker",
pageSize: "MaxItems",
items: "Functions"
)
@readonly
@http(method: "GET", uri: "/functions", code: 200)
operation ListFunctions {
input: ListFunctionsRequest,
output: ListFunctionsResponse
}

structure ListFunctionsRequest {
@httpQuery("FunctionVersion")
FunctionVersion: String,
@httpQuery("Marker")
Marker: String,
@httpQuery("MasterRegion")
MasterRegion: String,
@httpQuery("MaxItems")
MaxItems: Integer
}

structure ListFunctionsResponse {
Functions: FunctionConfigurationMap,
NextMarker: String
}

map FunctionConfigurationMap {
key: String
value: FunctionConfiguration
}

document FunctionConfiguration
""".toSmithyModel()
val testContextWithItems = testModelWithItems.newTestContext("Lambda", "com.test")

val codegenContextWithItems = object : CodegenContext {
override val model: Model = testContextWithItems.generationCtx.model
override val symbolProvider: SymbolProvider = testContextWithItems.generationCtx.symbolProvider
override val settings: KotlinSettings = testContextWithItems.generationCtx.settings
override val protocolGenerator: ProtocolGenerator = testContextWithItems.generator
override val integrations: List<KotlinIntegration> = testContextWithItems.generationCtx.integrations
}

val unit = PaginatorGenerator()
unit.writeAdditionalFiles(codegenContextWithItems, testContextWithItems.generationCtx.delegator)

testContextWithItems.generationCtx.delegator.flushWriters()
val testManifest = testContextWithItems.generationCtx.delegator.fileManifest as MockManifest
val actual = testManifest.expectFileString("src/main/kotlin/com/test/paginators/Paginators.kt")

val expectedCode = """
@JvmName("listFunctionsResponseFunctionConfigurationMap")
public fun Flow<ListFunctionsResponse>.functions(): Flow<Map.Entry<String, Document?>> =
transform() { response ->
response.functions?.forEach {
emit(it)
}
}
""".trimIndent()
actual.shouldContainOnlyOnceWithDiff(expectedCode)
}

@Test
fun testRenderPaginatorWithSparseDocumentMap() {
val testModelWithItems = """
namespace com.test

use aws.protocols#restJson1

service Lambda {
operations: [ListFunctions]
}

@paginated(
inputToken: "Marker",
outputToken: "NextMarker",
pageSize: "MaxItems",
items: "Functions"
)
@readonly
@http(method: "GET", uri: "/functions", code: 200)
operation ListFunctions {
input: ListFunctionsRequest,
output: ListFunctionsResponse
}

structure ListFunctionsRequest {
@httpQuery("FunctionVersion")
FunctionVersion: String,
@httpQuery("Marker")
Marker: String,
@httpQuery("MasterRegion")
MasterRegion: String,
@httpQuery("MaxItems")
MaxItems: Integer
}

structure ListFunctionsResponse {
Functions: FunctionConfigurationMap,
NextMarker: String
}

@sparse
map FunctionConfigurationMap {
key: String
value: FunctionConfiguration
}

document FunctionConfiguration
""".toSmithyModel()
val testContextWithItems = testModelWithItems.newTestContext("Lambda", "com.test")

val codegenContextWithItems = object : CodegenContext {
override val model: Model = testContextWithItems.generationCtx.model
override val symbolProvider: SymbolProvider = testContextWithItems.generationCtx.symbolProvider
override val settings: KotlinSettings = testContextWithItems.generationCtx.settings
override val protocolGenerator: ProtocolGenerator = testContextWithItems.generator
override val integrations: List<KotlinIntegration> = testContextWithItems.generationCtx.integrations
}

val unit = PaginatorGenerator()
unit.writeAdditionalFiles(codegenContextWithItems, testContextWithItems.generationCtx.delegator)

testContextWithItems.generationCtx.delegator.flushWriters()
val testManifest = testContextWithItems.generationCtx.delegator.fileManifest as MockManifest
val actual = testManifest.expectFileString("src/main/kotlin/com/test/paginators/Paginators.kt")

val expectedCode = """
@JvmName("listFunctionsResponseFunctionConfigurationMap")
public fun Flow<ListFunctionsResponse>.functions(): Flow<Map.Entry<String, Document?>?> =
transform() { response ->
response.functions?.forEach {
emit(it)
}
}
""".trimIndent()
actual.shouldContainOnlyOnceWithDiff(expectedCode)
}
}
Loading