From 9b626a86b256998fe3171ec3d7fc0caf47816079 Mon Sep 17 00:00:00 2001 From: Jens Klingenberg Date: Sat, 21 Oct 2023 12:32:34 +0200 Subject: [PATCH 1/5] Add Tag annotation --- docs/CHANGELOG.md | 3 ++ docs/requests.md | 12 +++++ .../de/jensklingenberg/ktorfit/http/Tag.kt | 18 ++++++++ .../model/annotations/ParameterAnnotation.kt | 5 +++ .../AttributeCodeGenerator.kt | 17 +++++++ .../ReqBuilderExtensionNode.kt | 5 ++- .../ktorfit/utils/KSValueParameterExt.kt | 7 +++ .../ktorfit/HeaderAnnotationsTest.kt | 6 +-- .../ktorfit/HeadersAnnotationsTest.kt | 5 +-- .../ktorfit/QueryAnnotationsTest.kt | 1 - .../ktorfit/ReqBuilderAnnotationsTest.kt | 11 +++-- .../ktorfit/RequestTypeTest.kt | 6 +-- .../ktorfit/TagAnnotationsTest.kt | 45 +++++++++++++++++++ .../GetBodyDataTextKtTest.kt | 3 +- .../GetRequestBuilderTextKtTest.kt | 3 +- 15 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Tag.kt create mode 100644 ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/AttributeCodeGenerator.kt create mode 100644 ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a9a41692a..71a87c67c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,9 @@ But there is no intent to bump the Ktorfit major version for every KSP update. ## [Unreleased] ======================================== +### Added +- Add support for Tag annotation https://foso.github.io/Ktorfit/requests/#tag + ### Changed - Removed unnecessary safe call warnings from generated code for the Headers [#460](https://github.com/Foso/Ktorfit/issues/460) diff --git a/docs/requests.md b/docs/requests.md index 6c93ed793..dd5a11622 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -108,7 +108,19 @@ suspend fun signup( To send FormData you can use @Field or @FieldMap. Your function needs to be annotated with @FormUrlEncoded. +## Tag +Tag can be used to add a tag to a request. +```kotlin +@GET("posts") +fun getPosts(@Tag("myTag") tag: String): List +``` + +You can then access the tag from the attributes of a Ktor HttpClientCall + +```kotlin +val test = response.call.attributes[AttributeKey("myTag")] +``` ## Multipart To send Multipart data you have two options: diff --git a/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Tag.kt b/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Tag.kt new file mode 100644 index 000000000..a97f20e8c --- /dev/null +++ b/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Tag.kt @@ -0,0 +1,18 @@ +package de.jensklingenberg.ktorfit.http + +/** + * Adds the argument instance as a request tag using the type as a AttributeKey. + * + * ``` + * @GET("/") + * fun foo(@Tag tag: String) + * ``` + * + * Tag arguments may be `null` which will omit them from the request. + * + * @param value Will be used as the name for the attribute key. The default value will be replaced with the name of the parameter that is annotated. + * + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +annotation class Tag(val value: String = "KTORFIT_DEFAULT_VALUE") + diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/ParameterAnnotation.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/ParameterAnnotation.kt index 636c135a6..54d70d4ca 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/ParameterAnnotation.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/ParameterAnnotation.kt @@ -24,6 +24,7 @@ sealed class ParameterAnnotation{ data class FieldMap(val encoded: Boolean) : ParameterAnnotation() data class Part(val value: String = "", val encoding: String = "binary") : ParameterAnnotation() data class PartMap(val encoding: String = "binary") : ParameterAnnotation() + data class Tag(val value: String) : ParameterAnnotation() } @@ -60,6 +61,10 @@ fun KSValueParameter.getParamAnnotationList(logger: KSPLogger): List): String { + return parameterDataList.filter { it.hasAnnotation() } + .joinToString("\n") { + val tag = it.findAnnotationOrNull()!! + if (it.type.isNullable) { + "${it.name}?.let{ attributes.put(io.ktor.util.AttributeKey(\"${tag.value}\"), it) }" + } else { + "attributes.put(io.ktor.util.AttributeKey(\"${tag.value}\"), ${it.name})" + } + } +} + 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 490f25934..c29e7328d 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 @@ -40,13 +40,16 @@ fun getReqBuilderExtensionText(functionData: FunctionData, resolver: Resolver): functionData.parameterDataList, listType, arrayType ) - val url = getUrlCode(functionData.parameterDataList, functionData.httpMethodAnnotation, queryCode) + val url = + getUrlCode(functionData.parameterDataList, functionData.httpMethodAnnotation, queryCode) val customReqBuilder = getCustomRequestBuilderText(functionData.parameterDataList) + val attributeKeys = getAttributeCode(functionData.parameterDataList) val args = listOf( method, url, body, headers, + attributeKeys, fields, parts, customReqBuilder diff --git a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/KSValueParameterExt.kt b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/KSValueParameterExt.kt index bb99a1dab..9dbabd0a4 100644 --- a/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/KSValueParameterExt.kt +++ b/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/KSValueParameterExt.kt @@ -103,6 +103,13 @@ fun KSValueParameter.getBodyAnnotation(): Body? { } } +@OptIn(KspExperimental::class) +fun KSValueParameter.getTagAnnotation(): Tag? { + return this.getAnnotationsByType(de.jensklingenberg.ktorfit.http.Tag::class).firstOrNull()?.let { + return Tag(it.value.replace(KTORFIT_DEFAULT_VALUE, this.name.safeString())) + } +} + fun KSValueParameter.getRequestTypeAnnotation(): RequestType? { val requestTypeClazz = de.jensklingenberg.ktorfit.http.RequestType::class val filteredAnnotations = this.annotations.filter { diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeaderAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeaderAnnotationsTest.kt index b299de89b..f3dcb3396 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeaderAnnotationsTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeaderAnnotationsTest.kt @@ -3,9 +3,9 @@ package de.jensklingenberg.ktorfit import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.kspSourcesDir -import de.jensklingenberg.ktorfit.model.KtorfitError.Companion.HEADER_MAP_KEYS_MUST_BE_OF_TYPE_STRING -import de.jensklingenberg.ktorfit.model.KtorfitError.Companion.HEADER_MAP_PARAMETER_TYPE_MUST_BE_MAP -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test import java.io.File diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeadersAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeadersAnnotationsTest.kt index f53031fdc..6323c07a5 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeadersAnnotationsTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/HeadersAnnotationsTest.kt @@ -3,9 +3,8 @@ package de.jensklingenberg.ktorfit import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.kspSourcesDir -import de.jensklingenberg.ktorfit.model.KtorfitError.Companion.HEADER_MAP_KEYS_MUST_BE_OF_TYPE_STRING -import de.jensklingenberg.ktorfit.model.KtorfitError.Companion.HEADER_MAP_PARAMETER_TYPE_MUST_BE_MAP -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test import java.io.File diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/QueryAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/QueryAnnotationsTest.kt index aaefd6996..5bbb579da 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/QueryAnnotationsTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/QueryAnnotationsTest.kt @@ -5,7 +5,6 @@ import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.kspSourcesDir import de.jensklingenberg.ktorfit.model.KtorfitError -import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/ReqBuilderAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/ReqBuilderAnnotationsTest.kt index bca600fd9..5d489e96a 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/ReqBuilderAnnotationsTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/ReqBuilderAnnotationsTest.kt @@ -1,9 +1,14 @@ package de.jensklingenberg.ktorfit -import com.tschuchort.compiletesting.* +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import com.tschuchort.compiletesting.kspIncremental +import com.tschuchort.compiletesting.kspSourcesDir +import com.tschuchort.compiletesting.symbolProcessorProviders import de.jensklingenberg.ktorfit.model.KtorfitError -import org.junit.Assert -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test import java.io.File diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/RequestTypeTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/RequestTypeTest.kt index 276c6e626..87c967cde 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/RequestTypeTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/RequestTypeTest.kt @@ -3,8 +3,8 @@ import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.kspSourcesDir import de.jensklingenberg.ktorfit.getCompilation -import org.junit.Assert -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test import java.io.File @@ -19,10 +19,8 @@ class RequestTypeTest { import de.jensklingenberg.ktorfit.http.* interface TestService { - @GET("posts/{postId}/comments") suspend fun test(@RequestType(Int::class) @Path("postId") postId: String): String - } """ ) diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt new file mode 100644 index 000000000..b0a3c353e --- /dev/null +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt @@ -0,0 +1,45 @@ +package de.jensklingenberg.ktorfit + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import com.tschuchort.compiletesting.kspSourcesDir +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +class TagAnnotationsTest { + + @Test + fun whenTagsAnnotationFound_AddThemAsAttributeKey() { + + val source = SourceFile.kotlin( + "Source.kt", """ + package com.example.api +import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.Tag + +interface TestService { + @GET("posts") + suspend fun test(@Tag myTag1 : String, @Tag("myTag2") someParameter: Int?): String +} + """ + ) + + val expectedHeadersArgumentText = + """attributes.put(io.ktor.util.AttributeKey("myTag1"), myTag1) + someParameter?.let{ attributes.put(io.ktor.util.AttributeKey("myTag2"), it) } """.trimMargin() + + val compilation = getCompilation(listOf(source)) + val result = compilation.compile() + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val generatedSourcesDir = compilation.kspSourcesDir + val generatedFile = File( + generatedSourcesDir, + "/kotlin/com/example/api/_TestServiceImpl.kt" + ) + + val actualSource = generatedFile.readText() + assertTrue(actualSource.contains(expectedHeadersArgumentText)) + } +} diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetBodyDataTextKtTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetBodyDataTextKtTest.kt index 1c87abfeb..be39aa654 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetBodyDataTextKtTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetBodyDataTextKtTest.kt @@ -3,8 +3,7 @@ package de.jensklingenberg.ktorfit.reqBuilderExtension import de.jensklingenberg.ktorfit.model.ParameterData import de.jensklingenberg.ktorfit.model.ReturnTypeData import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Body -import org.junit.Assert -import org.junit.Assert.* +import org.junit.Assert.assertEquals import org.junit.Test class GetBodyDataTextKtTest { diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetRequestBuilderTextKtTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetRequestBuilderTextKtTest.kt index 320deb7d9..74d259920 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetRequestBuilderTextKtTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/GetRequestBuilderTextKtTest.kt @@ -3,8 +3,7 @@ package de.jensklingenberg.ktorfit.reqBuilderExtension import de.jensklingenberg.ktorfit.model.ParameterData import de.jensklingenberg.ktorfit.model.ReturnTypeData import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.RequestBuilder -import org.junit.Assert -import org.junit.Assert.* +import org.junit.Assert.assertEquals import org.junit.Test class GetRequestBuilderTextKtTest { From 4f8c763731ec8b9d7ab997e8c5f4b554a0a7497b Mon Sep 17 00:00:00 2001 From: Jens Klingenberg Date: Sat, 21 Oct 2023 12:33:39 +0200 Subject: [PATCH 2/5] Add Tag annotation --- docs/requests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requests.md b/docs/requests.md index dd5a11622..bd607b9a7 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -119,7 +119,7 @@ fun getPosts(@Tag("myTag") tag: String): List You can then access the tag from the attributes of a Ktor HttpClientCall ```kotlin -val test = response.call.attributes[AttributeKey("myTag")] +val myTag = response.call.attributes[AttributeKey("myTag")] ``` ## Multipart To send Multipart data you have two options: From b3a47139f0d444177bdd4cf8b408cfa3782ec73a Mon Sep 17 00:00:00 2001 From: Jens Klingenberg Date: Sat, 21 Oct 2023 12:33:49 +0200 Subject: [PATCH 3/5] Add Tag annotation --- docs/requests.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requests.md b/docs/requests.md index bd607b9a7..d0f29b716 100644 --- a/docs/requests.md +++ b/docs/requests.md @@ -121,6 +121,7 @@ You can then access the tag from the attributes of a Ktor HttpClientCall ```kotlin val myTag = response.call.attributes[AttributeKey("myTag")] ``` + ## Multipart To send Multipart data you have two options: From 1e6484c4913f62fc7e5d2c2ab06d76e56e1a27d8 Mon Sep 17 00:00:00 2001 From: Jens Klingenberg Date: Sat, 21 Oct 2023 12:35:17 +0200 Subject: [PATCH 4/5] Add Tag annotation --- .../kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt index b0a3c353e..892033775 100644 --- a/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt +++ b/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/TagAnnotationsTest.kt @@ -28,7 +28,7 @@ interface TestService { val expectedHeadersArgumentText = """attributes.put(io.ktor.util.AttributeKey("myTag1"), myTag1) - someParameter?.let{ attributes.put(io.ktor.util.AttributeKey("myTag2"), it) } """.trimMargin() + someParameter?.let{ attributes.put(io.ktor.util.AttributeKey("myTag2"), it) } """ val compilation = getCompilation(listOf(source)) val result = compilation.compile() From 1ebde2a35e7c7eb59fbdad970a060a86c481b32f Mon Sep 17 00:00:00 2001 From: Jens Klingenberg Date: Sat, 21 Oct 2023 12:39:03 +0200 Subject: [PATCH 5/5] Add Tag annotation --- ktorfit-annotations/api/android/ktorfit-annotations.api | 4 ++++ ktorfit-annotations/api/jvm/ktorfit-annotations.api | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ktorfit-annotations/api/android/ktorfit-annotations.api b/ktorfit-annotations/api/android/ktorfit-annotations.api index 98df29bb5..98d529ae6 100644 --- a/ktorfit-annotations/api/android/ktorfit-annotations.api +++ b/ktorfit-annotations/api/android/ktorfit-annotations.api @@ -97,6 +97,10 @@ public abstract interface annotation class de/jensklingenberg/ktorfit/http/Reque public abstract interface annotation class de/jensklingenberg/ktorfit/http/Streaming : java/lang/annotation/Annotation { } +public abstract interface annotation class de/jensklingenberg/ktorfit/http/Tag : java/lang/annotation/Annotation { + public abstract fun value ()Ljava/lang/String; +} + public abstract interface annotation class de/jensklingenberg/ktorfit/http/Url : java/lang/annotation/Annotation { } diff --git a/ktorfit-annotations/api/jvm/ktorfit-annotations.api b/ktorfit-annotations/api/jvm/ktorfit-annotations.api index 98df29bb5..98d529ae6 100644 --- a/ktorfit-annotations/api/jvm/ktorfit-annotations.api +++ b/ktorfit-annotations/api/jvm/ktorfit-annotations.api @@ -97,6 +97,10 @@ public abstract interface annotation class de/jensklingenberg/ktorfit/http/Reque public abstract interface annotation class de/jensklingenberg/ktorfit/http/Streaming : java/lang/annotation/Annotation { } +public abstract interface annotation class de/jensklingenberg/ktorfit/http/Tag : java/lang/annotation/Annotation { + public abstract fun value ()Ljava/lang/String; +} + public abstract interface annotation class de/jensklingenberg/ktorfit/http/Url : java/lang/annotation/Annotation { }