Skip to content

Commit

Permalink
Include function annotations in request attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
dewantawsif committed Jan 8, 2025
1 parent 278765b commit d24d726
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 7 deletions.
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
31 changes: 31 additions & 0 deletions docs/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,34 @@ 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 getCommentsById(
@Query("message") message: String,
): List<Comment>
```

```kotlin
val MyAuthPlugin = createClientPlugin("MyAuthPlugin", ::MyAuthPluginConfig) {
onRequest { request, _ ->
val auth = request.annotations.firstInstanceOrNull<AuthRequired>() ?: 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
}
```
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()

0 comments on commit d24d726

Please sign in to comment.