Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ local.properties
services/*/generated-src
services/*/build.gradle.kts
services/*/API.md
services/*/OVERVIEW.md
.kotest/
.kotlin/
*.klib
Expand Down
18 changes: 10 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,18 @@ allprojects {
}

tasks.withType<org.jetbrains.dokka.gradle.DokkaTaskPartial>().configureEach {
// each module can include their own top-level module documentation
// see https://kotlinlang.org/docs/dokka-module-and-package-docs.html
if (project.file("API.md").exists()) {
dokkaSourceSets.configureEach {
includes.from(project.file("API.md"))
}
}

dokkaSourceSets.configureEach {
samples.from(project.file("samples").path, project.file("generated-src/samples").path)

// Each module can include their own top-level module documentation in one or more included Markdown files,
// each of which must begin with `# Module <module-name>` where <module-name> is the literal name of the
// Gradle module. See https://kotlinlang.org/docs/dokka-module-and-package-docs.html for more details.
val includeFiles = setOf(
"OVERVIEW.md", // Auto-generated by ModuleDocumentationIntegration
"DOCS.md", // Hand-written docs explaining a module in greater detail
"API.md", // Auto-generated by `kat` tool
).mapNotNull { project.file(it).takeIf { it.exists() } }
includes.from(includeFiles)
}

val smithyKotlinPackageListUrl: String? by project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,125 +1,31 @@
package aws.sdk.kotlin.codegen

import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.aws.traits.ServiceTrait
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.model.expectShape
import software.amazon.smithy.kotlin.codegen.model.getTrait
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.traits.TitleTrait
import java.io.File

/**
* Maps a service's SDK ID to its code examples
* Generates an `OVERVIEW.md` file that will provide a brief intro for each service module in API reference docs.
*/
private val CODE_EXAMPLES_SERVICES_MAP = mapOf(
"API Gateway" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_api-gateway_code_examples.html",
"Auto Scaling" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_auto-scaling_code_examples.html",
"Bedrock" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_bedrock_code_examples.html",
"CloudWatch" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_cloudwatch_code_examples.html",
"Comprehend" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_comprehend_code_examples.html",
"DynamoDB" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_dynamodb_code_examples.html",
"EC2" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_ec2_code_examples.html",
"ECR" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_ecr_code_examples.html",
"OpenSearch" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_opensearch_code_examples.html",
"EventBridge" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_eventbridge_code_examples.html",
"Glue" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_glue_code_examples.html",
"IAM" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_iam_code_examples.html",
"IoT" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_iot_code_examples.html ",
"Keyspaces" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_keyspaces_code_examples.html",
"KMS" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_kms_code_examples.html",
"Lambda" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_lambda_code_examples.html",
"MediaConvert" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_mediaconvert_code_examples.html",
"Pinpoint" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_pinpoint_code_examples.html",
"RDS" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_rds_code_examples.html",
"Redshift" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_redshift_code_examples.html",
"Rekognition" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_rekognition_code_examples.html",
)

/**
* Generates an `API.md` file that will be used as module documentation in our API ref docs.
* Some services have code example documentation we need to generate. Others have handwritten documentation.
* The integration renders both into the `API.md` file.
*
* See: https://kotlinlang.org/docs/dokka-module-and-package-docs.html
*
* See: https://github.com/awslabs/aws-sdk-kotlin/blob/0581f5c5eeaa14dcd8af4ea0dfc088b1057f5ba5/build.gradle.kts#L68-L75
*/
class ModuleDocumentationIntegration(
private val codeExamples: Map<String, String> = CODE_EXAMPLES_SERVICES_MAP,
) : KotlinIntegration {
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
codeExamples.keys.contains(
model
.expectShape<ServiceShape>(settings.service)
.sdkId,
) ||
handWrittenDocsFile(settings).exists()

class ModuleDocumentationIntegration : KotlinIntegration {
override fun writeAdditionalFiles(ctx: CodegenContext, delegator: KotlinDelegator) {
delegator.fileManifest.writeFile(
"API.md",
generateModuleDocumentation(ctx),
)
}

internal fun generateModuleDocumentation(
ctx: CodegenContext,
) = buildString {
val handWrittenDocsFile = handWrittenDocsFile(ctx.settings)
if (handWrittenDocsFile.exists()) {
append(
handWrittenDocsFile.readText(),
)
val overview = buildString {
val moduleName = ctx.settings.pkg.name.split(".").last()
val service = ctx.model.expectShape<ServiceShape>(ctx.settings.service)
val title = service.getTrait<TitleTrait>()?.value
?: service.getTrait<ServiceTrait>()?.cloudFormationName
?: moduleName

appendLine("# Module $moduleName")
appendLine()
appendLine("This module contains the Kotlin SDK client for **$title**.")
}
if (codeExamples.keys.contains(ctx.settings.sdkId)) {
if (!handWrittenDocsFile.exists()) {
append(
boilerPlate(ctx),
)
}
append(
codeExamplesDocs(ctx),
)
}
}

private fun boilerPlate(ctx: CodegenContext) = buildString {
// Title must be "Module" followed by the exact module name or dokka won't render it
appendLine("# Module ${ctx.settings.pkg.name.split(".").last()}")
appendLine()
ctx
.model
.expectShape<ServiceShape>(ctx.settings.service)
.getTrait<TitleTrait>()
?.value
?.let {
appendLine(it)
appendLine()
}
}

private fun codeExamplesDocs(ctx: CodegenContext) = buildString {
val sdkId = ctx.settings.sdkId
val codeExampleLink = codeExamples[sdkId]
val title = ctx
.model
.expectShape<ServiceShape>(ctx.settings.service)
.getTrait<TitleTrait>()
?.value

appendLine("## Code Examples")
appendLine("Explore code examples for ${title ?: sdkId} in the <a href=\"$codeExampleLink\">AWS code example library</a>.")
appendLine()
delegator.fileManifest.writeFile("OVERVIEW.md", overview)
}
}

private fun handWrittenDocsFile(settings: KotlinSettings): File {
val sdkRootDir = System.getProperty("user.dir")
val serviceDir = "$sdkRootDir/services/${settings.pkg.name.split(".").last()}"

return File("$serviceDir/DOCS.md")
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package aws.sdk.kotlin.codegen

import software.amazon.smithy.build.MockManifest
import software.amazon.smithy.kotlin.codegen.test.newTestContext
import software.amazon.smithy.kotlin.codegen.test.shouldContainOnlyOnceWithDiff
import software.amazon.smithy.kotlin.codegen.test.toGenerationContext
import software.amazon.smithy.kotlin.codegen.test.toCodegenContext
import software.amazon.smithy.kotlin.codegen.test.toSmithyModel
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertEquals
import kotlin.test.assertTrue

private val model = """
Expand All @@ -28,40 +28,21 @@ val ctx = model.newTestContext("Test")
class ModuleDocumentationIntegrationTest {
@Test
fun integrationIsAppliedCorrectly() {
assertFalse(
ModuleDocumentationIntegration().enabledForService(model, ctx.generationCtx.settings),
)
assertTrue(
ModuleDocumentationIntegration(
codeExamples = mapOf("Test" to "https://example.com"),
).enabledForService(model, ctx.generationCtx.settings),
)
}
val integration = ModuleDocumentationIntegration()
assertTrue(integration.enabledForService(model, ctx.generationCtx.settings))

@Test
fun rendersBoilerplate() =
ModuleDocumentationIntegration(
codeExamples = mapOf("Test" to "https://example.com"),
)
.generateModuleDocumentation(ctx.toGenerationContext())
.shouldContainOnlyOnceWithDiff(
"""
# Module test

Test Service
""".trimIndent(),
)
integration.writeAdditionalFiles(ctx.toCodegenContext(), ctx.generationCtx.delegator)
ctx.generationCtx.delegator.flushWriters()
val testManifest = ctx.generationCtx.delegator.fileManifest as MockManifest

@Test
fun rendersCodeExampleDocs() =
ModuleDocumentationIntegration(
codeExamples = mapOf("Test" to "https://example.com"),
)
.generateModuleDocumentation(ctx.toGenerationContext())
.shouldContainOnlyOnceWithDiff(
"""
## Code Examples
Explore code examples for Test Service in the <a href="https://example.com">AWS code example library</a>
""".trimIndent(),
)
val actual = testManifest.expectFileString("OVERVIEW.md")
val expected = """
# Module test

This module contains the Kotlin SDK client for **Test Service**.

""".trimIndent()

assertEquals(expected, actual)
}
}
2 changes: 1 addition & 1 deletion codegen/sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ val stageSdks = tasks.register("stageSdks") {
into(it.destinationDir)
}
copy {
from("$projectionOutputDir/API.md")
from("$projectionOutputDir/OVERVIEW.md")
into(it.destinationDir)
}
}
Expand Down
19 changes: 7 additions & 12 deletions services/s3/DOCS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# Module s3

Amazon Simple Storage Service

## Binary Data

Binary data (streams) are represented as an [aws.smithy.kotlin.runtime.content.ByteStream].

To supply a `ByteStream` there are several convenience functions including:
Binary data (streams) are represented as a [`ByteStream`][aws.smithy.kotlin.runtime.content.ByteStream]. To supply a
`ByteStream` there are several convenience functions including:

```kt
val req = PutObjectRequest {
Expand All @@ -30,15 +27,12 @@ s3.getObject(req) { resp -> {
}
```

See [aws.sdk.kotlin.services.s3.model.GetObjectResponse]
See [`GetObjectResponse`][aws.sdk.kotlin.services.s3.model.GetObjectResponse] for more details.

## Streaming Responses

Streaming responses are scoped to a `block`. Instead of returning the response directly, you must pass a lambda which is given access to the response (and the underlying stream).
The result of the call is whatever the lambda returns.


See [aws.sdk.kotlin.services.s3.S3Client.getObject]
Streaming responses are scoped to a `block`. Instead of returning the response directly, you must pass a lambda which is
given access to the response (and the underlying stream). The result of the call is whatever the lambda returns.

```kt
val s3 = S3Client { ... }
Expand All @@ -57,5 +51,6 @@ val contentSize = s3.getObject(req) { resp ->
println("wrote $contentSize bytes to $path")
```

This scoped response simplifies lifetime management for both the caller and the runtime.

This scoped response simplifies lifetime management for both the caller and the runtime.
See [`getObject`][aws.sdk.kotlin.services.s3.S3Client.getObject] for more details.
Loading