@@ -263,6 +263,143 @@ class PaginatorGeneratorTest {
263263 actual.shouldContainOnlyOnceWithDiff(expectedImports)
264264 }
265265
266+ @Test
267+ fun testRenderPaginatorWithSparseItem () {
268+ val testModelWithItems = """
269+ namespace com.test
270+
271+ use aws.protocols#restJson1
272+
273+ service Lambda {
274+ operations: [ListFunctions]
275+ }
276+
277+ @paginated(
278+ inputToken: "Marker",
279+ outputToken: "NextMarker",
280+ pageSize: "MaxItems",
281+ items: "Functions"
282+ )
283+ @readonly
284+ @http(method: "GET", uri: "/functions", code: 200)
285+ operation ListFunctions {
286+ input: ListFunctionsRequest,
287+ output: ListFunctionsResponse
288+ }
289+
290+ structure ListFunctionsRequest {
291+ @httpQuery("FunctionVersion")
292+ FunctionVersion: String,
293+ @httpQuery("Marker")
294+ Marker: String,
295+ @httpQuery("MasterRegion")
296+ MasterRegion: String,
297+ @httpQuery("MaxItems")
298+ MaxItems: Integer
299+ }
300+
301+ structure ListFunctionsResponse {
302+ Functions: FunctionConfigurationList,
303+ NextMarker: String
304+ }
305+
306+ @sparse
307+ list FunctionConfigurationList {
308+ member: FunctionConfiguration
309+ }
310+
311+ structure FunctionConfiguration {
312+ FunctionName: String
313+ }
314+ """ .toSmithyModel()
315+ val testContextWithItems = testModelWithItems.newTestContext(" Lambda" , " com.test" )
316+
317+ val codegenContextWithItems = object : CodegenContext {
318+ override val model: Model = testContextWithItems.generationCtx.model
319+ override val symbolProvider: SymbolProvider = testContextWithItems.generationCtx.symbolProvider
320+ override val settings: KotlinSettings = testContextWithItems.generationCtx.settings
321+ override val protocolGenerator: ProtocolGenerator = testContextWithItems.generator
322+ override val integrations: List <KotlinIntegration > = testContextWithItems.generationCtx.integrations
323+ }
324+
325+ val unit = PaginatorGenerator ()
326+ unit.writeAdditionalFiles(codegenContextWithItems, testContextWithItems.generationCtx.delegator)
327+
328+ testContextWithItems.generationCtx.delegator.flushWriters()
329+ val testManifest = testContextWithItems.generationCtx.delegator.fileManifest as MockManifest
330+ val actual = testManifest.expectFileString(" src/main/kotlin/com/test/paginators/Paginators.kt" )
331+
332+ val expectedCode = """
333+ /**
334+ * Paginate over [ListFunctionsResponse] results.
335+ *
336+ * When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
337+ * calls are made until the flow is collected. This also means there is no guarantee that the request is valid
338+ * until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
339+ * calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
340+ * see the failures only after you start collection.
341+ * @param initialRequest A [ListFunctionsRequest] to start pagination
342+ * @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
343+ */
344+ public fun TestClient.listFunctionsPaginated(initialRequest: ListFunctionsRequest = ListFunctionsRequest { }): Flow<ListFunctionsResponse> =
345+ flow {
346+ var cursor: kotlin.String? = null
347+ var hasNextPage: Boolean = true
348+
349+ while (hasNextPage) {
350+ val req = initialRequest.copy {
351+ this.marker = cursor
352+ }
353+ val result = [email protected] (req) 354+ cursor = result.nextMarker
355+ hasNextPage = cursor?.isNotEmpty() == true
356+ emit(result)
357+ }
358+ }
359+
360+ /**
361+ * Paginate over [ListFunctionsResponse] results.
362+ *
363+ * When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
364+ * calls are made until the flow is collected. This also means there is no guarantee that the request is valid
365+ * until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
366+ * calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
367+ * see the failures only after you start collection.
368+ * @param block A builder block used for DSL-style invocation of the operation
369+ * @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
370+ */
371+ public fun TestClient.listFunctionsPaginated(block: ListFunctionsRequest.Builder.() -> Unit): Flow<ListFunctionsResponse> =
372+ listFunctionsPaginated(ListFunctionsRequest.Builder().apply(block).build())
373+
374+ /**
375+ * This paginator transforms the flow returned by [listFunctionsPaginated]
376+ * to access the nested member [FunctionConfiguration]
377+ * @return A [kotlinx.coroutines.flow.Flow] that can collect [FunctionConfiguration]
378+ */
379+ @JvmName("listFunctionsResponseFunctionConfiguration")
380+ public fun Flow<ListFunctionsResponse>.functions(): Flow<FunctionConfiguration?> =
381+ transform() { response ->
382+ response.functions?.forEach {
383+ emit(it)
384+ }
385+ }
386+ """ .trimIndent()
387+
388+ actual.shouldContainOnlyOnceWithDiff(expectedCode)
389+
390+ val expectedImports = """
391+ import com.test.model.FunctionConfiguration
392+ import com.test.model.ListFunctionsRequest
393+ import com.test.model.ListFunctionsResponse
394+ import kotlin.jvm.JvmName
395+ import kotlinx.coroutines.flow.Flow
396+ import kotlinx.coroutines.flow.flow
397+ import kotlinx.coroutines.flow.transform
398+ """ .trimIndent()
399+
400+ actual.shouldContainOnlyOnceWithDiff(expectedImports)
401+ }
402+
266403 @Test
267404 fun testRenderPaginatorWithTruncationMember () {
268405 val testModel = """
0 commit comments