Skip to content

Commit

Permalink
feat: add DSL overloads to paginator methods (#591)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianbotsf authored Feb 16, 2022
1 parent ff6a786 commit 5a1a921
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,24 @@ private fun String.stripAll(stripList: List<String>): String {
}

// Remove whitespace from the beginning and end of each line of documentation
// Remove blank lines
// Remove leading, trailing, and consecutive blank lines
private fun formatDocumentation(doc: String, lineSeparator: String = "\n") =
doc
.split('\n') // Break the doc into lines
.filter { it.isNotBlank() } // Remove empty lines
.dropWhile { it.isBlank() } // Drop leading blank lines
.dropLastWhile { it.isBlank() } // Drop trailing blank lines
.dropConsecutive { it.isBlank() } // Remove consecutive empty lines
.joinToString(separator = lineSeparator) { it.trim() } // Trim line

/**
* Filters out consecutive items matching the given [predicate].
*/
private fun <T> List<T>.dropConsecutive(predicate: (T) -> Boolean) =
windowed(2, partialWindows = true)
.flatMap { window ->
if (predicate(window.first()) && window.first() == window.elementAtOrNull(1)) {
listOf()
} else {
listOf(window.first())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.defaultName
import software.amazon.smithy.kotlin.codegen.core.withBlock
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
import software.amazon.smithy.kotlin.codegen.model.SymbolProperty
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.model.hasTrait
Expand Down Expand Up @@ -108,36 +109,37 @@ class PaginatorGenerator : KotlinIntegration {
}
val markerLiteral = paginationInfo.inputTokenMember.defaultName()

val docBody = """
Paginate over [${outputSymbol.name}] results.
When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
calls are made until the flow is collected. This also means there is no guarantee that the request is valid
until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
see the failures only after you start collection.
""".trimIndent()
val docReturn = "@return A [kotlinx.coroutines.flow.Flow] that can collect [${outputSymbol.name}]"

writer.write("")
writer.dokka(
"""
Paginate over [${outputSymbol.name}] results.
When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
collection.
@param initialRequest A [${inputSymbol.name}] to start pagination
@return A [kotlinx.coroutines.flow.Flow] that can collect [${outputSymbol.name}]
""".trimIndent()
)
writer
.addImport(ExternalTypes.KotlinxCoroutines.Flow)
.addImport(ExternalTypes.KotlinxCoroutines.FlowGenerator)
.addImport(serviceSymbol)
.addImport(inputSymbol)
.addImport(outputSymbol)
.addImport(cursorSymbol)
.dokka(
"""
$docBody
@param initialRequest A [${inputSymbol.name}] to start pagination
$docReturn
""".trimIndent()
)
.addImportReferences(cursorSymbol, SymbolReference.ContextOption.DECLARE)
.withBlock(
"fun #T.#LPaginated(initialRequest: #T): Flow<#T> =",
"fun #T.#LPaginated(initialRequest: #T): #T<#T> =",
"",
serviceSymbol,
operationShape.defaultName(),
inputSymbol,
outputSymbol
ExternalTypes.KotlinxCoroutines.Flow,
outputSymbol,
) {
withBlock("flow {", "}") {
withBlock("#T {", "}", ExternalTypes.KotlinxCoroutines.FlowGenerator) {
write("var cursor: #F = null", cursorSymbol)
write("var isFirstPage: Boolean = true")
write("")
Expand All @@ -155,6 +157,28 @@ class PaginatorGenerator : KotlinIntegration {
}
}
}

writer.write("")
writer
.dokka(
"""
$docBody
@param block A builder block used for DSL-style invocation of the operation
$docReturn
""".trimIndent()
)
.withBlock(
"fun #T.#LPaginated(block: #T.Builder.() -> #T): #T<#T> =",
"",
serviceSymbol,
operationShape.defaultName(),
inputSymbol,
KotlinTypes.Unit,
ExternalTypes.KotlinxCoroutines.Flow,
outputSymbol,
) {
write("#LPaginated(#T.Builder().apply(block).build())", operationShape.defaultName(), inputSymbol)
}
}

// Generate a paginator that iterates over the model-specified item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ class Config private constructor(builder: Builder): HttpClientConfig, Idempotenc
/**
* Configure events that will be logged. By default clients will not output
* raw requests or responses. Use this setting to opt-in to additional debug logging.
*
* This can be used to configure logging of requests, responses, retries, etc of SDK clients.
*
* **NOTE**: Logging of raw requests or responses may leak sensitive information! It may also have
* performance considerations when dumping the request/response body. This is primarily a tool for
* debug purposes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@ class PaginatorGeneratorTest {
val expected = """
/**
* Paginate over [ListFunctionsResponse] results.
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
* made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
* you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
* pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
* collection.
*
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
* see the failures only after you start collection.
* @param initialRequest A [ListFunctionsRequest] to start pagination
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
*/
Expand All @@ -165,6 +166,20 @@ class PaginatorGeneratorTest {
emit(result)
}
}
/**
* Paginate over [ListFunctionsResponse] results.
*
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
* see the failures only after you start collection.
* @param block A builder block used for DSL-style invocation of the operation
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
*/
fun TestClient.listFunctionsPaginated(block: ListFunctionsRequest.Builder.() -> Unit): Flow<ListFunctionsResponse> =
listFunctionsPaginated(ListFunctionsRequest.Builder().apply(block).build())
""".trimIndent()

actual.shouldContainOnlyOnceWithDiff(expected)
Expand All @@ -182,11 +197,12 @@ class PaginatorGeneratorTest {
val expectedCode = """
/**
* Paginate over [ListFunctionsResponse] results.
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service calls are
* made until the flow is collected. This also means there is no guarantee that the request is valid until then. Once
* you start collecting the flow, the SDK will lazily load response pages by making service calls until there are no
* pages left or the flow is cancelled. If there are errors in your request, you will see the failures only after you start
* collection.
*
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
* see the failures only after you start collection.
* @param initialRequest A [ListFunctionsRequest] to start pagination
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
*/
Expand All @@ -206,6 +222,20 @@ class PaginatorGeneratorTest {
}
}
/**
* Paginate over [ListFunctionsResponse] results.
*
* When this operation is called, a [kotlinx.coroutines.Flow] is created. Flows are lazy (cold) so no service
* calls are made until the flow is collected. This also means there is no guarantee that the request is valid
* until then. Once you start collecting the flow, the SDK will lazily load response pages by making service
* calls until there are no pages left or the flow is cancelled. If there are errors in your request, you will
* see the failures only after you start collection.
* @param block A builder block used for DSL-style invocation of the operation
* @return A [kotlinx.coroutines.flow.Flow] that can collect [ListFunctionsResponse]
*/
fun TestClient.listFunctionsPaginated(block: ListFunctionsRequest.Builder.() -> Unit): Flow<ListFunctionsResponse> =
listFunctionsPaginated(ListFunctionsRequest.Builder().apply(block).build())
/**
* This paginator transforms the flow returned by [listFunctionsPaginated]
* to access the nested member [FunctionConfiguration]
Expand Down

0 comments on commit 5a1a921

Please sign in to comment.