Skip to content

Commit 5a1a921

Browse files
authored
feat: add DSL overloads to paginator methods (#591)
1 parent ff6a786 commit 5a1a921

File tree

4 files changed

+104
-33
lines changed

4 files changed

+104
-33
lines changed

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinWriter.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,24 @@ private fun String.stripAll(stripList: List<String>): String {
379379
}
380380

381381
// Remove whitespace from the beginning and end of each line of documentation
382-
// Remove blank lines
382+
// Remove leading, trailing, and consecutive blank lines
383383
private fun formatDocumentation(doc: String, lineSeparator: String = "\n") =
384384
doc
385385
.split('\n') // Break the doc into lines
386-
.filter { it.isNotBlank() } // Remove empty lines
386+
.dropWhile { it.isBlank() } // Drop leading blank lines
387+
.dropLastWhile { it.isBlank() } // Drop trailing blank lines
388+
.dropConsecutive { it.isBlank() } // Remove consecutive empty lines
387389
.joinToString(separator = lineSeparator) { it.trim() } // Trim line
390+
391+
/**
392+
* Filters out consecutive items matching the given [predicate].
393+
*/
394+
private fun <T> List<T>.dropConsecutive(predicate: (T) -> Boolean) =
395+
windowed(2, partialWindows = true)
396+
.flatMap { window ->
397+
if (predicate(window.first()) && window.first() == window.elementAtOrNull(1)) {
398+
listOf()
399+
} else {
400+
listOf(window.first())
401+
}
402+
}

smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/PaginatorGenerator.kt

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
1515
import software.amazon.smithy.kotlin.codegen.core.defaultName
1616
import software.amazon.smithy.kotlin.codegen.core.withBlock
1717
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
18+
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
1819
import software.amazon.smithy.kotlin.codegen.model.SymbolProperty
1920
import software.amazon.smithy.kotlin.codegen.model.expectShape
2021
import software.amazon.smithy.kotlin.codegen.model.hasTrait
@@ -108,36 +109,37 @@ class PaginatorGenerator : KotlinIntegration {
108109
}
109110
val markerLiteral = paginationInfo.inputTokenMember.defaultName()
110111

112+
val docBody = """
113+
Paginate over [${outputSymbol.name}] results.
114+
115+
When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
116+
calls are made until the flow is collected. This also means there is no guarantee that the request is valid
117+
until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
118+
calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
119+
see the failures only after you start collection.
120+
""".trimIndent()
121+
val docReturn = "@return A [kotlinx.coroutines.flow.Flow] that can collect [${outputSymbol.name}]"
122+
111123
writer.write("")
112-
writer.dokka(
113-
"""
114-
Paginate over [${outputSymbol.name}] results.
115-
When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
116-
made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
117-
you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
118-
pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
119-
collection.
120-
@param initialRequest A [${inputSymbol.name}] to start pagination
121-
@return A [kotlinx.coroutines.flow.Flow] that can collect [${outputSymbol.name}]
122-
""".trimIndent()
123-
)
124124
writer
125-
.addImport(ExternalTypes.KotlinxCoroutines.Flow)
126-
.addImport(ExternalTypes.KotlinxCoroutines.FlowGenerator)
127-
.addImport(serviceSymbol)
128-
.addImport(inputSymbol)
129-
.addImport(outputSymbol)
130-
.addImport(cursorSymbol)
125+
.dokka(
126+
"""
127+
$docBody
128+
@param initialRequest A [${inputSymbol.name}] to start pagination
129+
$docReturn
130+
""".trimIndent()
131+
)
131132
.addImportReferences(cursorSymbol, SymbolReference.ContextOption.DECLARE)
132133
.withBlock(
133-
"fun #T.#LPaginated(initialRequest: #T): Flow<#T> =",
134+
"fun #T.#LPaginated(initialRequest: #T): #T<#T> =",
134135
"",
135136
serviceSymbol,
136137
operationShape.defaultName(),
137138
inputSymbol,
138-
outputSymbol
139+
ExternalTypes.KotlinxCoroutines.Flow,
140+
outputSymbol,
139141
) {
140-
withBlock("flow {", "}") {
142+
withBlock("#T {", "}", ExternalTypes.KotlinxCoroutines.FlowGenerator) {
141143
write("var cursor: #F = null", cursorSymbol)
142144
write("var isFirstPage: Boolean = true")
143145
write("")
@@ -155,6 +157,28 @@ class PaginatorGenerator : KotlinIntegration {
155157
}
156158
}
157159
}
160+
161+
writer.write("")
162+
writer
163+
.dokka(
164+
"""
165+
$docBody
166+
@param block A builder block used for DSL-style invocation of the operation
167+
$docReturn
168+
""".trimIndent()
169+
)
170+
.withBlock(
171+
"fun #T.#LPaginated(block: #T.Builder.() -> #T): #T<#T> =",
172+
"",
173+
serviceSymbol,
174+
operationShape.defaultName(),
175+
inputSymbol,
176+
KotlinTypes.Unit,
177+
ExternalTypes.KotlinxCoroutines.Flow,
178+
outputSymbol,
179+
) {
180+
write("#LPaginated(#T.Builder().apply(block).build())", operationShape.defaultName(), inputSymbol)
181+
}
158182
}
159183

160184
// Generate a paginator that iterates over the model-specified item

smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/ClientConfigGeneratorTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ class Config private constructor(builder: Builder): HttpClientConfig, Idempotenc
7474
/**
7575
* Configure events that will be logged. By default clients will not output
7676
* raw requests or responses. Use this setting to opt-in to additional debug logging.
77+
*
7778
* This can be used to configure logging of requests, responses, retries, etc of SDK clients.
79+
*
7880
* **NOTE**: Logging of raw requests or responses may leak sensitive information! It may also have
7981
* performance considerations when dumping the request/response body. This is primarily a tool for
8082
* debug purposes.

smithy-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/rendering/PaginatorGeneratorTest.kt

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,12 @@ class PaginatorGeneratorTest {
142142
val expected = """
143143
/**
144144
* Paginate over [ListFunctionsResponse] results.
145-
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
146-
* made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
147-
* you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
148-
* pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
149-
* collection.
145+
*
146+
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
147+
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
148+
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
149+
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
150+
* see the failures only after you start collection.
150151
* @param initialRequest A [ListFunctionsRequest] to start pagination
151152
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
152153
*/
@@ -165,6 +166,20 @@ class PaginatorGeneratorTest {
165166
emit(result)
166167
}
167168
}
169+
170+
/**
171+
* Paginate over [ListFunctionsResponse] results.
172+
*
173+
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
174+
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
175+
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
176+
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
177+
* see the failures only after you start collection.
178+
* @param block A builder block used for DSL-style invocation of the operation
179+
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
180+
*/
181+
fun TestClient.listFunctionsPaginated(block: ListFunctionsRequest.Builder.() -> Unit): Flow<ListFunctionsResponse> =
182+
listFunctionsPaginated(ListFunctionsRequest.Builder().apply(block).build())
168183
""".trimIndent()
169184

170185
actual.shouldContainOnlyOnceWithDiff(expected)
@@ -182,11 +197,12 @@ class PaginatorGeneratorTest {
182197
val expectedCode = """
183198
/**
184199
* Paginate over [ListFunctionsResponse] results.
185-
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
186-
* made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
187-
* you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
188-
* pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
189-
* collection.
200+
*
201+
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
202+
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
203+
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
204+
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
205+
* see the failures only after you start collection.
190206
* @param initialRequest A [ListFunctionsRequest] to start pagination
191207
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
192208
*/
@@ -206,6 +222,20 @@ class PaginatorGeneratorTest {
206222
}
207223
}
208224
225+
/**
226+
* Paginate over [ListFunctionsResponse] results.
227+
*
228+
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
229+
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
230+
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
231+
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
232+
* see the failures only after you start collection.
233+
* @param block A builder block used for DSL-style invocation of the operation
234+
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
235+
*/
236+
fun TestClient.listFunctionsPaginated(block: ListFunctionsRequest.Builder.() -> Unit): Flow<ListFunctionsResponse> =
237+
listFunctionsPaginated(ListFunctionsRequest.Builder().apply(block).build())
238+
209239
/**
210240
* This paginator transforms the flow returned by [listFunctionsPaginated]
211241
* to access the nested member [FunctionConfiguration]

0 commit comments

Comments
 (0)