All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.kotlin.codegen.rendering.ClientConfigProperty.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.Symbol
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.model.boxed
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
import software.amazon.smithy.kotlin.codegen.model.defaultValue
import software.amazon.smithy.kotlin.codegen.model.namespace

/**
 * Represents a service client config property to be added to the generated client
 *
 * e.g.
 *
 * ```
 * val myProp = ConfigProperty {
 *     symbol = buildSymbol { ... }
 *     documentation = "my property documentation"
 *     addBaseClass(myBaseClass)
 * }
 * ```
 */
class ClientConfigProperty private constructor(builder: Builder) {

    /**
     * The symbol (type) for the property
     *
     * NOTE: Use the extension properties on Symbol.Builder to set additional properties:
     * e.g.
     * ```
     * val symbol = Symbol.builder()
     *    .nullable = true // mark the symbol as nullable
     *    .defaultValue("foo") // set the default value for the property
     *    .build()
     * ```
     */
    val symbol: Symbol = requireNotNull(builder.symbol)

    /**
     * Help text to be rendered for the property
     */
    val documentation: String? = builder.documentation

    /**
     * The name of the property to render to config
     */
    val propertyName: String = builder.name ?: symbol.name.replaceFirstChar { c -> c.lowercaseChar() }

    /**
     * Additional base classes config should inherit from
     *
     * NOTE: Adding 1 or more base classes will implicitly render the property with an `override` modifier
     */
    val baseClass: Symbol? = builder.baseClass

    /**
     * The configuration property type. This controls how the property is constructed and rendered
     */
    val propertyType: ClientConfigPropertyType = builder.propertyType

    /**
     * Additional symbols that should be imported when this property is generated. This is useful for
     * example when the [symbol] type has is an interface and has a default or constant value that
     * implements that type. The default value symbol also needs imported.
     */
    val additionalImports: List = builder.additionalImports

    /**
     * Flag indicating if this property stems from some base class and needs an override modifier when rendered
     */
    internal val requiresOverride: Boolean
        get() = baseClass != null

    companion object {
        operator fun invoke(block: Builder.() -> Unit): ClientConfigProperty =
            Builder().apply(block).build()

        /**
         * Convenience init for an integer symbol.
         * @param name The property name
         * @param defaultValue The default value the config property should have if not set (if not specified the
         * parameter is assumed nullable)
         * @param documentation Help text to render as documentation for the property
         * @param baseClass Base class the config class should inherit from (assumes this property
         * stems from this type)
         */
        fun Int(
            name: String,
            defaultValue: Int? = null,
            documentation: String? = null,
            baseClass: Symbol? = null,
        ): ClientConfigProperty =
            builtInProperty(name, builtInSymbol("Int", defaultValue?.toString()), documentation, baseClass)

        /**
         * Convenience init for a boolean symbol.
         * @param name The property name
         * @param defaultValue The default value the config property should have if not set (if not specified the
         * parameter is assumed nullable)
         * @param documentation Help text to render as documentation for the property
         * @param baseClass Base class the config class should inherit from (assumes this property
         * stems from this type)
         */
        fun Boolean(
            name: String,
            defaultValue: Boolean? = null,
            documentation: String? = null,
            baseClass: Symbol? = null,
        ): ClientConfigProperty =
            builtInProperty(name, builtInSymbol("Boolean", defaultValue?.toString()), documentation, baseClass)

        /**
         * Convenience init for a string symbol.
         * @param name The property name
         * @param defaultValue The default value the config property should have if not set (if not specified the
         * parameter is assumed nullable)
         * @param documentation Help text to render as documentation for the property
         * @param baseClass Base class the config class should inherit from (assumes this property
         * stems from this type)
         */
        fun String(
            name: String,
            defaultValue: String? = null,
            documentation: String? = null,
            baseClass: Symbol? = null,
        ): ClientConfigProperty =
            builtInProperty(name, builtInSymbol("String", defaultValue), documentation, baseClass)
    }

    class Builder {
        var symbol: Symbol? = null
        // override the property name (defaults to symbol name)
        var name: String? = null
        var documentation: String? = null

        var baseClass: Symbol? = null

        var propertyType: ClientConfigPropertyType = ClientConfigPropertyType.SymbolDefault

        var additionalImports: List = emptyList()

        fun build(): ClientConfigProperty = ClientConfigProperty(this)
    }
}

/**
 * Descriptor for how a configuration property is rendered when the configuration is built
 */
sealed class ClientConfigPropertyType {
    /**
     * A property type that uses the symbol type and builder symbol directly
     */
    object SymbolDefault : ClientConfigPropertyType()

    /**
     * Specifies that the value should be populated with a constant value that cannot be overridden in the builder.
     * These are effectively read-only properties that will show up in the configuration type but not the builder.
     *
     * @param value the value to assign to the property at construction time
     */
    data class ConstantValue(val value: String) : ClientConfigPropertyType()

    /**
     * A configuration property that is required to be set (i.e. not null).
     * If the property is not provided in the builder then an IllegalArgumentException is thrown
     *
     * @param message The exception message to throw if the property is null, if not set a message is generated
     * automatically based on the property name
     */
    data class Required(val message: String? = null) : ClientConfigPropertyType()

    /**
     * A configuration property that is required but has a default value. This has the same semantics of [Required]
     * but instead of an exception the default value will be used when not provided in the builder.
     *
     * @param default the value to assign if the corresponding builder property is null
     */
    data class RequiredWithDefault(val default: String) : ClientConfigPropertyType()
}

private fun builtInSymbol(symbolName: String, defaultValue: String?): Symbol {
    val builder = Symbol.builder()
        .name(symbolName)

    if (defaultValue != null) {
        builder.defaultValue(defaultValue)
    } else {
        builder.boxed()
    }
    return builder.build()
}

private fun builtInProperty(
    name: String,
    symbol: Symbol,
    documentation: String?,
    baseClass: Symbol?,
): ClientConfigProperty =
    ClientConfigProperty {
        this.symbol = symbol
        this.name = name
        this.documentation = documentation
        this.baseClass = baseClass
    }

/**
 * Common client runtime related config properties
 */
object KotlinClientRuntimeConfigProperty {
    val HttpClientEngine: ClientConfigProperty
    val IdempotencyTokenProvider: ClientConfigProperty
    val RetryStrategy: ClientConfigProperty
    val SdkLogMode: ClientConfigProperty
    val EndpointResolver: ClientConfigProperty

    init {
        val httpClientConfigSymbol = buildSymbol {
            name = "HttpClientConfig"
            namespace(KotlinDependency.HTTP, "config")
        }

        HttpClientEngine = ClientConfigProperty {
            symbol = RuntimeTypes.Http.Engine.HttpClientEngine
            baseClass = httpClientConfigSymbol
            documentation = """
            Override the default HTTP client engine used to make SDK requests (e.g. configure proxy behavior, timeouts, concurrency, etc)    
            """.trimIndent()
        }

        IdempotencyTokenProvider = ClientConfigProperty {
            symbol = buildSymbol {
                name = "IdempotencyTokenProvider"
                namespace(KotlinDependency.CORE, "config")
            }

            baseClass = buildSymbol {
                name = "IdempotencyTokenConfig"
                namespace(KotlinDependency.CORE, "config")
            }

            documentation = """
            Override the default idempotency token generator. SDK clients will generate tokens for members
            that represent idempotent tokens when not explicitly set by the caller using this generator.
            """.trimIndent()
        }

        RetryStrategy = ClientConfigProperty {
            symbol = RuntimeTypes.Core.Retries.RetryStrategy
            name = "retryStrategy"
            documentation = """
                The [RetryStrategy] implementation to use for service calls. All API calls will be wrapped by the
                strategy.
            """.trimIndent()

            val retryStrategyBlock = """
                run {
                    val strategyOptions = StandardRetryStrategyOptions.Default
                    val tokenBucket = StandardRetryTokenBucket(StandardRetryTokenBucketOptions.Default)
                    val delayer = ExponentialBackoffWithJitter(ExponentialBackoffWithJitterOptions.Default)
                    StandardRetryStrategy(strategyOptions, tokenBucket, delayer)
                }
            """.trimIndent()
            propertyType = ClientConfigPropertyType.ConstantValue(retryStrategyBlock)

            additionalImports = listOf(
                RuntimeTypes.Core.Retries.Impl.StandardRetryStrategy,
                RuntimeTypes.Core.Retries.Impl.StandardRetryStrategyOptions,
                RuntimeTypes.Core.Retries.Impl.StandardRetryTokenBucket,
                RuntimeTypes.Core.Retries.Impl.StandardRetryTokenBucketOptions,
                RuntimeTypes.Core.Retries.Impl.ExponentialBackoffWithJitter,
                RuntimeTypes.Core.Retries.Impl.ExponentialBackoffWithJitterOptions,
            )
        }

        SdkLogMode = ClientConfigProperty {
            symbol = buildSymbol {
                name = "SdkLogMode"
                namespace(KotlinDependency.CORE, "client")
                defaultValue = "SdkLogMode.Default"
                nullable = false
            }

            baseClass = buildSymbol {
                name = "SdkClientConfig"
                namespace(KotlinDependency.CORE, "config")
            }

            documentation = """
            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.
            """.trimIndent()
        }

        EndpointResolver = ClientConfigProperty {
            symbol = RuntimeTypes.Http.Operation.EndpointResolver
            documentation = """
            Set the [${symbol!!.fullName}] used to resolve service endpoints. Operation requests will be 
            made against the endpoint returned by the resolver.
            """.trimIndent()
            propertyType = ClientConfigPropertyType.Required()
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy