diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 35e6a2e40..84b88c643 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,15 @@ and this project orients towards [Semantic Versioning](http://semver.org/spec/v2 Note: This project needs KSP to work and every new Ktorfit with an update of the KSP version is technically a breaking change. But there is no intent to bump the Ktorfit major version for every KSP update. +# [Unreleased]() +* Supported Kotlin version: +* Supported KSP version: +* Ktor version: + +## Added +- Include function annotations in request attribute +See https://foso.github.io/Ktorfit/requests/#annotations + # [2.2.0]() * Supported Kotlin version: 2.0.0; 2.0.10; 2.0.20, 2.1.0-Beta1; 2.0.21-RC, 2.0.21, 2.1.0-RC, 2.1.0-RC2, 2.1.0 * Supported KSP version: 1.0.27, 1.0.28, 1.0.29 diff --git a/docs/requests.md b/docs/requests.md index d0f29b716..796d8f09b 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -224,3 +224,35 @@ val result = secondApi.getCommentsById("3") { ``` Then you can use the extension function to set additional configuration. The RequestBuilder will be applied last after everything that is set by Ktorfit + +## Annotations +Function annotations are available in the request object with their respective values via the `annotation` extension (`HttpRequestBuilder.annotations`) + +Do note that `OptIn` annotation is not included in the returned list + +```kotlin +@AuthRequired(optional = true) +@POST("comments") +suspend fun postComment( + @Query("issue") issue: String, + @Query("message") message: String, +): List +``` + +```kotlin +val MyAuthPlugin = createClientPlugin("MyAuthPlugin", ::MyAuthPluginConfig) { + onRequest { request, _ -> + val auth = request.annotations.firstInstanceOrNull() ?: return@onRequest + + val token = this@createClientPlugin.pluginConfig.token + if (!auth.optional && token == null) throw Exception("Need to be logged in") + + token?.let { request.headers.append("Authorization", "Bearer $it") } + + } +} + +class MyAuthPluginConfig { + var token: String? = null +} +``` diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/ClassData.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/ClassData.kt index 4cba3dd7d..0fd263db0 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/ClassData.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/ClassData.kt @@ -49,6 +49,7 @@ fun KSClassDeclaration.toClassData(logger: KSPLogger): ClassData { "io.ktor.http.URLBuilder", "io.ktor.http.takeFrom", "io.ktor.http.decodeURLQueryComponent", + annotationsAttributeKey.packageName + "." + annotationsAttributeKey.name, typeDataClass.packageName + "." + typeDataClass.name, ) diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/FunctionData.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/FunctionData.kt index e7750edf1..2294f93c5 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/FunctionData.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/FunctionData.kt @@ -32,6 +32,7 @@ import de.jensklingenberg.ktorfit.utils.getStreamingAnnotation import de.jensklingenberg.ktorfit.utils.isSuspend import de.jensklingenberg.ktorfit.utils.parseHTTPMethodAnno import de.jensklingenberg.ktorfit.utils.resolveTypeName +import de.jensklingenberg.ktorfit.utils.toClassName data class FunctionData( val name: String, @@ -41,7 +42,8 @@ data class FunctionData( val annotations: List = emptyList(), val httpMethodAnnotation: HttpMethodAnnotation, val modifiers: List = emptyList(), - val optInAnnotations: List + val rawAnnotations: List, + val rawOptInAnnotations: List, ) /** @@ -286,12 +288,14 @@ fun KSFunctionDeclaration.toFunctionData( } } - val optInAnnotations = - funcDeclaration.annotations - .filter { it.shortName.getShortName() == "OptIn" } + val annotations = funcDeclaration.annotations .map { it.toAnnotationSpec() } .toList() + val (rawOptInAnnotation, rawAnnotations) = annotations.partition { it.toClassName().simpleName == "OptIn" } + + rawAnnotations.forEach { addImport(it.toClassName().canonicalName) } + return FunctionData( functionName, returnType, @@ -300,6 +304,7 @@ fun KSFunctionDeclaration.toFunctionData( functionAnnotationList, firstHttpMethodAnnotation, modifiers, - optInAnnotations + rawAnnotations, + rawOptInAnnotation, ) } diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/KtorfitClass.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/KtorfitClass.kt index 4f3a9754b..d6dcfc4c6 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/KtorfitClass.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/KtorfitClass.kt @@ -16,5 +16,6 @@ val formParameters = KtorfitClass("", "", "__formParameters") val formData = KtorfitClass("", "", "__formData") val converterHelper = KtorfitClass("KtorfitConverterHelper", "de.jensklingenberg.ktorfit.internal", "_helper") val internalApi = ClassName("de.jensklingenberg.ktorfit.internal", "InternalKtorfitApi") +val annotationsAttributeKey = KtorfitClass("annotationsAttributeKey", "de.jensklingenberg.ktorfit", "") fun KtorfitClass.toClassName() = ClassName(packageName, name) diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/poetspec/FunctionSpec.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/poetspec/FunctionSpec.kt index e3676fe0d..a66d01c9f 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/poetspec/FunctionSpec.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/poetspec/FunctionSpec.kt @@ -22,12 +22,13 @@ fun FunctionData.toFunSpec( return FunSpec .builder(name) .addModifiers(modifiers) - .addAnnotations(optInAnnotations) + .addAnnotations(rawOptInAnnotations) .addParameters( parameterDataList.map { it.parameterSpec() }, - ).addBody(this, resolver, setQualifiedTypeName, returnTypeName) + ) + .addBody(this, resolver, setQualifiedTypeName, returnTypeName) .returns(returnTypeName) .build() } diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/AttributesCodeGeneration.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/AttributesCodeGeneration.kt new file mode 100644 index 000000000..a7a7e12b1 --- /dev/null +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/AttributesCodeGeneration.kt @@ -0,0 +1,23 @@ +package de.jensklingenberg.ktorfit.reqBuilderExtension + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ksp.toAnnotationSpec +import de.jensklingenberg.ktorfit.model.annotations.CustomHttp +import de.jensklingenberg.ktorfit.model.annotations.HttpMethodAnnotation +import de.jensklingenberg.ktorfit.model.annotationsAttributeKey +import de.jensklingenberg.ktorfit.utils.toClassName + +fun getAttributesCode(rawAnnotation: List): String { + val annotations = rawAnnotation.joinToString( + separator = ",\n", + prefix = "listOf(\n", + postfix = ",\n)", + ) { annotation -> + annotation + .members + .joinToString { it.toString() } + .let { "${annotation.toClassName().simpleName}($it)" } + } + + return "attributes.put(${annotationsAttributeKey.name}, $annotations)" +} diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/ReqBuilderExtensionNode.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/ReqBuilderExtensionNode.kt index 5cb46ffb0..83b3d3925 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/ReqBuilderExtensionNode.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/ReqBuilderExtensionNode.kt @@ -12,6 +12,7 @@ fun getReqBuilderExtensionText( listType: KSType, arrayType: KSType, ): String { + val attributes = getAttributesCode(functionData.rawAnnotations) val method = getMethodCode(functionData.httpMethodAnnotation) val headers = @@ -47,6 +48,7 @@ fun getReqBuilderExtensionText( val attributeKeys = getAttributeCode(functionData.parameterDataList) val args = listOf( + attributes, method, url, body, diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/AnnotationSpecExt.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/AnnotationSpecExt.kt new file mode 100644 index 000000000..ad78d15b6 --- /dev/null +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/AnnotationSpecExt.kt @@ -0,0 +1,13 @@ +package de.jensklingenberg.ktorfit.utils + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName + +fun AnnotationSpec.toClassName(): ClassName { + return if (typeName is ClassName) { + typeName as ClassName + } else { + (typeName as ParameterizedTypeName).rawType + } +} diff --git a/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/annotations.kt b/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/annotations.kt new file mode 100644 index 000000000..31e2566ff --- /dev/null +++ b/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/annotations.kt @@ -0,0 +1,9 @@ +package de.jensklingenberg.ktorfit + +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.util.AttributeKey + +public val annotationsAttributeKey: AttributeKey> = AttributeKey("annotations") + +public val HttpRequestBuilder.annotations: List + get() = attributes.getOrNull(annotationsAttributeKey) ?: emptyList()