
software.amazon.smithy.kotlin.codegen.rendering.ServiceClientConfigGenerator.kt Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.kotlin.codegen.rendering
import software.amazon.smithy.codegen.core.CodegenException
import software.amazon.smithy.kotlin.codegen.core.CodegenContext
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.model.expectTrait
import software.amazon.smithy.kotlin.codegen.model.hasIdempotentTokenMember
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.DefaultEndpointProviderGenerator
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointParametersGenerator
import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointProviderGenerator
import software.amazon.smithy.kotlin.codegen.rendering.util.AbstractConfigGenerator
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigProperty
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigPropertyType
import software.amazon.smithy.kotlin.codegen.rendering.util.RuntimeConfigProperty
import software.amazon.smithy.kotlin.codegen.utils.getOrNull
import software.amazon.smithy.kotlin.codegen.utils.toCamelCase
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.ShapeType
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait
/**
* Default generator for rendering a service config. By default integrations can register additional properties
* without overriding the entirety of service config generation.
*
* @param serviceShape the service shape to render config for
* @param detectDefaultProps Flag indicating if properties should be added automatically based on model detection
*/
class ServiceClientConfigGenerator(
private val serviceShape: ServiceShape,
private val detectDefaultProps: Boolean = true,
) : AbstractConfigGenerator() {
/**
* Attempt to detect configuration properties automatically based on the model
*/
private fun detectDefaultProps(context: CodegenContext, shape: ServiceShape): List = buildList {
add(RuntimeConfigProperty.ClientName)
add(RuntimeConfigProperty.LogMode)
if (context.protocolGenerator?.applicationProtocol?.isHttpProtocol == true) {
add(RuntimeConfigProperty.HttpClient)
add(RuntimeConfigProperty.HttpInterceptors)
add(RuntimeConfigProperty.AuthSchemes)
}
if (shape.hasIdempotentTokenMember(context.model)) {
add(RuntimeConfigProperty.IdempotencyTokenProvider)
}
add(RuntimeConfigProperty.RetryPolicy)
add(RuntimeConfigProperty.RetryStrategy)
add(RuntimeConfigProperty.TelemetryProvider)
if (shape.hasTrait()) {
addAll(clientContextConfigProps(shape.expectTrait()))
}
// FIXME - we only generate an endpoint provider type if we have a protocol generator defined
if (context.protocolGenerator != null) {
add(
ConfigProperty {
val hasRules = shape.hasTrait()
val defaultEndpointProviderSymbol = DefaultEndpointProviderGenerator.getSymbol(context.settings)
symbol = EndpointProviderGenerator.getSymbol(context.settings)
name = "endpointProvider"
propertyType = if (hasRules) { // if there's a ruleset, we have a usable default, otherwise caller has to provide their own
ConfigPropertyType.RequiredWithDefault("${defaultEndpointProviderSymbol.name}()")
} else {
ConfigPropertyType.Required()
}
documentation = """
The endpoint provider used to determine where to make service requests. **This is an advanced config
option.**
Endpoint resolution occurs as part of the workflow for every request made via the service client.
The inputs to endpoint resolution are defined on a per-service basis (see [EndpointParameters]).
""".trimIndent()
additionalImports = buildList {
add(EndpointParametersGenerator.getSymbol(context.settings))
if (hasRules) {
add(defaultEndpointProviderSymbol)
}
}
},
)
}
}
/**
* Derives client config properties from the service context params trait.
*/
private fun clientContextConfigProps(trait: ClientContextParamsTrait): List =
trait
.parameters
.map { (k, v) ->
when (v.type) {
ShapeType.BOOLEAN -> ConfigProperty.Boolean(
name = k.toCamelCase(),
defaultValue = false,
documentation = v.documentation.getOrNull(),
)
ShapeType.STRING -> ConfigProperty.String(
name = k.toCamelCase(),
defaultValue = null,
documentation = v.documentation.getOrNull(),
)
else -> throw CodegenException("unsupported client context param type ${v.type}")
}
}
fun render(ctx: CodegenContext, writer: KotlinWriter) = render(ctx, emptyList(), writer)
override fun render(ctx: CodegenContext, props: Collection, writer: KotlinWriter) {
val allPropsByName = props.byName().toMutableMap()
if (detectDefaultProps) {
val defaultPropsByName = detectDefaultProps(ctx, serviceShape).byName()
// register auto detected properties
allPropsByName.putAll(defaultPropsByName)
}
// register properties from integrations
ctx
.integrations
.map { it.additionalServiceConfigProps(ctx).byName() }
.forEach(allPropsByName::putAll)
writer.pushState()
// Service client config is always nested inside the service client interface. Its visibility is taken
// from that type. If the interface is `internal` then trying to use `internal` as the visibility on the
// nested config class is a compilation error.
writer.putContext("visibility", "public")
super.render(ctx, allPropsByName.values.toList(), writer)
writer.popState()
}
override fun renderBuilderBuildMethod(writer: KotlinWriter) {
// we should _ALWAYS_ end up with SdkClientConfig.Builder as a base class for service client config, need
// to override the build() method we inherit rather than use the default generated `internal` one
writer.write("override fun build(): #configClass.name:L = #configClass.name:L(this)")
}
}
private fun Collection.byName(): Map = associateBy(ConfigProperty::propertyName)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy