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

software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver.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.protocol

import software.amazon.smithy.kotlin.codegen.model.expectTrait
import software.amazon.smithy.kotlin.codegen.model.hasTrait
import software.amazon.smithy.kotlin.codegen.utils.getOrNull
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.knowledge.HttpBinding
import software.amazon.smithy.model.knowledge.HttpBindingIndex
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.*
import software.amazon.smithy.model.traits.HttpTrait
import software.amazon.smithy.model.traits.TimestampFormatTrait

/**
 * A generic subset of [HttpBinding] that is not specific to any protocol implementation.
 */
data class HttpBindingDescriptor(
    val member: MemberShape,
    val location: HttpBinding.Location,
    val locationName: String? = null,
) {
    /**
     * @param Smithy [HttpBinding] to create from
     */
    constructor(httpBinding: HttpBinding) : this(httpBinding.member, httpBinding.location, httpBinding.locationName)

    val memberName: String = member.memberName
}

/**
 * Represents a protocol-specific implementation that resolves http binding data.
 * Smithy protocol implementations need to supply a protocol-specific implementation
 * of this type in order to use [HttpBindingProtocolGenerator].
 */
interface HttpBindingResolver {
    /**
     * Return from the model all operations with HTTP bindings (explicit/implicit/or assumed)
     */
    fun bindingOperations(): List

    /**
     * Return HttpTrait data from an operation suitable for the protocol implementation.
     * @param operationShape [OperationShape] for which to retrieve the HttpTrait
     * @return [HttpTrait]

     */
    fun httpTrait(operationShape: OperationShape): HttpTrait

    /**
     * Return request bindings for an operation.
     * @param operationShape [OperationShape] for which to retrieve bindings
     * @return all found http request bindings
     */
    fun requestBindings(operationShape: OperationShape): List

    /**
     * Return response bindings for an operation.
     * @param shape [Shape] for which to retrieve bindings
     * @return all found http response bindings
     */
    fun responseBindings(shape: Shape): List

    /**
     * Determine the request content type for the protocol.
     *
     * @param operationShape [OperationShape] associated with content type
     * @return content type or null if no content-type header should be set
     */
    fun determineRequestContentType(operationShape: OperationShape): String?

    /**
     * Determine the timestamp format depending on input parameter values.
     *
     * @param member id of member
     * @param location location in the request for timestamp format
     * @param defaultFormat default value
     */
    fun determineTimestampFormat(
        member: ToShapeId,
        location: HttpBinding.Location,
        defaultFormat: TimestampFormatTrait.Format,
    ): TimestampFormatTrait.Format
}

/**
 * @return true if the operation contains request data bound to the PAYLOAD or DOCUMENT locations
 */
fun HttpBindingResolver.hasHttpBody(operationShape: OperationShape): Boolean =
    requestBindings(operationShape).any {
        it.location == HttpBinding.Location.PAYLOAD || it.location == HttpBinding.Location.DOCUMENT
    }

/**
 * Protocol content type mappings
 */
data class ProtocolContentTypes(
    val requestContentType: String? = null,
    val responseContentType: String? = null,
    val eventStreamContentType: String? = null,
) {
    companion object {
        /**
         * Create an instance of [ProtocolContentTypes] where all content types are the same
         */
        fun consistent(contentType: String) = ProtocolContentTypes(contentType, contentType, contentType)
    }
}

/**
 * A Http Binding Resolver that relies on [HttpTrait] data from service models.
 * @param model Model
 * @param serviceShape service under which to find bindings
 * @param contentTypes content-type mappings
 */
class HttpTraitResolver(
    private val model: Model,
    private val serviceShape: ServiceShape,
    private val contentTypes: ProtocolContentTypes,
) : HttpBindingResolver {
    /**
     * @param ctx [ProtocolGenerator.GenerationContext]
     * @param defaultContentType content-type
     */
    constructor(
        ctx: ProtocolGenerator.GenerationContext,
        defaultContentType: String,
    ) : this(ctx.model, ctx.service, ProtocolContentTypes.consistent(defaultContentType))

    private val bindingIndex: HttpBindingIndex = HttpBindingIndex.of(model)
    private val topDownIndex: TopDownIndex = TopDownIndex.of(model)

    override fun bindingOperations(): List = topDownIndex
        .getContainedOperations(serviceShape)
        .filter { op -> op.hasTrait() }
        .toList()

    override fun httpTrait(operationShape: OperationShape): HttpTrait = operationShape.expectTrait()

    override fun requestBindings(operationShape: OperationShape): List = bindingIndex
        .getRequestBindings(operationShape)
        .values
        .map { HttpBindingDescriptor(it) }

    override fun responseBindings(shape: Shape): List = when (shape) {
        is OperationShape,
        is StructureShape,
        -> bindingIndex.getResponseBindings(shape.toShapeId()).values.map { HttpBindingDescriptor(it) }
        else -> error { "Unimplemented resolving bindings for ${shape.javaClass.canonicalName}" }
    }

    override fun determineRequestContentType(operationShape: OperationShape): String? = bindingIndex
        .determineRequestContentType(operationShape, contentTypes.requestContentType, contentTypes.eventStreamContentType)
        .getOrNull()

    override fun determineTimestampFormat(
        member: ToShapeId,
        location: HttpBinding.Location,
        defaultFormat: TimestampFormatTrait.Format,
    ): TimestampFormatTrait.Format =
        bindingIndex.determineTimestampFormat(member, location, defaultFormat)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy