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

main.de.jensklingenberg.ktorfit.model.FunctionData.kt Maven / Gradle / Ivy

There is a newer version: 2.2.0-1.0.28
Show newest version
package de.jensklingenberg.ktorfit.model

import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import de.jensklingenberg.ktorfit.model.annotations.FunctionAnnotation
import de.jensklingenberg.ktorfit.model.annotations.HttpMethod
import de.jensklingenberg.ktorfit.model.annotations.HttpMethodAnnotation
import de.jensklingenberg.ktorfit.model.annotations.Multipart
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Body
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Field
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.FieldMap
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Path
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Url
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.RequestBuilder
import de.jensklingenberg.ktorfit.utils.anyInstance
import de.jensklingenberg.ktorfit.utils.getFormUrlEncodedAnnotation
import de.jensklingenberg.ktorfit.utils.getHeaderAnnotation
import de.jensklingenberg.ktorfit.utils.getMultipartAnnotation
import de.jensklingenberg.ktorfit.utils.getStreamingAnnotation
import de.jensklingenberg.ktorfit.utils.isSuspend
import de.jensklingenberg.ktorfit.utils.parseHTTPMethodAnno
import de.jensklingenberg.ktorfit.utils.resolveTypeName

data class FunctionData(
    val name: String,
    val returnType: ReturnTypeData,
    val isSuspend: Boolean = false,
    val parameterDataList: List,
    val annotations: List = emptyList(),
    val httpMethodAnnotation: HttpMethodAnnotation,
)

/**
 * Collect all [HttpMethodAnnotation] from a [KSFunctionDeclaration]
 * @return list of [HttpMethodAnnotation]
 */
private fun getHttpMethodAnnotations(ksFunctionDeclaration: KSFunctionDeclaration): List {
    val getAnno = ksFunctionDeclaration.parseHTTPMethodAnno("GET")
    val putAnno = ksFunctionDeclaration.parseHTTPMethodAnno("PUT")
    val postAnno = ksFunctionDeclaration.parseHTTPMethodAnno("POST")
    val deleteAnno = ksFunctionDeclaration.parseHTTPMethodAnno("DELETE")
    val headAnno = ksFunctionDeclaration.parseHTTPMethodAnno("HEAD")
    val optionsAnno = ksFunctionDeclaration.parseHTTPMethodAnno("OPTIONS")
    val patchAnno = ksFunctionDeclaration.parseHTTPMethodAnno("PATCH")
    val httpAnno = ksFunctionDeclaration.parseHTTPMethodAnno("HTTP")

    return listOfNotNull(getAnno, postAnno, putAnno, deleteAnno, headAnno, optionsAnno, patchAnno, httpAnno)
}

fun KSFunctionDeclaration.toFunctionData(logger: KSPLogger): FunctionData {
    val funcDeclaration = this
    val functionName = funcDeclaration.simpleName.asString()
    val functionParameters = funcDeclaration.parameters.map { it.createParameterData(logger) }

    val resolvedReturnType =
        funcDeclaration.returnType?.resolve() ?: throw IllegalStateException("Return type not found")

    val returnType =
        ReturnTypeData(
            name = resolvedReturnType.resolveTypeName(),
            parameterType = resolvedReturnType,
        )

    val functionAnnotationList = mutableListOf()

    funcDeclaration.getMultipartAnnotation()?.let {
        functionAnnotationList.add(it)
    }

    if (funcDeclaration.typeParameters.isNotEmpty()) {
        logger.error(
            KtorfitError.FUNCTION_OR_PARAMETERS_TYPES_MUST_NOT_INCLUDE_ATYPE_VARIABLE_OR_WILDCARD,
            funcDeclaration,
        )
    }

    funcDeclaration.getHeaderAnnotation()?.let { headers ->
        headers.value.forEach {
            // Check if headers are in valid format
            try {
                val (key, value) = it.split(":")
            } catch (exception: Exception) {
                logger.error(KtorfitError.HEADERS_VALUE_MUST_BE_IN_FORM + it, funcDeclaration)
            }
        }
        functionAnnotationList.add(headers)
    }

    funcDeclaration.getFormUrlEncodedAnnotation()?.let { formUrlEncoded ->
        val isWithoutFieldOrFieldMap =
            functionParameters.none { it.hasAnnotation() || it.hasAnnotation() }
        if (isWithoutFieldOrFieldMap) {
            logger.error(
                KtorfitError.FORM_ENCODED_METHOD_MUST_CONTAIN_AT_LEAST_ONE_FIELD_OR_FIELD_MAP,
                funcDeclaration,
            )
        }

        if (funcDeclaration.getMultipartAnnotation() != null) {
            logger.error(KtorfitError.ONLY_ONE_ENCODING_ANNOTATION_IS_ALLOWED, funcDeclaration)
        }

        functionAnnotationList.add(formUrlEncoded)
    }

    funcDeclaration.getStreamingAnnotation()?.let { streaming ->
        val returnsHttpStatement = returnType.name == "HttpStatement"
        if (!returnsHttpStatement) {
            logger.error(
                KtorfitError.FOR_STREAMING_THE_RETURN_TYPE_MUST_BE_HTTP_STATEMENT,
                funcDeclaration,
            )
        }
        functionAnnotationList.add(streaming)
    }

    val httpMethodAnnoList = getHttpMethodAnnotations(funcDeclaration)

    if (httpMethodAnnoList.isEmpty()) {
        logger.error(KtorfitError.noHttpAnnotationAt(functionName), funcDeclaration)
    }

    if (httpMethodAnnoList.size > 1) {
        logger.error(
            KtorfitError.ONLY_ONE_HTTP_METHOD_IS_ALLOWED + "Found: " +
                httpMethodAnnoList.joinToString {
                    it.httpMethod.keyword
                } + " at " + functionName,
            funcDeclaration,
        )
    }

    val firstHttpMethodAnnotation = httpMethodAnnoList.first()

    val isEmptyHttpPathWithoutUrlAnnotation =
        firstHttpMethodAnnotation.path.isEmpty() && functionParameters.none { it.hasAnnotation() }
    if (isEmptyHttpPathWithoutUrlAnnotation) {
        logger.error(
            KtorfitError.missingEitherKeywordUrlOrUrlParameter(firstHttpMethodAnnotation.httpMethod.keyword),
            funcDeclaration,
        )
    }

    if (functionParameters.filter { it.hasAnnotation() }.size > 1) {
        logger.error(
            KtorfitError.ONLY_ONE_REQUEST_BUILDER_IS_ALLOWED + " Found: " + httpMethodAnnoList.joinToString { it.toString() } + " at " +
                functionName,
            funcDeclaration,
        )
    }

    when (firstHttpMethodAnnotation.httpMethod) {
        HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH -> {}
        else -> {
            if (functionAnnotationList.anyInstance()) {
                logger.error(
                    KtorfitError.MULTIPART_CAN_ONLY_BE_SPECIFIED_ON_HTTPMETHODS,
                    funcDeclaration,
                )
            }

            if (funcDeclaration.getFormUrlEncodedAnnotation() != null) {
                logger.error(
                    KtorfitError.FORM_URL_ENCODED_CAN_ONLY_BE_SPECIFIED_ON_HTTP_METHODS_WITH_REQUEST_BODY,
                    funcDeclaration,
                )
            }
        }
    }

    if (functionParameters.any { it.hasAnnotation() } && firstHttpMethodAnnotation.path.isEmpty()) {
        logger.error(
            KtorfitError.PATH_CAN_ONLY_BE_USED_WITH_RELATIVE_URL_ON + "@${firstHttpMethodAnnotation.httpMethod.keyword}",
            funcDeclaration,
        )
    }

    functionParameters.filter { it.hasAnnotation() }.forEach {
        val pathAnnotation = it.findAnnotationOrNull()
        if (!firstHttpMethodAnnotation.path.contains("{${pathAnnotation?.value ?: ""}}")) {
            logger.error(
                KtorfitError.missingXInRelativeUrlPath(pathAnnotation?.value.orEmpty()),
                funcDeclaration,
            )
        }
    }

    if (functionParameters.any { it.hasAnnotation() }) {
        if (functionParameters.filter { it.hasAnnotation() }.size > 1) {
            logger.error(KtorfitError.MULTIPLE_URL_METHOD_ANNOTATIONS_FOUND, funcDeclaration)
        }
        if (firstHttpMethodAnnotation.path.isNotEmpty()) {
            logger.error(
                KtorfitError.urlCanOnlyBeUsedWithEmpty(firstHttpMethodAnnotation.httpMethod.keyword),
                funcDeclaration,
            )
        }
    }

    if (functionParameters.any { it.hasAnnotation() } && funcDeclaration.getFormUrlEncodedAnnotation() == null) {
        logger.error(KtorfitError.FIELD_PARAMETERS_CAN_ONLY_BE_USED_WITH_FORM_ENCODING, funcDeclaration)
    }

    if (functionParameters.any { it.hasAnnotation() } && funcDeclaration.getFormUrlEncodedAnnotation() == null) {
        logger.error(
            KtorfitError.FIELD_MAP_PARAMETERS_CAN_ONLY_BE_USED_WITH_FORM_ENCODING,
            funcDeclaration,
        )
    }

    if (functionParameters.any { it.hasAnnotation() } && funcDeclaration.getFormUrlEncodedAnnotation() != null) {
        logger.error(
            KtorfitError.BODY_PARAMETERS_CANNOT_BE_USED_WITH_FORM_OR_MULTI_PART_ENCODING,
            funcDeclaration,
        )
    }

    return FunctionData(
        functionName,
        returnType,
        funcDeclaration.isSuspend,
        functionParameters,
        functionAnnotationList,
        firstHttpMethodAnnotation,
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy