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

Add Tag annotation #466

Merged
merged 5 commits into from
Oct 21, 2023
Merged
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
3 changes: 3 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
13 changes: 13 additions & 0 deletions docs/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +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<Post>
```

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:
Expand Down
4 changes: 4 additions & 0 deletions ktorfit-annotations/api/android/ktorfit-annotations.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}

4 changes: 4 additions & 0 deletions ktorfit-annotations/api/jvm/ktorfit-annotations.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}

Original file line number Diff line number Diff line change
@@ -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")

Original file line number Diff line number Diff line change
Expand Up @@ -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()
}


Expand Down Expand Up @@ -60,6 +61,10 @@ fun KSValueParameter.getParamAnnotationList(logger: KSPLogger): List<ParameterAn
paramAnnos.add(it)
}

ksValueParameter.getTagAnnotation()?.let {
paramAnnos.add(it)
}

ksValueParameter.getPathAnnotation()?.let {
if (ksValueParameter.type.resolve().isMarkedNullable) {
logger.error(KtorfitError.PATH_PARAMETER_TYPE_MAY_NOT_BE_NULLABLE, ksValueParameter.type)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.jensklingenberg.ktorfit.reqBuilderExtension

import de.jensklingenberg.ktorfit.model.ParameterData
import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation

fun getAttributeCode(parameterDataList: List<ParameterData>): String {
return parameterDataList.filter { it.hasAnnotation<ParameterAnnotation.Tag>() }
.joinToString("\n") {
val tag = it.findAnnotationOrNull<ParameterAnnotation.Tag>()!!
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})"
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

}
"""
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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) } """

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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading