Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

misc!: v1.2.0 #1079

Merged
merged 5 commits into from
Apr 25, 2024
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
9 changes: 9 additions & 0 deletions .changes/5498a35b-f57f-4c0c-bbf7-43e07830be01.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "5498a35b-f57f-4c0c-bbf7-43e07830be01",
"type": "bugfix",
"description": "⚠️ **IMPORTANT**: Add config finalization to service clients via new abstract factory class; apply clock skew interceptor to clients created via `invoke`",
"issues": [
"awslabs/aws-sdk-kotlin#1211"
],
"requiresMinorVersionBump": true
}
6 changes: 6 additions & 0 deletions .changes/8e87c6dd-5138-4b79-a0c2-255ecf31898b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "8e87c6dd-5138-4b79-a0c2-255ecf31898b",
"type": "misc",
"description": "⚠️ **IMPORTANT**: Upgrade to latest versions of OkHttp, Okio, Kotlin",
"requiresMinorVersionBump": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import software.amazon.smithy.kotlin.codegen.rendering.ServiceClientGenerator
*/
class ClockSkew : KotlinIntegration {
override val sectionWriters: List<SectionWriterBinding>
get() = listOf(SectionWriterBinding(ServiceClientGenerator.Sections.FinalizeConfig, clockSkewSectionWriter))
get() = listOf(
SectionWriterBinding(
ServiceClientGenerator.Sections.CompanionObject.FinalizeConfig,
clockSkewSectionWriter,
),
)

private val clockSkewSectionWriter = AppendingSectionWriter { writer ->
val interceptorSymbol = RuntimeTypes.AwsProtocolCore.ClockSkewInterceptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ object RuntimeTypes {
object SmithyClient : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT) {
val SdkClient = symbol("SdkClient")
val AbstractSdkClientBuilder = symbol("AbstractSdkClientBuilder")
val AbstractSdkClientFactory = symbol("AbstractSdkClientFactory")
val LogMode = symbol("LogMode")
val RetryClientConfig = symbol("RetryClientConfig")
val RetryStrategyClientConfig = symbol("RetryStrategyClientConfig")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {
* [SectionId] used when rendering the service interface companion object
*/
object CompanionObject : SectionId {

/**
* [SectionId] used when rendering the finalizeConfig block of a service client companion object
*/
object FinalizeConfig : SectionId

/**
* Context key for the service symbol
*/
Expand All @@ -46,6 +52,11 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {
* Context key for the SDK ID
*/
val SdkId: SectionKey<String> = SectionKey("SdkId")

/**
* [SectionId] used when rendering the supertype(s) of the companion object
*/
object SuperTypes : SectionId
}

/**
Expand All @@ -57,11 +68,6 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {
*/
val RenderingContext: SectionKey<RenderingContext<ServiceShape>> = SectionKey("RenderingContext")
}

/**
* [SectionId] used when rendering the finalizeConfig block of a service client
*/
object FinalizeConfig : SectionId
}

init {
Expand All @@ -88,38 +94,28 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {

writer.renderDocumentation(service)
writer.renderAnnotations(service)
writer.openBlock(
writer.withBlock(
"#L interface ${serviceSymbol.name} : #T {",
"}",
ctx.settings.api.visibility,
RuntimeTypes.SmithyClient.SdkClient,
)
.call {
// allow access to client's Config
writer.dokka("${serviceSymbol.name}'s configuration")
writer.write("public override val config: Config")
}
.call {
// allow integrations to add additional fields to companion object or configuration
writer.write("")
writer.declareSection(
Sections.CompanionObject,
context = mapOf(
Sections.CompanionObject.ServiceSymbol to serviceSymbol,
Sections.CompanionObject.SdkId to ctx.settings.sdkId,
),
) {
renderCompanionObject()
}
writer.write("")
renderServiceBuilder()
) {
// allow access to client's Config
dokka("${serviceSymbol.name}'s configuration")
write("public override val config: Config")

writer.write("")
renderServiceConfig()
}
.call {
operations.forEach { renderOperation(operationsIndex, it) }
}
.closeBlock("}")
// allow integrations to add additional fields to companion object or configuration
write("")
renderCompanionObject()

write("")
renderServiceBuilder()

write("")
renderServiceConfig()

operations.forEach { renderOperation(operationsIndex, it) }
}
.write("")

if (ctx.protocolGenerator != null) { // returns default impl, which only exists if there's a protocol generator
Expand Down Expand Up @@ -172,15 +168,39 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {
private fun renderCompanionObject() {
// don't render a companion object which is used for building a service client unless we have a protocol generator
if (ctx.protocolGenerator == null) return
writer.withBlock(
"public companion object : #T<Config, Config.Builder, #T, Builder> {",
"}",
RuntimeTypes.SmithyClient.SdkClientFactory,
serviceSymbol,
) {
write("@#T", KotlinTypes.Jvm.JvmStatic)
write("override fun builder(): Builder = Builder()")
}

writer
.writeInline("public companion object : ")
.declareSection(
Sections.CompanionObject.SuperTypes,
context = mapOf(
Sections.CompanionObject.ServiceSymbol to serviceSymbol,
),
) {
writeInline(
"#T<Config, Config.Builder, #T, Builder>()",
RuntimeTypes.SmithyClient.AbstractSdkClientFactory,
serviceSymbol,
)
}
.withBlock(" {", "}") {
declareSection(
Sections.CompanionObject,
context = mapOf(
Sections.CompanionObject.ServiceSymbol to serviceSymbol,
Sections.CompanionObject.SdkId to ctx.settings.sdkId,
),
) {
write("@#T", KotlinTypes.Jvm.JvmStatic)
write("override fun builder(): Builder = Builder()")
write("")
withBlock("override fun finalizeConfig(builder: Builder) {", "}") {
declareSection(Sections.CompanionObject.FinalizeConfig) {
write("super.finalizeConfig(builder)")
}
}
}
}
}

private fun renderOperation(opIndex: OperationIndex, op: OperationShape) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ class ServiceClientGeneratorTest {
@Test
fun `it renders a companion object with default client factory if protocol generator`() {
val expected = """
public companion object : SdkClientFactory<Config, Config.Builder, TestClient, Builder> {
public companion object : AbstractSdkClientFactory<Config, Config.Builder, TestClient, Builder>() {
@JvmStatic
override fun builder(): Builder = Builder()

override fun finalizeConfig(builder: Builder) {
super.finalizeConfig(builder)
}
}
""".formatForTest()
commonWithProtocolTestContents.shouldContainOnlyOnceWithDiff(expected)
Expand All @@ -66,7 +70,7 @@ class ServiceClientGeneratorTest {
@Test
fun `it renders a companion object without default client factory if no protocol generator`() {
val expected = """
public companion object : SdkClientFactory
public companion object : AbstractSdkClientFactory
""".formatForTest()
commonTestContents.shouldNotContain(expected)
}
Expand All @@ -91,9 +95,7 @@ class ServiceClientGeneratorTest {
val writer = KotlinWriter(TestModelDefault.NAMESPACE)
val service = model.expectShape<ServiceShape>(TestModelDefault.SERVICE_SHAPE_ID)
writer.registerSectionWriter(ServiceClientGenerator.Sections.CompanionObject) { codeWriter, _ ->
codeWriter.openBlock("public companion object {")
.write("public fun foo(): Int = 1")
.closeBlock("}")
codeWriter.write("public fun foo(): Int = 1")
}

writer.registerSectionWriter(ServiceClientGenerator.Sections.ServiceConfig) { codeWriter, _ ->
Expand All @@ -103,13 +105,17 @@ class ServiceClientGeneratorTest {
}

val settings = KotlinSettings(service.id, KotlinSettings.PackageSettings("test", "0.0"), sdkId = service.id.name)
val renderingCtx = RenderingContext(writer, service, model, provider, settings)

// Without a protocol generator the companion object won't be generated so use a fake protocol here.
val protocol = MockHttpProtocolGenerator(model)
val renderingCtx = RenderingContext(writer, service, model, provider, settings, protocol)

val generator = ServiceClientGenerator(renderingCtx)
generator.render()
val contents = writer.toString()

val expectedCompanionOverride = """
public companion object {
public companion object : AbstractSdkClientFactory<Config, Config.Builder, TestClient, Builder>() {
public fun foo(): Int = 1
}
""".formatForTest()
Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[versions]
kotlin-version = "1.9.22"
kotlin-version = "1.9.23"
dokka-version = "1.9.10"

aws-kotlin-repo-tools-version = "0.4.0"

# libs
coroutines-version = "1.7.3"
atomicfu-version = "0.23.1"
okhttp-version = "5.0.0-alpha.11"
okio-version = "3.6.0"
okhttp-version = "5.0.0-alpha.14"
okio-version = "3.9.0"
otel-version = "1.32.0"
slf4j-version = "2.0.9"
slf4j-v1x-version = "1.7.36"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import aws.smithy.kotlin.runtime.net.TlsVersion
import aws.smithy.kotlin.runtime.operation.ExecutionContext
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.job
import okhttp3.*
import okhttp3.coroutines.executeAsync
import java.util.concurrent.TimeUnit
import kotlin.time.toJavaDuration
import aws.smithy.kotlin.runtime.net.TlsVersion as SdkTlsVersion
Expand Down Expand Up @@ -48,6 +50,8 @@ public class OkHttpEngine(

val engineRequest = request.toOkHttpRequest(context, callContext, metrics)
val engineCall = client.newCall(engineRequest)

@OptIn(ExperimentalCoroutinesApi::class)
val engineResponse = mapOkHttpExceptions { engineCall.executeAsync() }

val response = engineResponse.toSdkResponse()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import aws.smithy.kotlin.runtime.text.encoding.encodeToHex
import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import okio.Buffer
import okio.BufferedSink
import org.junit.jupiter.api.Test
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.test.*
Expand Down Expand Up @@ -134,7 +133,7 @@ class StreamingRequestBodyTest {
override fun readFrom(): SdkByteReadChannel = chan
}

val sink = TestSink()
val sink = Buffer()

val callJob = Job()
val callContext = coroutineContext + callJob
Expand All @@ -145,19 +144,14 @@ class StreamingRequestBodyTest {
assertEquals(0, callJob.children.toList().size)
actual.writeTo(sink)
assertEquals(1, callJob.children.toList().size) // writer
assertEquals(sink.buffer.size, 0)
assertEquals(sink.size, 0)
chan.writeAll(content.source())

assertFalse(sink.isClosed)

chan.close()
callJob.complete()
callJob.join()

// we must manually close the sink given to us when stream completes
assertTrue(sink.isClosed)

val actualSha256 = sink.buffer.sha256().hex()
val actualSha256 = sink.sha256().hex()
assertEquals(expectedSha256, actualSha256)
}

Expand All @@ -174,14 +168,9 @@ class StreamingRequestBodyTest {
val callContext = coroutineContext + Job()
val actual = StreamingRequestBody(body, callContext)

val sink = TestSink()
val sink = Buffer()
actual.writeTo(sink)

assertContentEquals(file.readBytes(), sink.buffer.readByteArray())
assertContentEquals(file.readBytes(), sink.readByteArray())
}
}

private class TestSink(override val buffer: Buffer = Buffer()) : BufferedSink by buffer {
var isClosed = false
override fun close() { isClosed = true }
}
6 changes: 6 additions & 0 deletions runtime/smithy-client/api/smithy-client.api
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ public abstract class aws/smithy/kotlin/runtime/client/AbstractSdkClientBuilder
protected abstract fun newClient (Laws/smithy/kotlin/runtime/client/SdkClientConfig;)Laws/smithy/kotlin/runtime/client/SdkClient;
}

public abstract class aws/smithy/kotlin/runtime/client/AbstractSdkClientFactory : aws/smithy/kotlin/runtime/client/SdkClientFactory {
public fun <init> ()V
protected fun finalizeConfig (Laws/smithy/kotlin/runtime/client/SdkClient$Builder;)V
public fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/client/SdkClient;
}

public abstract interface class aws/smithy/kotlin/runtime/client/IdempotencyTokenConfig {
public abstract fun getIdempotencyTokenProvider ()Laws/smithy/kotlin/runtime/client/IdempotencyTokenProvider;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.client

public abstract class AbstractSdkClientFactory<
TConfig : SdkClientConfig,
TConfigBuilder : SdkClientConfig.Builder<TConfig>,
TClient : SdkClient,
TClientBuilder : SdkClient.Builder<TConfig, TConfigBuilder, TClient>,
> : SdkClientFactory<TConfig, TConfigBuilder, TClient, TClientBuilder> {

/**
* Inject any client-specific config
*/
protected open fun finalizeConfig(builder: TClientBuilder) { }

public override operator fun invoke(block: TConfigBuilder.() -> Unit): TClient = builder().apply {
config.apply(block)
finalizeConfig(this)
}.build()
}
Loading