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

com.ancientlightstudios.quarkus.kotlin.openapi.emitter.ServerRestInterfaceEmitter.kt Maven / Gradle / Ivy

package com.ancientlightstudios.quarkus.kotlin.openapi.emitter

import com.ancientlightstudios.quarkus.kotlin.openapi.emitter.deserialization.CombineIntoObjectStatementEmitter
import com.ancientlightstudios.quarkus.kotlin.openapi.emitter.deserialization.Deserialization.getDeserializationSourceType
import com.ancientlightstudios.quarkus.kotlin.openapi.emitter.deserialization.DeserializationStatementEmitter
import com.ancientlightstudios.quarkus.kotlin.openapi.inspection.RequestBundleInspection
import com.ancientlightstudios.quarkus.kotlin.openapi.inspection.RequestInspection
import com.ancientlightstudios.quarkus.kotlin.openapi.inspection.inspect
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.ParameterVariableNameHint.parameterVariableName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.RequestContainerClassNameHint.requestContainerClassName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.RequestMethodNameHint.requestMethodName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.RequestContextClassNameHint.requestContextClassName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.ServerDelegateClassNameHint.serverDelegateClassName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.ServerRestInterfaceClassNameHint.serverRestInterfaceClassName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.hints.TypeUsageHint.typeUsage
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.*
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.InvocationExpression.Companion.invoke
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.MethodName.Companion.methodName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.PropertyExpression.Companion.property
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.TryCatchExpression.Companion.tryExpression
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.TypeName.GenericTypeName.Companion.of
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.TypeName.SimpleTypeName.Companion.typeName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.VariableName.Companion.rawVariableName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.kotlin.VariableName.Companion.variableName
import com.ancientlightstudios.quarkus.kotlin.openapi.models.transformable.ContentType
import com.ancientlightstudios.quarkus.kotlin.openapi.models.transformable.TransformableBody
import com.ancientlightstudios.quarkus.kotlin.openapi.models.transformable.TransformableParameter
import com.ancientlightstudios.quarkus.kotlin.openapi.models.transformable.TransformableRequest
import com.ancientlightstudios.quarkus.kotlin.openapi.models.types.ObjectTypeDefinition
import com.ancientlightstudios.quarkus.kotlin.openapi.models.types.TypeUsage
import com.ancientlightstudios.quarkus.kotlin.openapi.refactoring.AssignContentTypesRefactoring.Companion.getContentTypeForFormPart
import com.ancientlightstudios.quarkus.kotlin.openapi.utils.ProbableBug

class ServerRestInterfaceEmitter(private val pathPrefix: String) : CodeEmitter {

    private lateinit var emitterContext: EmitterContext

    override fun EmitterContext.emit() {
        emitterContext = this
        spec.inspect {
            bundles {
                emitRestInterfaceFile()
                    .writeFile()
            }
        }
    }

    private fun RequestBundleInspection.emitRestInterfaceFile() = kotlinFile(bundle.serverRestInterfaceClassName) {
        registerImports(Library.AllClasses)
        registerImports(emitterContext.getAdditionalImports())

        kotlinClass(fileName) {
            addPathAnnotation(pathPrefix)

            kotlinMember("delegate".variableName(), bundle.serverDelegateClassName.typeName())
            kotlinMember("objectMapper".variableName(), Misc.ObjectMapperClass.typeName())

            requests {
                emitRequest(this@kotlinClass)
            }
        }
    }

    private fun RequestInspection.emitRequest(containerClass: KotlinClass) = with(containerClass) {
        kotlinMethod(request.requestMethodName, true, Misc.RestResponseClass.typeName().of(Kotlin.Star.typeName())) {
            addRequestMethodAnnotation(request.method)
            addPathAnnotation(request.path)

            val requestContainerParts = mutableListOf()
            parameters { requestContainerParts.add(emitParameter(parameter)) }
            body { requestContainerParts.add(emitBody(body)) }

            val requestContainerName = emitterContext.runEmitter(
                CombineIntoObjectStatementEmitter(
                    "request".literal(), request.requestContainerClassName, listOf(), requestContainerParts
                )
            ).resultStatement?.declaration("request".variableName())
            emitDelegateInvocation(request, requestContainerName, request.requestContextClassName)
        }
    }

    // generates parameters and conversion code for path, query, header and cookie parameters
    private fun KotlinMethod.emitParameter(parameter: TransformableParameter): VariableName {
        val parameterKind = parameter.kind

        return emitMethodParameter(
            parameter.parameterVariableName,
            parameter.content.typeUsage.getDeserializationSourceType(),
            getSourceAnnotation(parameterKind, parameter.name),
            "request.${parameterKind.value}.${parameter.name}".literal(),
            parameter.content.typeUsage,
            parameter.content.mappedContentType
        )
    }

    // generates parameters and conversion for the request body depending on the media type
    private fun KotlinMethod.emitBody(body: TransformableBody): VariableName {
        addConsumesAnnotation(body.content.rawContentType)
        return when (body.content.mappedContentType) {
            ContentType.ApplicationJson -> emitJsonBody(body)
            ContentType.TextPlain -> emitPlainBody(body)
            ContentType.MultipartFormData -> emitMultipartBody(body)
            ContentType.ApplicationFormUrlencoded -> emitFormBody(body)
            ContentType.ApplicationOctetStream -> emitOctetBody(body)
        }
    }

    private fun KotlinMethod.emitJsonBody(body: TransformableBody): VariableName {
        return emitMethodParameter(
            body.parameterVariableName,
            Kotlin.StringClass.typeName(true),
            null,
            "request.body".literal(),
            body.content.typeUsage,
            ContentType.ApplicationJson
        )
    }

    private fun KotlinMethod.emitPlainBody(body: TransformableBody): VariableName {
        return emitMethodParameter(
            body.parameterVariableName,
            body.content.typeUsage.getDeserializationSourceType(),
            null,
            "request.body".literal(),
            body.content.typeUsage,
            ContentType.TextPlain
        )
    }

    private fun KotlinMethod.emitMultipartBody(body: TransformableBody): VariableName {
        return "multi".variableName()
    }

    private fun KotlinMethod.emitFormBody(body: TransformableBody): VariableName {
        val typeUsage = body.content.typeUsage
        val safeType = typeUsage.type

        if (safeType is ObjectTypeDefinition) {
            // create a parameter for each object property
            val parts = safeType.properties.map {
                val parameter = body.parameterVariableName.extend(prefix = it.sourceName)
                val propertyType = it.typeUsage
                val contentType = getContentTypeForFormPart(propertyType.type)
                val sourceType = typeUsage.getDeserializationSourceType()

                emitMethodParameter(
                    parameter,
                    sourceType,
                    KotlinAnnotation(Jakarta.FormParamAnnotationClass, null to it.sourceName.literal()),
                    "request.body.${it.sourceName}".literal(),
                    propertyType,
                    contentType
                )
            }

            return emitterContext.runEmitter(
                CombineIntoObjectStatementEmitter("request.body".literal(), safeType.modelName, listOf(), parts)
            ).resultStatement?.declaration(body.parameterVariableName)
                ?: ProbableBug("don't know how to deserialize form object")
        } else {
            // it's a simple type, just create a single parameter
            return emitMethodParameter(
                body.parameterVariableName,
                body.content.typeUsage.getDeserializationSourceType(),
                KotlinAnnotation(Jakarta.FormParamAnnotationClass, null to body.parameterVariableName.value.literal()),
                "request.body".literal(),
                body.content.typeUsage,
                ContentType.TextPlain
            )
        }
    }

    private fun KotlinMethod.emitOctetBody(body: TransformableBody): VariableName {
        return emitMethodParameter(
            body.parameterVariableName,
            Kotlin.ByteArrayClass.typeName(true),
            null,
            "request.body".literal(),
            body.content.typeUsage,
            ContentType.ApplicationOctetStream
        )
    }

    // generates the call to the delegate
    private fun KotlinMethod.emitDelegateInvocation(
        request: TransformableRequest,
        requestContainerName: VariableName?,
        requestContextClassName: ClassName
    ) {
        val arguments = when (requestContainerName) {
            null -> listOf("objectMapper".variableName())
            else -> listOf(requestContainerName, "objectMapper".variableName())
        }

        val requestContextName = invoke(requestContextClassName.constructorName, arguments)
            .declaration("response".variableName())

        tryExpression {
            "delegate".variableName().invoke("apply".methodName()) {
                requestContextName.invoke(request.requestMethodName).statement()
            }.statement()

            val signalName = "requestHandledSignal".rawVariableName()
            catchBlock(Library.RequestHandledSignalClass, signalName) {
                signalName.property("response".variableName()).returnStatement()
            }
        }.statement()
    }

    // adds a new parameter to the current method and generates some deserialization statements for it
    private fun KotlinMethod.emitMethodParameter(
        parameterName: VariableName, parameterType: TypeName, parameterAnnotation: KotlinAnnotation?,
        context: KotlinExpression, typeUsage: TypeUsage, contentType: ContentType
    ): VariableName {
        kotlinParameter(parameterName, parameterType) { parameterAnnotation?.let { addAnnotation(it) } }

        // produces
        //
        // Maybe.Success(, )
        val statement = invoke(Library.MaybeSuccessClass.constructorName, context, parameterName).wrap()

        // produces
        //
        // val Maybe = 
        //     .
        return emitterContext.runEmitter(
            DeserializationStatementEmitter(typeUsage, statement, contentType, true)
        ).resultStatement.declaration(parameterName.extend(postfix = "maybe"))
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy