Skip to content

Commit f978296

Browse files
committed
feat: link api ref docs to examples
1 parent 0581f5c commit f978296

File tree

8 files changed

+231
-6
lines changed

8 files changed

+231
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ local.properties
1616
# ignore generated files
1717
services/*/generated-src
1818
services/*/build.gradle.kts
19+
services/*/API.md
1920
.kotest/
2021
.kotlin/
2122
*.klib

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ allprojects {
6767

6868
tasks.withType<org.jetbrains.dokka.gradle.DokkaTaskPartial>().configureEach {
6969
// each module can include their own top-level module documentation
70-
// see https://kotlinlang.org/docs/kotlin-doc.html#module-and-package-documentation
70+
// see https://kotlinlang.org/docs/dokka-module-and-package-docs.html
7171
if (project.file("API.md").exists()) {
7272
dokkaSourceSets.configureEach {
7373
includes.from(project.file("API.md"))
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package aws.sdk.kotlin.codegen
2+
3+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
4+
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
5+
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
6+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
7+
import software.amazon.smithy.kotlin.codegen.model.expectShape
8+
import software.amazon.smithy.kotlin.codegen.model.getTrait
9+
import software.amazon.smithy.model.Model
10+
import software.amazon.smithy.model.shapes.ServiceShape
11+
import software.amazon.smithy.model.traits.TitleTrait
12+
13+
/**
14+
* Maps a services SKD ID to its code examples
15+
*/
16+
private val currentCodeExamplesServices = mapOf(
17+
"API Gateway" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_api-gateway_code_examples.html",
18+
"Auto Scaling" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_auto-scaling_code_examples.html",
19+
"Bedrock" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_bedrock_code_examples.html",
20+
"CloudWatch" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_cloudwatch_code_examples.html",
21+
"Comprehend" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_comprehend_code_examples.html",
22+
"DynamoDB" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_dynamodb_code_examples.html",
23+
"EC2" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_ec2_code_examples.html",
24+
"ECR" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_ecr_code_examples.html",
25+
"OpenSearch" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_opensearch_code_examples.html",
26+
"EventBridge" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_eventbridge_code_examples.html",
27+
"Glue" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_glue_code_examples.html",
28+
"IAM" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_iam_code_examples.html",
29+
"IoT" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_iot_code_examples.html ",
30+
"Keyspaces" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_keyspaces_code_examples.html",
31+
"KMS" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_kms_code_examples.html",
32+
"Lambda" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_lambda_code_examples.html",
33+
"MediaConvert" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_mediaconvert_code_examples.html",
34+
"Pinpoint" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_pinpoint_code_examples.html",
35+
"RDS" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_rds_code_examples.html",
36+
"Redshift" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_redshift_code_examples.html",
37+
"Rekognition" to "https://docs.aws.amazon.com/code-library/latest/ug/kotlin_1_rekognition_code_examples.html",
38+
)
39+
40+
/**
41+
* Maps a services SKD ID to its handwritten module documentation file in the `resources` dir.
42+
* The module documentation files MUST be markdown files.
43+
*/
44+
private val currentHandWrittenServices = mapOf(
45+
"S3" to "S3.md",
46+
)
47+
48+
/**
49+
* Generates an `API.md` file that will be used as module documentation in our API ref docs.
50+
* Some services have code examples we need to link to. Others have handwritten documentation.
51+
* The integration renders both into the `API.md` file.
52+
*
53+
* See: https://kotlinlang.org/docs/dokka-module-and-package-docs.html
54+
*
55+
* See: https://github.com/awslabs/aws-sdk-kotlin/blob/0581f5c5eeaa14dcd8af4ea0dfc088b1057f5ba5/build.gradle.kts#L68-L75
56+
*/
57+
class ModuleDocumentationIntegration(
58+
private val codeExamples: Map<String, String> = currentCodeExamplesServices,
59+
private val handWritten: Map<String, String> = currentHandWrittenServices,
60+
) : KotlinIntegration {
61+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
62+
model.expectShape<ServiceShape>(settings.service).sdkId.let {
63+
codeExamples.keys.contains(it) || handWritten.keys.contains(it)
64+
}
65+
66+
override fun writeAdditionalFiles(ctx: CodegenContext, delegator: KotlinDelegator) {
67+
delegator.fileManifest.writeFile(
68+
"API.md",
69+
generateModuleDocumentation(ctx, ctx.settings.sdkId),
70+
)
71+
}
72+
73+
internal fun generateModuleDocumentation(
74+
ctx: CodegenContext,
75+
sdkId: String,
76+
) = buildString {
77+
append(
78+
generateBoilerPlate(ctx),
79+
)
80+
if (codeExamples.keys.contains(sdkId)) {
81+
append(
82+
generateCodeExamplesDocs(sdkId),
83+
)
84+
appendLine()
85+
}
86+
if (handWritten.keys.contains(sdkId)) {
87+
append(
88+
generateHandWrittenDocs(sdkId),
89+
)
90+
}
91+
}
92+
93+
private fun generateBoilerPlate(ctx: CodegenContext) = buildString {
94+
// Title must me "Module" followed by the exact module name or dokka won't render it
95+
appendLine("# Module ${ctx.settings.pkg.name.split(".").last()}")
96+
appendLine()
97+
ctx
98+
.model
99+
.expectShape<ServiceShape>(ctx.settings.service)
100+
.getTrait<TitleTrait>()
101+
?.value
102+
?.let {
103+
appendLine(it)
104+
appendLine()
105+
}
106+
}
107+
108+
private fun generateCodeExamplesDocs(sdkId: String) = buildString {
109+
appendLine("## Code Examples")
110+
appendLine("To see full code examples, see the $sdkId examples in the AWS Code Library. See ${codeExamples[sdkId]}")
111+
appendLine()
112+
}
113+
114+
private fun generateHandWrittenDocs(sdkId: String): String = object {}
115+
.javaClass
116+
.classLoader
117+
.getResourceAsStream("aws/sdk/kotlin/codegen/moduledocumentation/${handWritten[sdkId]}")
118+
?.bufferedReader()
119+
?.readText()
120+
?: throw Exception("Unable to read from file ${handWritten[sdkId]}")
121+
}

codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ aws.sdk.kotlin.codegen.smoketests.SmokeTestsDenyListIntegration
4848
aws.sdk.kotlin.codegen.smoketests.testing.SmokeTestSuccessHttpEngineIntegration
4949
aws.sdk.kotlin.codegen.smoketests.testing.SmokeTestFailHttpEngineIntegration
5050
aws.sdk.kotlin.codegen.customization.AwsQueryModeCustomization
51+
aws.sdk.kotlin.codegen.ModuleDocumentationIntegration

services/s3/API.md renamed to codegen/aws-sdk-codegen/src/main/resources/aws/sdk/kotlin/codegen/moduledocumentation/S3.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# Module s3
2-
3-
Amazon Simple Storage Service
4-
51
## Binary Data
62

73
Binary data (streams) are represented as an [aws.smithy.kotlin.runtime.content.ByteStream].
@@ -35,7 +31,7 @@ See [aws.sdk.kotlin.services.s3.model.GetObjectResponse]
3531
## Streaming Responses
3632

3733
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).
38-
The result of the call is whatever the lambda returns.
34+
The result of the call is whatever the lambda returns.
3935

4036

4137
See [aws.sdk.kotlin.services.s3.S3Client.getObject]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package aws.sdk.kotlin.codegen
2+
3+
import software.amazon.smithy.kotlin.codegen.test.newTestContext
4+
import software.amazon.smithy.kotlin.codegen.test.shouldContainOnlyOnceWithDiff
5+
import software.amazon.smithy.kotlin.codegen.test.toGenerationContext
6+
import software.amazon.smithy.kotlin.codegen.test.toSmithyModel
7+
import kotlin.test.Test
8+
import kotlin.test.assertFalse
9+
import kotlin.test.assertTrue
10+
11+
private val model = """
12+
${"$"}version: "2"
13+
14+
namespace com.test
15+
16+
use aws.api#service
17+
18+
@service(sdkId: "Test")
19+
@title("A test service")
20+
service Test {
21+
version: "1.0.0",
22+
operations: []
23+
}
24+
""".toSmithyModel()
25+
26+
val ctx = model.newTestContext("Test")
27+
28+
class ModuleDocumentationIntegrationTest {
29+
@Test
30+
fun integrationIsAppliedCorrectly() {
31+
assertFalse(
32+
ModuleDocumentationIntegration().enabledForService(model, ctx.generationCtx.settings),
33+
)
34+
assertTrue(
35+
ModuleDocumentationIntegration(
36+
codeExamples = mapOf("Test" to "https://example.com"),
37+
).enabledForService(model, ctx.generationCtx.settings),
38+
)
39+
assertTrue(
40+
ModuleDocumentationIntegration(
41+
handWritten = mapOf("Test" to "example.md"),
42+
).enabledForService(model, ctx.generationCtx.settings),
43+
)
44+
assertTrue(
45+
ModuleDocumentationIntegration(
46+
codeExamples = mapOf("Test" to "https://example.com"),
47+
handWritten = mapOf("Test" to "test.md"),
48+
).enabledForService(model, ctx.generationCtx.settings),
49+
)
50+
}
51+
52+
@Test
53+
fun rendersBoilerplate() =
54+
ModuleDocumentationIntegration()
55+
.generateModuleDocumentation(
56+
ctx.toGenerationContext(),
57+
"Test",
58+
)
59+
.shouldContainOnlyOnceWithDiff(
60+
"""
61+
# Module test
62+
63+
A test service
64+
""".trimIndent(),
65+
)
66+
67+
@Test
68+
fun rendersCodeExampleDocs() =
69+
ModuleDocumentationIntegration(
70+
codeExamples = mapOf("Test" to "https://example.com"),
71+
)
72+
.generateModuleDocumentation(
73+
ctx.toGenerationContext(),
74+
"Test",
75+
)
76+
.shouldContainOnlyOnceWithDiff(
77+
"""
78+
## Code Examples
79+
To see full code examples, see the Test examples in the AWS Code Library. See https://example.com
80+
""".trimIndent(),
81+
)
82+
83+
@Test
84+
fun rendersHandWrittenDocs() =
85+
ModuleDocumentationIntegration(
86+
handWritten = mapOf("Test" to "test.md"),
87+
)
88+
.generateModuleDocumentation(
89+
ctx.toGenerationContext(),
90+
"Test",
91+
)
92+
.shouldContainOnlyOnceWithDiff(
93+
"""
94+
## Subtitle
95+
96+
Lorem Ipsum
97+
""".trimIndent(),
98+
)
99+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Subtitle
2+
3+
Lorem Ipsum

codegen/sdk/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ val stageSdks = tasks.register("stageSdks") {
180180
from("$projectionOutputDir/build.gradle.kts")
181181
into(it.destinationDir)
182182
}
183+
copy {
184+
from("$projectionOutputDir/API.md")
185+
into(it.destinationDir)
186+
}
183187
}
184188
}
185189
}

0 commit comments

Comments
 (0)