Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include function annotations in request attribute #759

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions docs/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Comment>
```

```kotlin
val MyAuthPlugin = createClientPlugin("MyAuthPlugin", ::MyAuthPluginConfig) {
onRequest { request, _ ->
val auth = request.annotations.firstInstanceOrNull<AuthRequired>() ?: return@onRequest

val token = [email protected]
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
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,7 +42,8 @@ data class FunctionData(
val annotations: List<FunctionAnnotation> = emptyList(),
val httpMethodAnnotation: HttpMethodAnnotation,
val modifiers: List<KModifier> = emptyList(),
val optInAnnotations: List<AnnotationSpec>
val rawAnnotations: List<AnnotationSpec>,
val rawOptInAnnotations: List<AnnotationSpec>,
)

/**
Expand Down Expand Up @@ -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,
Expand All @@ -300,6 +304,7 @@ fun KSFunctionDeclaration.toFunctionData(
functionAnnotationList,
firstHttpMethodAnnotation,
modifiers,
optInAnnotations
rawAnnotations,
rawOptInAnnotation,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AnnotationSpec>): 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)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fun getReqBuilderExtensionText(
listType: KSType,
arrayType: KSType,
): String {
val attributes = getAttributesCode(functionData.rawAnnotations)
val method = getMethodCode(functionData.httpMethodAnnotation)

val headers =
Expand Down Expand Up @@ -47,6 +48,7 @@ fun getReqBuilderExtensionText(
val attributeKeys = getAttributeCode(functionData.parameterDataList)
val args =
listOf(
attributes,
method,
url,
body,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.jensklingenberg.ktorfit

import io.ktor.client.request.HttpRequestBuilder
import io.ktor.util.AttributeKey

public val annotationsAttributeKey: AttributeKey<List<Any>> = AttributeKey("annotations")

public val HttpRequestBuilder.annotations: List<Any>
get() = attributes.getOrNull(annotationsAttributeKey) ?: emptyList()
Loading