10 | * ```
11 | */
12 | @Target(AnnotationTarget.FUNCTION)
13 | annotation class Headers(
14 | vararg val value: String
15 | )
16 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/HttpMethodAnnotation.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.model.annotations
2 |
3 | open class HttpMethodAnnotation(
4 | open val path: String,
5 | open val httpMethod: HttpMethod,
6 | ) : FunctionAnnotation()
7 |
8 | class CustomHttp(
9 | override val path: String,
10 | override val httpMethod: HttpMethod,
11 | val customValue: String,
12 | ) : HttpMethodAnnotation(path, httpMethod)
13 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/DELETE.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /** Make a DELETE request.
4 | *
5 | * ```
6 | * @DELETE("deleteIssue")
7 | * suspend fun deleteIssue(@Query("id") id: String)
8 | * ```
9 | * @param value relative url path, if empty, you need to have a parameter with [Url]
10 | *
11 | */
12 | @Target(AnnotationTarget.FUNCTION)
13 | annotation class DELETE(
14 | val value: String = ""
15 | )
16 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/QueryMap.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * Used for query parameters
5 | *
6 | * * A {@code null} value for the map, as a key is not allowed.
7 | * @param encoded true means that this value is already URL encoded and will not be encoded again
8 |
9 | */
10 | @Target(AnnotationTarget.VALUE_PARAMETER)
11 | annotation class QueryMap(
12 | val encoded: Boolean = false
13 | )
14 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/utils/AnnotationSpecExt.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.utils
2 |
3 | import com.squareup.kotlinpoet.AnnotationSpec
4 | import com.squareup.kotlinpoet.ClassName
5 | import com.squareup.kotlinpoet.ParameterizedTypeName
6 |
7 | fun AnnotationSpec.toClassName(): ClassName {
8 | return if (typeName is ClassName) {
9 | typeName as ClassName
10 | } else {
11 | (typeName as ParameterizedTypeName).rawType
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/iosApp.xcodeproj/xcuserdata/jens.klingenberg.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iosApp.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/ReqBuilder.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * This can be used to add additional configurations to a request
5 | * the parameter type has to be HttpRequestBuilder.() -> Unit
6 | *
7 | * ```
8 | * @GET("posts")
9 | * suspend fun getPosts(@ReqBuilder builder : HttpRequestBuilder.() -> Unit) : List
10 | * ```
11 | */
12 | @Target(AnnotationTarget.VALUE_PARAMETER)
13 | annotation class ReqBuilder
14 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/shared/src/commonTest/kotlin/com/example/ktorfittest/TestApi.kt:
--------------------------------------------------------------------------------
1 | package com.example.ktorfittest
2 |
3 | import de.jensklingenberg.ktorfit.http.GET
4 | import de.jensklingenberg.ktorfit.http.Path
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface TestApi {
8 | companion object {
9 | const val baseUrl = "https://swapi.dev/api/"
10 | }
11 |
12 | @GET("people/{id}/")
13 | suspend fun getPersonByIdResponse(@Path("id") peopleId: Int): Flow
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/shared/src/jvmMain/kotlin/com/example/ktorfittest/StarWarsApi.kt:
--------------------------------------------------------------------------------
1 | package com.example.ktorfittest
2 |
3 | import de.jensklingenberg.ktorfit.http.GET
4 | import de.jensklingenberg.ktorfit.http.Path
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface JvmStarWarsApi {
8 | @GET("people/{id}/")
9 | suspend fun getPersonByIdResponse(@Path("id") peopleId: Int): String
10 |
11 | @GET("people/{id}/")
12 | fun getPersonByIdFlowResponse(@Path("id") peopleId: Int): Flow
13 | }
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/Strings.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | internal class Strings {
4 | companion object {
5 | const val EXPECTED_URL_SCHEME = "Expected URL scheme 'http' or 'https' was not found"
6 | const val BASE_URL_REQUIRED = "Base URL required"
7 | const val ENABLE_GRADLE_PLUGIN = "You need to enable the Ktorfit Gradle Plugin"
8 | const val BASE_URL_NEEDS_TO_END_WITH = "Base URL needs to end with /"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/test/java/de/jensklingenberg/androidonlyexample/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/BodyCodeGenerator.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.reqBuilderExtension
2 |
3 | import de.jensklingenberg.ktorfit.model.ParameterData
4 | import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation.Body
5 |
6 | fun getBodyDataText(params: List): String =
7 | params
8 | .firstOrNull {
9 | it.hasAnnotation()
10 | }?.name
11 | ?.let { "setBody($it)" }
12 | .orEmpty()
13 |
--------------------------------------------------------------------------------
/ktorfit-compiler-plugin/src/main/java/de/jensklingenberg/ktorfit/DebugLogger.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
4 | import org.jetbrains.kotlin.cli.common.messages.MessageCollector
5 |
6 | internal data class DebugLogger(val debug: Boolean, val messageCollector: MessageCollector) {
7 | fun log(message: String) {
8 | if (debug) {
9 | messageCollector.report(CompilerMessageSeverity.INFO, message)
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/i-d-like-to-request-a-feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: I'd like to request a feature about: Suggest a new idea for this plugin. title: ''
3 | labels: enhancement assignees: ''
4 |
5 | ---
6 |
7 | **Describe the use-cases of your feature**
8 |
9 |
10 |
11 | **Describe your solution**
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ktorfit-lib/api/ktorfit-lib.klib.api:
--------------------------------------------------------------------------------
1 | // Klib ABI Dump
2 | // Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64]
3 | // Rendering settings:
4 | // - Signature version: 2
5 | // - Show manifest properties: true
6 | // - Show declarations: true
7 |
8 | // Library unique name:
9 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Part.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * String OR List< PartData>
5 | * ```
6 | * @Multipart
7 | * @POST("upload")
8 | * suspend fun uploadFile(@Part("description") description: String, @Part("description") data: List): String
9 | * ```
10 | * @param value part name
11 | * Part parameters type may not be nullable.
12 | */
13 | @Target(AnnotationTarget.VALUE_PARAMETER)
14 | annotation class Part(
15 | val value: String = ""
16 | )
17 |
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/model/MyOwnResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | sealed class MyOwnResponse {
7 | data class Success(
8 | val data: T
9 | ) : MyOwnResponse()
10 |
11 | class Error(
12 | val ex: Throwable
13 | ) : MyOwnResponse()
14 |
15 | companion object {
16 | fun success(data: T) = Success(data)
17 |
18 | fun error(ex: Throwable) = Error(ex)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.github/workflows/update-gradle-wrapper.yml:
--------------------------------------------------------------------------------
1 | name: Update Gradle Wrapper
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: "0 0 * * *"
7 |
8 | jobs:
9 | update-gradle-wrapper:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Install JDK 21
15 | uses: actions/setup-java@v5
16 | with:
17 | distribution: 'temurin'
18 | java-version: 21
19 | - uses: actions/checkout@v6
20 |
21 | - name: Update Gradle Wrapper
22 | uses: gradle-update/update-gradle-wrapper-action@v2
--------------------------------------------------------------------------------
/example/MultiplatformExample/shared/src/jsMain/kotlin/com/example/ktorfittest/main.kt:
--------------------------------------------------------------------------------
1 | package com.example.ktorfittest
2 |
3 | import kotlinx.coroutines.GlobalScope
4 | import kotlinx.coroutines.delay
5 | import kotlinx.coroutines.launch
6 |
7 | //Run with jsNodeRun
8 | fun main() {
9 | GlobalScope.launch {
10 |
11 |
12 | println("Launch")
13 |
14 |
15 | starWarsApi.getPeopleByIdFlowResponse(3,null).collect {
16 | println("JS getPeopleByIdFlowResponse:" + it.name)
17 | }
18 |
19 | delay(3000)
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/CustomRequestBuilderCodeGeneration.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.reqBuilderExtension
2 |
3 | import de.jensklingenberg.ktorfit.model.ParameterData
4 | import de.jensklingenberg.ktorfit.model.annotations.ParameterAnnotation
5 |
6 | fun getCustomRequestBuilderText(parameterDataList: List): String =
7 | parameterDataList
8 | .find { it.hasAnnotation() }
9 | ?.let {
10 | it.name + "(this)"
11 | }.orEmpty()
12 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/HTTP.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /** Make a request with a custom HTTP method.
4 | * ```
5 | * @HTTP(method = "CUSTOM", path = "custom/endpoint/")
6 | * suspend fun getIssue(@Query("id") id: String) : Issue
7 | * ```
8 | * @param method HTTP method verb.
9 | * @param path URL path.
10 | * @param hasBody
11 | * */
12 | @Target(AnnotationTarget.FUNCTION)
13 | annotation class HTTP(
14 | val method: String,
15 | val path: String = "",
16 | val hasBody: Boolean = false
17 | )
18 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/shared/src/commonTest/kotlin/com/example/ktorfittest/KtorfitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.ktorfittest
2 |
3 | import kotlinx.coroutines.GlobalScope
4 | import kotlinx.coroutines.launch
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 |
8 | class KtorfitTest {
9 | @Test
10 | fun test() {
11 | GlobalScope.launch {
12 | ktorfit.create()
13 | .getPersonByIdResponse(3)
14 | .collect {
15 | assertEquals(it.name, "R2-D2")
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/model/ExampleApi.kt:
--------------------------------------------------------------------------------
1 | package com.example.model
2 |
3 | import de.jensklingenberg.ktorfit.Response
4 | import de.jensklingenberg.ktorfit.http.GET
5 | import de.jensklingenberg.ktorfit.http.Headers
6 |
7 | interface ExampleApi {
8 |
9 | @Headers("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0")
10 | @GET("example.json")
11 | suspend fun getUser(): Response
12 |
13 | @GET("example.json")
14 | suspend fun getUserResponse(): MyOwnResponse
15 | }
16 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/core/NoDelegation.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.core
2 |
3 | /**
4 | * Indicates that the annotated interface should not be delegated in the generated implementation.
5 | *
6 | * When an interface is annotated with @NoDelegation, the generated implementation will not use
7 | * Kotlin delegation for this interface. This is useful when you want to manually implement the
8 | * methods of the interface or when delegation is not desired for other reasons.
9 | */
10 | @Target(AnnotationTarget.TYPE)
11 | annotation class NoDelegation
12 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Header.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * Add a header to a request
5 | *
6 | * ```
7 | * @GET("comments")
8 | * suspend fun request( @Header("Content-Type") name: String): List
9 | * ```
10 | *
11 | * A request with request("Hello World") will have the header "Content-Type:Hello World"
12 | * header with null values will be ignored
13 | * @see Headers
14 | * @see HeaderMap
15 | */
16 | @Target(AnnotationTarget.VALUE_PARAMETER)
17 | annotation class Header(
18 | val value: String
19 | )
20 |
--------------------------------------------------------------------------------
/docs/fundamentals/scope.md:
--------------------------------------------------------------------------------
1 | # Scope of Ktorfit
2 |
3 | The goal of Ktorfit is to provide a similar developer experience like [Retrofit](https://square.github.io/retrofit/) for Kotlin Multiplatform projects. It`s not a 100% drop-in replacement for Retrofit. It uses [Ktor clients](https://ktor.io/docs/getting-started-ktor-client.html) because they are available on nearly every compile target of KMP.
4 | Every feature should be implemented so that it works on all platforms that Ktor supports. Before a new functionality is added to Ktorfit, it should be checked if there is already a Ktor plugin for it which solves the same problem.
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Field.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * Needs to be used in combination with [FormUrlEncoded]
5 | * @param value The default value will be replaced with the name of the parameter that is annotated.
6 | * @param encoded true means that this value is already URL encoded and will not be encoded again
7 | * @see FormUrlEncoded
8 | * @see FieldMap
9 | */
10 | @Target(AnnotationTarget.VALUE_PARAMETER)
11 | annotation class Field(
12 | val value: String = "KTORFIT_DEFAULT_VALUE",
13 | val encoded: Boolean = false
14 | )
15 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/reqBuilderExtension/MethodCodeGeneration.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.reqBuilderExtension
2 |
3 | import de.jensklingenberg.ktorfit.model.annotations.CustomHttp
4 | import de.jensklingenberg.ktorfit.model.annotations.HttpMethodAnnotation
5 |
6 | fun getMethodCode(httpMethod: HttpMethodAnnotation): String {
7 | val httpMethodValue =
8 | if (httpMethod is CustomHttp) {
9 | httpMethod.customValue
10 | } else {
11 | httpMethod.httpMethod.keyword
12 | }
13 | return "this.method = HttpMethod.parse(\"${httpMethodValue}\")"
14 | }
15 |
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/Annotations.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import io.ktor.client.request.HttpRequest
4 | import io.ktor.client.request.HttpRequestBuilder
5 | import io.ktor.util.AttributeKey
6 |
7 | public val annotationsAttributeKey: AttributeKey> = AttributeKey("__ktorfit_attribute_annotations")
8 |
9 | public val HttpRequest.annotations: List
10 | inline get() = attributes.getOrNull(annotationsAttributeKey) ?: emptyList()
11 |
12 | public val HttpRequestBuilder.annotations: List
13 | inline get() = attributes.getOrNull(annotationsAttributeKey) ?: emptyList()
14 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Versions currently being supported with security updates:
6 |
7 | | Version | Supported |
8 | | ------- | ------------------ |
9 | | latest | :white_check_mark: |
10 |
11 | ## Reporting a Vulnerability
12 |
13 | Please use an [Issue](https://github.com/Foso/Ktorfit/security/advisories/new) to report vulnerabilities.
14 |
15 | If security bug is discovered, following actions will be taken:
16 |
17 | - Confirm the problem and determine the affected versions.
18 | - Audit code to find any potential similar problems.
19 | - Prepare fixes for all releases still under maintenance.
20 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Pods.xcodeproj/xcuserdata/jensklingenberg.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Pods-iosApp.xcscheme
8 |
9 | isShown
10 |
11 |
12 | shared.xcscheme
13 |
14 | isShown
15 |
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/person/src/commonMain/kotlin/com/example/ktorfittest/Person.kt:
--------------------------------------------------------------------------------
1 | package com.example.ktorfittest
2 |
3 | @kotlinx.serialization.Serializable
4 | data class Person(
5 | val films: List? = null,
6 | val homeworld: String? = null,
7 | val gender: String? = null,
8 | val skinColor: String? = null,
9 | val edited: String? = null,
10 | val created: String? = null,
11 | val mass: String? = null,
12 | val url: String? = null,
13 | val hairColor: String? = null,
14 | val birthYear: String? = null,
15 | val eyeColor: String? = null,
16 | val name: String? = null,
17 | val height: String? = null
18 | )
19 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Tag.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * Adds the argument instance as a request tag using the type as a AttributeKey.
5 | *
6 | * ```
7 | * @GET("/")
8 | * fun foo(@Tag tag: String)
9 | * ```
10 | *
11 | * Tag arguments may be `null` which will omit them from the request.
12 | *
13 | * @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.
14 | *
15 | */
16 | @Target(AnnotationTarget.VALUE_PARAMETER)
17 | annotation class Tag(
18 | val value: String = "KTORFIT_DEFAULT_VALUE"
19 | )
20 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Documentation for all configuration options:
2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 | # Updates for Github Actions used in the repo
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 | groups:
12 | GitHub_Actions:
13 | patterns:
14 | - "*"
15 | # Updates for Gradle dependencies used in the app
16 | - package-ecosystem: gradle
17 | directory: "/"
18 | schedule:
19 | interval: "weekly"
20 | day: monday
21 | time: "12:00"
22 | target-branch: master
23 |
--------------------------------------------------------------------------------
/ktorfit-compiler-plugin/src/main/java/de/jensklingenberg/ktorfit/KtorfitIrGenerationExtension.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
4 | import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
5 | import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
6 |
7 | internal class KtorfitIrGenerationExtension(private val debugLogger: DebugLogger) : IrGenerationExtension {
8 | override fun generate(
9 | moduleFragment: IrModuleFragment,
10 | pluginContext: IrPluginContext,
11 | ) {
12 | moduleFragment.transform(ElementTransformer(pluginContext, debugLogger), null)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/model/annotations/FunctionAnnotation.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.model.annotations
2 |
3 | enum class HttpMethod(
4 | val keyword: String
5 | ) {
6 | GET("GET"),
7 | POST("POST"),
8 | PUT("PUT"),
9 | DELETE("DELETE"),
10 | HEAD("HEAD"),
11 | PATCH("PATCH"),
12 | CUSTOM(""),
13 | }
14 |
15 | /**
16 | * Annotation at a function
17 | */
18 | open class FunctionAnnotation
19 |
20 | class Headers(
21 | val value: List
22 | ) : FunctionAnnotation()
23 |
24 | object FormUrlEncoded : FunctionAnnotation()
25 |
26 | object Streaming : FunctionAnnotation()
27 |
28 | object Multipart : FunctionAnnotation()
29 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/androidApp/src/main/java/com/example/myapplication/android/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.myapplication.android
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 |
6 | import android.widget.TextView
7 | import com.example.ktorfittest.Greeting
8 |
9 | fun greet(): String {
10 | return Greeting().greeting()
11 | }
12 |
13 | class MainActivity : AppCompatActivity() {
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContentView(R.layout.activity_main)
17 |
18 | val tv: TextView = findViewById(R.id.text_view)
19 | tv.text = greet()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/java/de/jensklingenberg/androidonlyexample/Person.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Person(
7 | val films: List? = null,
8 | val homeworld: String? = null,
9 | val gender: String? = null,
10 | val skinColor: String? = null,
11 | val edited: String? = null,
12 | val created: String? = null,
13 | val mass: String? = null,
14 | val url: String? = null,
15 | val hairColor: String? = null,
16 | val birthYear: String? = null,
17 | val eyeColor: String? = null,
18 | val name: String? = null,
19 | val height: String? = null
20 | )
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenLocal()
6 | mavenCentral()
7 | maven {
8 | url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
9 | }
10 | }
11 | }
12 | dependencyResolutionManagement {
13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
14 | repositories {
15 | google()
16 | mavenLocal()
17 | mavenCentral()
18 | maven {
19 | url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
20 | }
21 | }
22 | }
23 | rootProject.name = "AndroidOnlyExample"
24 | include(":app")
--------------------------------------------------------------------------------
/ktorfit-converters/flow/api/android/flow.api:
--------------------------------------------------------------------------------
1 | public final class de/jensklingenberg/ktorfit/converter/FlowConverterFactory : de/jensklingenberg/ktorfit/converter/Converter$Factory {
2 | public fun ()V
3 | public fun requestParameterConverter (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lde/jensklingenberg/ktorfit/converter/Converter$RequestParameterConverter;
4 | public fun responseConverter (Lde/jensklingenberg/ktorfit/converter/TypeData;Lde/jensklingenberg/ktorfit/Ktorfit;)Lde/jensklingenberg/ktorfit/converter/Converter$ResponseConverter;
5 | public fun suspendResponseConverter (Lde/jensklingenberg/ktorfit/converter/TypeData;Lde/jensklingenberg/ktorfit/Ktorfit;)Lde/jensklingenberg/ktorfit/converter/Converter$SuspendResponseConverter;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/ktorfit-converters/flow/api/jvm/flow.api:
--------------------------------------------------------------------------------
1 | public final class de/jensklingenberg/ktorfit/converter/FlowConverterFactory : de/jensklingenberg/ktorfit/converter/Converter$Factory {
2 | public fun ()V
3 | public fun requestParameterConverter (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;)Lde/jensklingenberg/ktorfit/converter/Converter$RequestParameterConverter;
4 | public fun responseConverter (Lde/jensklingenberg/ktorfit/converter/TypeData;Lde/jensklingenberg/ktorfit/Ktorfit;)Lde/jensklingenberg/ktorfit/converter/Converter$ResponseConverter;
5 | public fun suspendResponseConverter (Lde/jensklingenberg/ktorfit/converter/TypeData;Lde/jensklingenberg/ktorfit/Ktorfit;)Lde/jensklingenberg/ktorfit/converter/Converter$SuspendResponseConverter;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/model/Specie.kt:
--------------------------------------------------------------------------------
1 | package com.example.model
2 |
3 | @kotlinx.serialization.Serializable
4 | data class Specie(
5 | val films: List? = null,
6 | val skinColors: String? = null,
7 | val homeworld: String? = null,
8 | val edited: String? = null,
9 | val created: String? = null,
10 | val eyeColors: String? = null,
11 | val language: String? = null,
12 | val classification: String? = null,
13 | val people: List? = null,
14 | val url: String? = null,
15 | val hairColors: String? = null,
16 | val averageHeight: String? = null,
17 | val name: String? = null,
18 | val designation: String? = null,
19 | val averageLifespan: String? = null
20 | )
21 |
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/api/StarWarsApi.kt:
--------------------------------------------------------------------------------
1 | package com.example.api
2 |
3 | import com.example.model.People
4 | import de.jensklingenberg.ktorfit.Call
5 | import de.jensklingenberg.ktorfit.http.GET
6 | import de.jensklingenberg.ktorfit.http.Path
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | interface StarWarsApi {
10 | companion object {
11 | const val baseUrl = "https://swapi.dev/api/"
12 | }
13 |
14 | @GET("people/{id}/")
15 | fun getPersonById(
16 | @Path("id") peopleId: Int
17 | ): Call
18 |
19 | @GET("people/stormtrooper/all")
20 | fun subscribeToStormtroopers(): Flow>
21 |
22 | @GET("people/stormtrooper/all")
23 | suspend fun summonStormtroopers(): List
24 | }
25 |
--------------------------------------------------------------------------------
/sandbox/src/linuxX64Main/kotlin/LinuxMain.kt:
--------------------------------------------------------------------------------
1 | import com.example.api.JsonPlaceHolderApi
2 | import de.jensklingenberg.ktorfit.Ktorfit
3 | import de.jensklingenberg.ktorfit.converter.FlowConverterFactory
4 | import io.ktor.client.HttpClient
5 | import kotlinx.coroutines.runBlocking
6 |
7 | fun main() {
8 | val linuxKtorfit =
9 | Ktorfit
10 | .Builder()
11 | .baseUrl(JsonPlaceHolderApi.baseUrl)
12 | .httpClient(HttpClient())
13 | .converterFactories(FlowConverterFactory())
14 | .build()
15 |
16 | val api = linuxKtorfit.create()
17 | runBlocking {
18 | api.getPosts().collect {
19 | println(it)
20 | }
21 | }
22 |
23 | println("ddd")
24 | }
25 |
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/converter/KtorfitResult.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.converter
2 |
3 | import io.ktor.client.statement.HttpResponse
4 |
5 | /**
6 | * Represents the result from a Ktorfit request. */
7 | public sealed interface KtorfitResult {
8 | /**
9 | * Represents a successful response.
10 | * @property response The HTTP response.
11 | */
12 | public class Success(
13 | public val response: HttpResponse
14 | ) : KtorfitResult
15 |
16 | /**
17 | * Represents a failed response.
18 | * @property throwable The throwable associated with the failure.
19 | */
20 | public class Failure(
21 | public val throwable: Throwable
22 | ) : KtorfitResult
23 | }
24 |
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/model/People.kt:
--------------------------------------------------------------------------------
1 | package com.example.model
2 |
3 | @kotlinx.serialization.Serializable
4 | data class People(
5 | val films: List? = null,
6 | val homeworld: String? = null,
7 | val gender: String? = null,
8 | val skinColor: String? = null,
9 | val edited: String? = null,
10 | val created: String? = null,
11 | val mass: String? = null,
12 | // val vehicles: List? = null,
13 | val url: String? = null,
14 | val hairColor: String? = null,
15 | val birthYear: String? = null,
16 | val eyeColor: String? = null,
17 | // val species: List? = null,
18 | // val starships: List? = null,
19 | val name: String? = null,
20 | val height: String? = null
21 | )
22 |
--------------------------------------------------------------------------------
/ktorfit-converters/response/src/commonMain/kotlin/de/jensklingenberg/ktorfit/converter/ResponseConverterFactory.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.converter
2 |
3 | import de.jensklingenberg.ktorfit.Ktorfit
4 | import de.jensklingenberg.ktorfit.Response
5 | import io.ktor.client.statement.HttpResponse
6 |
7 | /**
8 | * Converter for [Response]
9 | */
10 | public class ResponseConverterFactory : Converter.Factory {
11 | override fun suspendResponseConverter(
12 | typeData: TypeData,
13 | ktorfit: Ktorfit,
14 | ): Converter.SuspendResponseConverter? {
15 | if (typeData.typeInfo.type == Response::class) {
16 | return ResponseClassSuspendConverter(typeData, ktorfit)
17 | }
18 | return null
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonMain/kotlin/de/jensklingenberg/ktorfit/TypeInfoExt.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import io.ktor.util.reflect.TypeInfo
4 | import kotlin.reflect.KClass
5 |
6 | /**
7 | * This will return the upper bound type.
8 | *
9 | * Example: Response will return String as TypeInfo with upperBoundType(0)
10 | */
11 | public fun TypeInfo.upperBoundType(index: Int = 0): TypeInfo? {
12 | val parentType = this.kotlinType ?: return null
13 | val modelKTypeProjection = if (parentType.arguments.isNotEmpty()) parentType.arguments[index] else return null
14 | val modelKType = modelKTypeProjection.type ?: return null
15 | val modelClass = (modelKType.classifier as? KClass<*>?) ?: return null
16 | return TypeInfo(modelClass, modelKType)
17 | }
18 |
--------------------------------------------------------------------------------
/docs/knownissues.md:
--------------------------------------------------------------------------------
1 | # Known Issues
2 |
3 | ## KMP project with single target
4 |
5 | * Unresolved reference for API class
6 |
7 | When you have a KMP project with a single target, IntelliJ will find the generated "create" extension function (e.g.
8 | **ktorfit.createExampleApi()**) in your common module, but the compilation will fail
9 | because of an "Unresolved reference" error. In that case, you have to use **ktorfit.create<ExampleApi>()** to make it work, even though it's already deprecated.
10 |
11 | Kotlin handles the compilation of a KMP project with a single target differently than with multiple targets.
12 |
13 | See:
14 |
15 | * https://youtrack.jetbrains.com/issue/KT-59129
16 |
17 | * https://youtrack.jetbrains.com/issue/KT-52664/Multiplatform-projects-with-a-single-target
--------------------------------------------------------------------------------
/detekt-config.yml:
--------------------------------------------------------------------------------
1 | build:
2 | maxIssues: 0
3 | weights:
4 | # complexity: 2
5 | # LongParameterList: 1
6 | # style: 1
7 | # comments: 1
8 |
9 | complexity:
10 | active: true
11 | ComplexMethod:
12 | active: true
13 | threshold: 16
14 | ComplexCondition:
15 | active: true
16 | threshold: 5
17 | LongMethod:
18 | active: true
19 | excludes: ['**/test/**', '**/androidTest/**']
20 | LongParameterList:
21 | active: true
22 | ignoreDefaultParameters: true
23 | TooManyFunctions:
24 | active: true
25 | ignoreOverridden: true
26 |
27 | style:
28 | ReturnCount:
29 | active: true
30 | max: 5
31 | excludedFunctions: "equals"
32 | excludeLabeled: false
33 | excludeReturnFromLambda: true
34 | UnusedImports:
35 | active: true
36 |
--------------------------------------------------------------------------------
/ktorfit-ksp/detekt-config.yml:
--------------------------------------------------------------------------------
1 | build:
2 | maxIssues: 0
3 | weights:
4 | # complexity: 2
5 | # LongParameterList: 1
6 | # style: 1
7 | # comments: 1
8 |
9 | complexity:
10 | active: true
11 | ComplexMethod:
12 | active: true
13 | threshold: 16
14 | ComplexCondition:
15 | active: true
16 | threshold: 5
17 | LongMethod:
18 | active: true
19 | excludes: [ '**/test/**', '**/androidTest/**' ]
20 | LongParameterList:
21 | active: true
22 | ignoreDefaultParameters: true
23 | TooManyFunctions:
24 | active: true
25 | ignoreOverridden: true
26 |
27 | style:
28 | ReturnCount:
29 | active: true
30 | max: 5
31 | excludedFunctions: "equals"
32 | excludeLabeled: false
33 | excludeReturnFromLambda: true
34 | UnusedImports:
35 | active: true
36 |
--------------------------------------------------------------------------------
/sandbox/src/jsMain/kotlin/JsMain.kt:
--------------------------------------------------------------------------------
1 | import com.example.model.Comment
2 | import com.example.model.MyOwnResponse
3 | import com.example.model.jsonPlaceHolderApi
4 | import kotlinx.coroutines.GlobalScope
5 | import kotlinx.coroutines.delay
6 | import kotlinx.coroutines.launch
7 |
8 | fun main() {
9 | GlobalScope.launch {
10 | println("Launch")
11 |
12 | when (val test = jsonPlaceHolderApi.getCommentsByPostIdResponse("3")) {
13 | is MyOwnResponse.Success -> {
14 | val list = test.data as List
15 | println(list.size)
16 | }
17 |
18 | else -> {
19 | val error = (test as MyOwnResponse.Error<*>)
20 | println(error.ex)
21 | }
22 | }
23 |
24 | delay(3000)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | // mavenLocal()
4 | gradlePluginPortal()
5 | google()
6 | mavenCentral()
7 | maven {
8 | url = uri("https://oss.sonatype.org/content/repositories/snapshots")
9 | }
10 | }
11 | dependencies {
12 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
13 | classpath("com.android.tools.build:gradle:8.12.0")
14 | classpath("org.jetbrains.kotlin:kotlin-serialization:2.2.0")
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | // mavenLocal()
21 | google()
22 | mavenCentral()
23 | maven {
24 | url = uri("https://oss.sonatype.org/content/repositories/snapshots")
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ktorfit-compiler-plugin/Readme.md:
--------------------------------------------------------------------------------
1 | # Compiler plugin
2 | The compiler plugin transform the usage of the create function from Ktorfit-lib
3 |
4 | It looks for the every usage of the create function from the Ktorfit-lib and adds an object of the
5 | wanted implementation class as an argument. Because of the naming convention of the generated classes
6 | we can deduce the name of the class from the name of type parameter.
7 |
8 | ```kotlin
9 | val api = jvmKtorfit.create()
10 | ```
11 |
12 | will be transformed to:
13 |
14 | ```kotlin
15 | val api = jvmKtorfit.create(_ExampleApiImpl(jvmKtorfit))
16 | ```
17 |
18 | # Compatibility table
19 | | Compiler plugin version | Kotlin |
20 | |-------------------------|-----------|
21 | | 2.3.2 | 2.2.21 |
22 | | 2.3.3 | 2.3.0-RC3 |
23 |
24 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | ENABLE_USER_SCRIPT_SANDBOXING = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -l"c++" -framework "shared"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
10 | PODS_ROOT = ${SRCROOT}/Pods
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
13 |
--------------------------------------------------------------------------------
/.github/workflows/publish-gradle-plugin.yml:
--------------------------------------------------------------------------------
1 | name: Publish Gradle Plugin
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | publish-gradle-plugin:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v6
12 | - name: Set up JDK 21
13 | uses: actions/setup-java@v5
14 | with:
15 | distribution: 'temurin'
16 | java-version: 21
17 | - name: Set up Gradle
18 | uses: gradle/actions/setup-gradle@v5
19 | - name: Publish plugin
20 | env:
21 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }}
22 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
23 | run: ./gradlew :ktorfit-gradle-plugin:publishPlugins -Dgradle.publish.key=${GRADLE_PUBLISH_KEY} -Dgradle.publish.secret=${GRADLE_PUBLISH_SECRET}
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | ENABLE_USER_SCRIPT_SANDBOXING = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
6 | OTHER_LDFLAGS = $(inherited) -l"c++" -framework "shared"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
10 | PODS_ROOT = ${SRCROOT}/Pods
11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
13 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/androidApp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/shared/src/jvmMain/kotlin/JvmExampleClass.kt:
--------------------------------------------------------------------------------
1 | import com.example.ktorfittest.Person
2 | import com.example.ktorfittest.starWarsApi
3 | import de.jensklingenberg.ktorfit.Callback
4 | import io.ktor.client.statement.*
5 | import kotlinx.coroutines.runBlocking
6 |
7 | fun main() {
8 | starWarsApi.getPeopleByIdCallResponse(3).onExecute(
9 | object : Callback {
10 | override fun onError(exception: Throwable) {
11 | }
12 |
13 | override fun onResponse(
14 | call: Person,
15 | response: HttpResponse
16 | ) {
17 | println("onResponse" + call)
18 | }
19 | }
20 | )
21 |
22 | runBlocking {
23 | val response = starWarsApi.getPersonByIdResponse(3)
24 | println(response)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | ij_kotlin_name_count_to_use_star_import = 2147483647
5 |
6 | [*.{kt,kts}]
7 | # Disabled rules:
8 | # noinspection EditorConfigKeyCorrectness
9 | ktlint_standard_trailing-comma-on-call-site = disabled
10 | # noinspection EditorConfigKeyCorrectness
11 | ktlint_standard_trailing-comma-on-declaration-site = disabled
12 | # noinspection EditorConfigKeyCorrectness
13 | ktlint_standard_import-ordering = disabled
14 | # noinspection EditorConfigKeyCorrectness
15 | ktlint_standard_multiline-if-else = disabled
16 | # noinspection EditorConfigKeyCorrectness
17 | ktlint_standard_comment-wrapping = disabled
18 | # noinspection EditorConfigKeyCorrectness
19 | ktlint_standard_block-comment-initial-star-alignment = disabled
20 | # noinspection EditorConfigKeyCorrectness
21 | ktlint_standard_no-single-line-block-comment = disabled
22 | max_line_length=off
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | build:
2 | - changed-files:
3 | - any-glob-to-any-file: '**/*.gradle'
4 | - any-glob-to-any-file: '**/*.gradle.kts'
5 |
6 | ci:
7 | - changed-files:
8 | - any-glob-to-any-file: '.github/*'
9 |
10 | compiler-plugin:
11 | - changed-files:
12 | - any-glob-to-any-file: 'compiler-plugin/src/**/*'
13 |
14 | documentation:
15 | - changed-files:
16 | - any-glob-to-any-file: 'docs/*.md'
17 |
18 | ktorfit-gradle-plugin:
19 | - changed-files:
20 | - any-glob-to-any-file: 'ktorfit-gradle-plugin/src/**/*'
21 |
22 | ktorfit-ksp:
23 | - changed-files:
24 | - any-glob-to-any-file: 'ktorfit-ksp/src/**/*'
25 |
26 | ktorfit-lib:
27 | - changed-files:
28 | - any-glob-to-any-file: 'ktorfit-lib/src/**/*'
29 | - any-glob-to-any-file: 'ktorfit-lib-core/src/**/*'
30 |
31 | sandbox:
32 | - changed-files:
33 | - any-glob-to-any-file: 'sandbox/src/**/*'
34 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/KtorfitOptions.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | class KtorfitOptions(
4 | options: Map
5 | ) {
6 | /**
7 | * 0: Turn off all Ktorfit related error checking
8 | *
9 | * 1: Check for errors
10 | *
11 | * 2: Turn errors into warnings
12 | */
13 | val errorsLoggingType: Int = (options["Ktorfit_Errors"]?.toIntOrNull()) ?: 1
14 |
15 | /**
16 | * If set to true, the generated code will contain qualified type names
17 | */
18 | val setQualifiedType = options["Ktorfit_QualifiedTypeName"]?.toBoolean() ?: false
19 |
20 | /**
21 | * If the compilation is multiplatform and has only one target, this will be true
22 | */
23 | val multiplatformWithSingleTarget = options["Ktorfit_MultiplatformWithSingleTarget"]?.toBoolean() ?: false
24 | }
25 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/androidTest/java/de/jensklingenberg/androidonlyexample/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("de.jensklingenberg.androidonlyexample", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/java/de/jensklingenberg/androidonlyexample/TestJava.java:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample;
2 |
3 | import static de.jensklingenberg.androidonlyexample.MainActivityKt.getApi;
4 | import android.util.Log;
5 |
6 | import androidx.annotation.NonNull;
7 |
8 | import de.jensklingenberg.ktorfit.Callback;
9 | import io.ktor.client.statement.HttpResponse;
10 |
11 | public class TestJava {
12 |
13 | void test() {
14 | getApi().getPersonCall(1).onExecute(new Callback() {
15 |
16 | @Override
17 | public void onError(@NonNull Throwable throwable) {
18 |
19 | }
20 |
21 | @Override
22 | public void onResponse(Person person, @NonNull HttpResponse httpResponse) {
23 | Log.d("Android:", person.toString());
24 | }
25 |
26 | });
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/sandbox/src/commonMain/kotlin/com/example/model/github/GithubFollowerResponse.kt:
--------------------------------------------------------------------------------
1 | package com.example.model.github
2 |
3 | @kotlinx.serialization.Serializable
4 | data class GithubFollowerResponseItem(
5 | val gistsUrl: String? = null,
6 | val reposUrl: String? = null,
7 | val followingUrl: String? = null,
8 | val starredUrl: String? = null,
9 | val login: String? = null,
10 | val followersUrl: String? = null,
11 | val type: String? = null,
12 | val url: String? = null,
13 | val subscriptionsUrl: String? = null,
14 | val receivedEventsUrl: String? = null,
15 | val avatarUrl: String? = null,
16 | val eventsUrl: String? = null,
17 | val htmlUrl: String? = null,
18 | val siteAdmin: Boolean? = null,
19 | val id: Int? = null,
20 | val gravatarId: String? = null,
21 | val nodeId: String? = null,
22 | val organizationsUrl: String? = null
23 | )
24 |
--------------------------------------------------------------------------------
/sandbox/src/jvmMain/kotlin/de/jensklingenberg/ktorfit/demo/TestApi2.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.demo
2 |
3 | import com.example.api.StarWarsApi
4 | import com.example.model.People
5 | import de.jensklingenberg.ktorfit.http.GET
6 | import de.jensklingenberg.ktorfit.http.Path
7 | import de.jensklingenberg.ktorfit.http.QueryName
8 |
9 | interface TestApi2 :
10 | StarWarsApi,
11 | QueryNameTestApi {
12 | @GET("people/{id}/")
13 | fun tste()
14 | }
15 |
16 | data class Test(
17 | val name: String
18 | )
19 |
20 | interface QueryNameTestApi {
21 | @GET("people/{id}/")
22 | suspend fun testQueryName(
23 | @Path("id") peopleId: Int,
24 | @QueryName name: String
25 | ): People
26 |
27 | @GET("people/{id}/")
28 | suspend fun testQueryNameList(
29 | @Path("id") peopleId: Int,
30 | @QueryName(false) name: List
31 | ): People
32 | }
33 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/main/kotlin/de/jensklingenberg/ktorfit/KtorfitLogger.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import com.google.devtools.ksp.processing.KSPLogger
4 | import com.google.devtools.ksp.symbol.KSNode
5 |
6 | class KtorfitLogger(
7 | private val kspLogger: KSPLogger,
8 | private val loggingType: Int
9 | ) : KSPLogger by kspLogger {
10 | override fun error(
11 | message: String,
12 | symbol: KSNode?,
13 | ) {
14 | when (loggingType) {
15 | 0 -> {
16 | // Do nothing
17 | }
18 |
19 | 1 -> {
20 | // Throw compile errors for Ktorfit
21 | kspLogger.error("Ktorfit: $message", symbol)
22 | }
23 |
24 | 2 -> {
25 | // Turn errors into compile warnings
26 | kspLogger.warn("Ktorfit: $message", symbol)
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/androidApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | kotlin("android")
4 | }
5 |
6 | android {
7 | compileSdk = 36
8 | defaultConfig {
9 | applicationId = "com.example.myapplication.android"
10 | minSdk = 21
11 | targetSdk = 34
12 | versionCode = 1
13 | versionName = "1.0"
14 | namespace = "com.example.myapplication.android"
15 | }
16 | buildTypes {
17 | getByName("release") {
18 | isMinifyEnabled = false
19 | }
20 | }
21 |
22 | compileOptions {
23 | sourceCompatibility = JavaVersion.VERSION_1_8
24 | targetCompatibility = JavaVersion.VERSION_1_8
25 | }
26 |
27 | kotlinOptions {
28 | jvmTarget= "1.8"
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation(project(":shared"))
34 | implementation("com.google.android.material:material:1.13.0")
35 | }
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/java/de/jensklingenberg/androidonlyexample/StarWarsApi.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample
2 |
3 | import de.jensklingenberg.ktorfit.Call
4 | import de.jensklingenberg.ktorfit.Response
5 | import de.jensklingenberg.ktorfit.http.GET
6 | import de.jensklingenberg.ktorfit.http.Path
7 | import de.jensklingenberg.ktorfit.http.Query
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | interface StarWarsApi {
11 |
12 | companion object {
13 | const val baseUrl = "https://swapi.info/api/"
14 | }
15 |
16 | @GET("people/{id}/")
17 | suspend fun getPerson(@Path("id") personId: Int): Person
18 |
19 | @GET("people")
20 | fun getPeopleFlow(@Query("page") page: Int): Flow
21 |
22 | @GET("people/{id}/")
23 | fun getPersonCall(@Path("id") personId: Int): Call
24 |
25 | @GET("people/{id}/")
26 | suspend fun getPersonResponse(@Path("id") personId: Int): Response
27 |
28 | }
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Path.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * This can be set if you have parts in your URL that want to dynamically replaced
5 | *
6 | * @param value The default value will be replaced with the name of the parameter that is annotated.
7 | * When the URL of an HTTP Method Annotation contains curly braces, they will be replaced with the value of
8 | * the corresponding parameter that has a matching [value].
9 | * @param encoded true means that this value is already URL encoded and will not be encoded again
10 | * Path parameters type may not be nullable.
11 | *
12 | *
13 | * ```
14 | * @GET("post/{postId}")
15 | * suspend fun getPosts(@Path("postId") postId: Int): List
16 | * ```
17 | */
18 |
19 | @Target(AnnotationTarget.VALUE_PARAMETER)
20 | annotation class Path(
21 | val value: String = "KTORFIT_DEFAULT_VALUE",
22 | val encoded: Boolean = false
23 | )
24 |
--------------------------------------------------------------------------------
/docs/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | # Update Ktorfit for new Kotlin version
4 | - Bump **kotlin** in libs.versions
5 | - Change **ktorfitCompiler** in libs.versions to KTORFIT_VERSION-NEW_KOTLIN_VERSION
6 | - Run tests in :ktorfit-compiler-plugin
7 | - Create a PR against master
8 | - Merge PR
9 | - Run GitHub Actions "publish" workflow
10 |
11 | ## 👷 Project Structure
12 |
13 | * compiler plugin - module with source for the compiler plugin
14 | * ktorfit-annotations - module with annotations for the Ktorfit
15 | * ktorfit-ksp - module with source for the KSP plugin
16 | * ktorfit-lib-core - module with source for the Ktorfit lib
17 | * ktorfit-lib - ktorfit-lib-core + dependencies on platform specific clients
18 | * sandbox - experimental test module to try various stuff
19 |
20 | * example - contains example projects that use Ktorfit
21 | * docs - contains the source for the GitHub page
22 |
--------------------------------------------------------------------------------
/docs/converters/requestparameterconverter.md:
--------------------------------------------------------------------------------
1 | # RequestParameterConverter
2 |
3 | ```kotlin
4 | @GET("posts/{postId}/comments")
5 | suspend fun getCommentsById(@RequestType(Int::class) @Path("postId") postId: String): List
6 | ```
7 |
8 | You can set RequestType at a parameter with a type to which the parameter should be converted.
9 |
10 | Then you need to implement a Converter factory with a RequestParameterConverter.
11 |
12 | ```kotlin
13 | class StringToIntRequestConverterFactory : Converter.Factory {
14 | override fun requestParameterConverter(
15 | parameterType: KClass<*>,
16 | requestType: KClass<*>
17 | ): Converter.RequestParameterConverter? {
18 | return object : Converter.RequestParameterConverter {
19 | override fun convert(data: Any): Any {
20 | //convert the data
21 | }
22 | }
23 | }
24 | }
25 | ```
26 |
27 | ```kotlin
28 | ktorfit.converterFactories(StringToIntRequestConverterFactory())
29 | ```
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #versions
2 |
3 | org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=8g
4 |
5 | android.defaults.buildfeatures.buildconfig = false
6 | android.useAndroidX=true
7 |
8 | # kotlin
9 | kotlin.incremental=true
10 | kotlin.compiler.execution.strategy=in-process
11 | kotlin.native.ignoreDisabledTargets=true
12 | kotlin.native.binary.freezing=disabled
13 |
14 | # Maven Central
15 | POM_NAME=Ktorfit
16 | POM_DESCRIPTION=Ktorfit
17 | POM_INCEPTION_YEAR=2022
18 | POM_URL=https://github.com/Foso/Ktorfit
19 | POM_SCM_URL=https://github.com/Foso/Ktorfit
20 | POM_SCM_CONNECTION=scm:https://github.com/Foso/Ktorfit.git
21 | POM_SCM_DEV_CONNECTION=scm:git://github.com/Foso/Ktorfit.git
22 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
23 | POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
24 | POM_LICENCE_DIST=repo
25 | POM_DEVELOPER_ID=Foso
26 | POM_DEVELOPER_NAME=Jens Klingenberg
27 | POM_DEVELOPER_URL=https://www.jensklingenberg.de
28 | SONATYPE_STAGING_PROFILE=de.jensklingenberg
29 |
--------------------------------------------------------------------------------
/example/AndroidOnlyExample/app/src/main/java/de/jensklingenberg/androidonlyexample/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.androidonlyexample.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/shared/shared.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/shared
3 | ENABLE_USER_SCRIPT_SANDBOXING = NO
4 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework"
5 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
6 | KOTLIN_PROJECT_PATH = :shared
7 | OTHER_LDFLAGS = $(inherited) -l"c++"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
11 | PODS_ROOT = ${SRCROOT}
12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../shared
13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
15 | PRODUCT_MODULE_NAME = shared
16 | SKIP_INSTALL = YES
17 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
18 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/shared/shared.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/shared
3 | ENABLE_USER_SCRIPT_SANDBOXING = NO
4 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/build/cocoapods/framework"
5 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
6 | KOTLIN_PROJECT_PATH = :shared
7 | OTHER_LDFLAGS = $(inherited) -l"c++"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
11 | PODS_ROOT = ${SRCROOT}
12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../shared
13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
15 | PRODUCT_MODULE_NAME = shared
16 | SKIP_INSTALL = YES
17 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
18 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | Releasing
2 | =========
3 |
4 | # Publish new version
5 |
6 | 1. Create new branch `release/X.Y.Z` from `master` branch
7 | 2. Update **ktorfit** version inside `gradle/libs.versions.toml`
8 | 3. Update **ktorfitGradlePlugin** version inside `gradle/libs.versions.toml`
9 | 4. Update Compatibility table in Readme.md
10 | 5. Update KtorfitCompilerSubPlugin.defaultCompilerPluginVersion if necessary
11 | 6. Update ktorfit release version in mkdocs.yml
12 | 7. Update version in KtorfitGradleConfiguration
13 | 8. Set the release date in docs/changelog.md
14 | 9. `git commit -am "X.Y.Z."` (where X.Y.Z is the new version)
15 | 10. Push and create a PR to the `master` branch
16 | 11. When all checks successful, run GitHub Action `Publish Release` from your branch
17 | 12. Set the Git tag `git tag -a X.Y.Z -m "X.Y.Z"` (where X.Y.Z is the new version)
18 | 13. Merge the PR
19 | 14. Create a new release with for the Tag on GitHub
20 | 15. Run "deploy to GitHub pages" action
21 | 16. Put the relevant changelog in the release description
22 |
--------------------------------------------------------------------------------
/ktorfit-ksp/detekt-baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ComplexMethod:FunctionsParser.kt$fun getFunctionDataList( ksFunctionDeclarationList: List<KSFunctionDeclaration>, logger: KSPLogger ): List<FunctionData>
6 | ComplexMethod:ParameterParser.kt$fun getParamAnnotationList(ksValueParameter: KSValueParameter, logger: KSPLogger): List<ParameterAnnotation>
7 | LongMethod:FunctionsParser.kt$fun getFunctionDataList( ksFunctionDeclarationList: List<KSFunctionDeclaration>, logger: KSPLogger ): List<FunctionData>
8 | LongMethod:ParameterParser.kt$fun getParamAnnotationList(ksValueParameter: KSValueParameter, logger: KSPLogger): List<ParameterAnnotation>
9 | TooManyFunctions:KSValueParameterExt.kt$de.jensklingenberg.ktorfit.utils.KSValueParameterExt.kt
10 | TooManyFunctions:Utils.kt$de.jensklingenberg.ktorfit.utils.Utils.kt
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/MultiplatformExample/iosApp/Pods/Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | ${PODS_DEVELOPMENT_LANGUAGE}
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ktorfit-ksp/src/test/kotlin/de/jensklingenberg/ktorfit/Utils.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import com.tschuchort.compiletesting.KotlinCompilation
4 | import com.tschuchort.compiletesting.SourceFile
5 | import com.tschuchort.compiletesting.configureKsp
6 | import com.tschuchort.compiletesting.kspIncremental
7 | import com.tschuchort.compiletesting.kspProcessorOptions
8 | import com.tschuchort.compiletesting.kspWithCompilation
9 | import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
10 |
11 | @OptIn(ExperimentalCompilerApi::class)
12 | fun getCompilation(
13 | sources: List,
14 | kspArgs: MutableMap = mutableMapOf(),
15 | ): KotlinCompilation =
16 | KotlinCompilation().apply {
17 | this.sources = sources
18 | inheritClassPath = true
19 |
20 | configureKsp {
21 | kspProcessorOptions = kspArgs
22 | symbolProcessorProviders += KtorfitProcessorProvider()
23 | }
24 | kspIncremental = true
25 | kspWithCompilation = true
26 | }
27 |
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonTest/kotlin/de/jensklingenberg/ktorfit/TestStringToIntRequestConverter.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit
2 |
3 | import de.jensklingenberg.ktorfit.converter.Converter
4 | import kotlin.reflect.KClass
5 |
6 | class TestStringToIntRequestConverter : Converter.Factory {
7 | private fun supportedType(
8 | parameterType: KClass<*>,
9 | requestType: KClass<*>,
10 | ): Boolean {
11 | val parameterIsString = parameterType == String::class
12 | val requestIsInt = requestType == Int::class
13 | return parameterIsString && requestIsInt
14 | }
15 |
16 | class Test : Converter.RequestParameterConverter {
17 | override fun convert(data: Any): Any {
18 | return (data as String).toInt()
19 | }
20 | }
21 |
22 | override fun requestParameterConverter(
23 | parameterType: KClass<*>,
24 | requestType: KClass<*>,
25 | ): Converter.RequestParameterConverter? {
26 | if (!supportedType(parameterType, requestType)) return null
27 | return Test()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ktorfit-annotations/src/commonMain/kotlin/de/jensklingenberg/ktorfit/http/Query.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.http
2 |
3 | /**
4 | * Used for query parameters
5 | *
6 | * ```
7 | * @GET("comments")
8 | * suspend fun getCommentsById(@Query("postId") postId: String): List
9 | * ```
10 | * A request with getCommentsById(3) will result in the relative URL “comments?postId=3”
11 | *
12 | * ```
13 | * @GET("comments")
14 | * suspend fun getCommentsById(@Query("postId") postId: List): List
15 | * ```
16 | *
17 | * A request with getCommentsById(listOf("3",null,"4")) will result in the relative URL “comments?postId=3&postId=4”
18 | *
19 | * @param value The default value will be replaced with the name of the parameter that is annotated.It is the key of the query parameter.
20 | * null values are ignored
21 | * @param encoded true means that this value is already URL encoded and will not be encoded again
22 | */
23 | @Target(AnnotationTarget.VALUE_PARAMETER)
24 | annotation class Query(
25 | val value: String = "KTORFIT_DEFAULT_VALUE",
26 | val encoded: Boolean = false
27 | )
28 |
--------------------------------------------------------------------------------
/ktorfit-lib-core/src/commonTest/kotlin/de/jensklingenberg/ktorfit/internal/TypeDataTest.kt:
--------------------------------------------------------------------------------
1 | package de.jensklingenberg.ktorfit.internal
2 |
3 | import de.jensklingenberg.ktorfit.converter.TypeData
4 | import io.ktor.util.reflect.typeInfo
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertTrue
8 |
9 | class TypeDataTest {
10 | @Test
11 | fun testTypeDataCreator() {
12 | val typeData = TypeData.createTypeData("kotlin.Map", typeInfo