From ad966b714d73afdd76af1627243a3455a5c5eb98 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Wed, 8 Jun 2022 10:46:40 +0700 Subject: [PATCH 1/9] #178 update to ktor 2.0.1 --- gradle/libs.versions.toml | 6 +-- .../icerock/moko/network/LanguageProvider.kt | 4 +- .../dev/icerock/moko/network/HttpExt.kt | 6 ++- .../icerock/moko/network/LanguageProvider.kt | 4 +- .../ExceptionPlugin.kt} | 17 ++++---- .../LanguagePlugin.kt} | 20 ++++----- .../RefreshTokenFeature.kt | 41 ++++++++++--------- .../TokenPlugin.kt} | 20 ++++----- .../commonTest/kotlin/ExceptionFeatureTest.kt | 5 +-- .../commonTest/kotlin/LanguageFeatureTest.kt | 11 +++-- .../kotlin/RefreshTokenFeatureTest.kt | 14 +++---- .../src/commonTest/kotlin/TokenFeatureTest.kt | 12 +++--- .../icerock/moko/network/LanguageProvider.kt | 4 +- .../icerock/moko/network/LanguageProvider.kt | 4 +- .../com/icerockdev/library/TestViewModel.kt | 10 ++--- .../src/commonTest/kotlin/createHttpClient.kt | 4 +- 16 files changed, 88 insertions(+), 94 deletions(-) rename network/src/commonMain/kotlin/dev/icerock/moko/network/{features/ExceptionFeature.kt => plugins/ExceptionPlugin.kt} (72%) rename network/src/commonMain/kotlin/dev/icerock/moko/network/{features/LanguageFeature.kt => plugins/LanguagePlugin.kt} (61%) rename network/src/commonMain/kotlin/dev/icerock/moko/network/{features => plugins}/RefreshTokenFeature.kt (64%) rename network/src/commonMain/kotlin/dev/icerock/moko/network/{features/TokenFeature.kt => plugins/TokenPlugin.kt} (62%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2ef20d5..56e9de8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlinVersion = "1.6.10" +kotlinVersion = "1.6.20" # android lifecycleViewModelVersion = "2.3.1" @@ -19,7 +19,7 @@ mokoResourcesVersion = "0.18.0" mokoMvvmVersion = "0.12.0" mokoErrorsVersion = "0.6.0" mokoTestVersion = "0.6.1" -mokoNetworkVersion = "0.17.0" +mokoNetworkVersion = "0.17.2" # tests espressoCoreVersion = "3.2.0" @@ -29,7 +29,7 @@ androidxTestVersion = "1.3.0" robolectricVersion = "4.6.1" # other -ktorClientVersion = "1.6.7" +ktorClientVersion = "2.0.1" kbignumVersion = "2.4.12" multidexVersion = "2.0.1" diff --git a/network/src/androidMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt b/network/src/androidMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt index 67838a0..7eed8ac 100644 --- a/network/src/androidMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt +++ b/network/src/androidMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt @@ -6,9 +6,9 @@ package dev.icerock.moko.network import android.content.res.Resources import androidx.core.os.ConfigurationCompat -import dev.icerock.moko.network.features.LanguageFeature +import dev.icerock.moko.network.plugins.LanguagePlugin -actual class LanguageProvider : LanguageFeature.LanguageCodeProvider { +actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { override fun getLanguageCode(): String? { return ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0).language } diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt index 06ded1e..4c39958 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt @@ -7,8 +7,10 @@ package dev.icerock.moko.network import dev.icerock.moko.network.exceptions.ResponseException import io.ktor.client.HttpClient import io.ktor.client.call.ReceivePipelineException +import io.ktor.client.call.body import io.ktor.client.request.request import io.ktor.client.request.url +import io.ktor.client.request.setBody import io.ktor.client.utils.EmptyContent import io.ktor.http.ContentType import io.ktor.http.Headers @@ -36,8 +38,8 @@ suspend inline fun HttpClient.createRequest( method = methodType url(path) if (contentType != null) contentType(contentType) - this.body = body - } + setBody(body) + }.body() } catch (e: ReceivePipelineException) { if (e.cause is ResponseException) { throw e.cause diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt index 9829d59..54eaa90 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt @@ -4,7 +4,7 @@ package dev.icerock.moko.network -import dev.icerock.moko.network.features.LanguageFeature +import dev.icerock.moko.network.plugins.LanguagePlugin @Suppress("EmptyDefaultConstructor") -expect class LanguageProvider() : LanguageFeature.LanguageCodeProvider +expect class LanguageProvider() : LanguagePlugin.LanguageCodeProvider diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/ExceptionFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt similarity index 72% rename from network/src/commonMain/kotlin/dev/icerock/moko/network/features/ExceptionFeature.kt rename to network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt index 4d674a2..a20af64 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/ExceptionFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt @@ -2,36 +2,35 @@ * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -package dev.icerock.moko.network.features +package dev.icerock.moko.network.plugins import dev.icerock.moko.network.exceptionfactory.ExceptionFactory import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.statement.HttpResponsePipeline import io.ktor.http.isSuccess import io.ktor.util.AttributeKey import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.charsets.Charset import io.ktor.utils.io.core.readText -import io.ktor.utils.io.readRemaining -class ExceptionFeature(private val exceptionFactory: ExceptionFactory) { +class ExceptionPlugin(private val exceptionFactory: ExceptionFactory) { class Config { var exceptionFactory: ExceptionFactory? = null - fun build() = ExceptionFeature( + fun build() = ExceptionPlugin( exceptionFactory ?: throw IllegalArgumentException("Exception factory should be contain") ) } - companion object Feature : HttpClientFeature { + companion object Plugin : HttpClientPlugin { - override val key = AttributeKey("ExceptionFeature") + override val key = AttributeKey("ExceptionPlugin") override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() - override fun install(feature: ExceptionFeature, scope: HttpClient) { + override fun install(plugin: ExceptionPlugin, scope: HttpClient) { scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { (_, body) -> if (body !is ByteReadChannel) return@intercept @@ -39,7 +38,7 @@ class ExceptionFeature(private val exceptionFactory: ExceptionFactory) { if (!response.status.isSuccess()) { val packet = body.readRemaining() val responseString = packet.readText(charset = Charset.forName("UTF-8")) - throw feature.exceptionFactory.createException( + throw plugin.exceptionFactory.createException( request = context.request, response = context.response, responseBody = responseString diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/LanguageFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/LanguagePlugin.kt similarity index 61% rename from network/src/commonMain/kotlin/dev/icerock/moko/network/features/LanguageFeature.kt rename to network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/LanguagePlugin.kt index cbeb573..d2cc374 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/LanguageFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/LanguagePlugin.kt @@ -2,37 +2,37 @@ * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -package dev.icerock.moko.network.features +package dev.icerock.moko.network.plugins import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpRequestPipeline import io.ktor.client.request.header import io.ktor.util.AttributeKey -class LanguageFeature private constructor( +class LanguagePlugin private constructor( private val languageHeaderName: String, - private val languageProvider: LanguageFeature.LanguageCodeProvider + private val languageProvider: LanguagePlugin.LanguageCodeProvider ) { class Config { var languageHeaderName: String? = null var languageCodeProvider: LanguageCodeProvider? = null - fun build() = LanguageFeature( + fun build() = LanguagePlugin( languageHeaderName ?: throw IllegalArgumentException("HeaderName should be contain"), languageCodeProvider ?: throw IllegalArgumentException("LanguageCodeProvider should be contain") ) } - companion object Feature : HttpClientFeature { - override val key = AttributeKey("LanguageFeature") + companion object Plugin : HttpClientPlugin { + override val key = AttributeKey("LanguagePlugin") override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() - override fun install(feature: LanguageFeature, scope: HttpClient) { + override fun install(plugin: LanguagePlugin, scope: HttpClient) { scope.requestPipeline.intercept(HttpRequestPipeline.State) { - feature.languageProvider.getLanguageCode()?.apply { - context.header(feature.languageHeaderName, this) + plugin.languageProvider.getLanguageCode()?.apply { + context.header(plugin.languageHeaderName, this) } } } diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/RefreshTokenFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt similarity index 64% rename from network/src/commonMain/kotlin/dev/icerock/moko/network/features/RefreshTokenFeature.kt rename to network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt index d36a961..874b381 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/RefreshTokenFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt @@ -2,21 +2,22 @@ * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -package dev.icerock.moko.network.features +package dev.icerock.moko.network.plugins import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpRequest import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.request import io.ktor.client.request.takeFrom import io.ktor.client.statement.HttpReceivePipeline import io.ktor.client.statement.HttpResponse +import io.ktor.client.statement.request import io.ktor.http.HttpStatusCode import io.ktor.util.AttributeKey import kotlinx.coroutines.sync.Mutex -class RefreshTokenFeature( +class RefreshTokenPlugin( private val updateTokenHandler: suspend () -> Boolean, private val isCredentialsActual: (HttpRequest) -> Boolean ) { @@ -25,50 +26,50 @@ class RefreshTokenFeature( var updateTokenHandler: (suspend () -> Boolean)? = null var isCredentialsActual: ((HttpRequest) -> Boolean)? = null - fun build() = RefreshTokenFeature( + fun build() = RefreshTokenPlugin( updateTokenHandler ?: throw IllegalArgumentException("updateTokenHandler should be passed"), isCredentialsActual ?: throw IllegalArgumentException("isCredentialsActual should be passed") ) } - companion object Feature : HttpClientFeature { + companion object Plugin : HttpClientPlugin { - private val refreshTokenHttpFeatureMutex = Mutex() + private val refreshTokenHttpPluginMutex = Mutex() - override val key = AttributeKey("RefreshTokenFeature") + override val key = AttributeKey("RefreshTokenPlugin") override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() - override fun install(feature: RefreshTokenFeature, scope: HttpClient) { - scope.receivePipeline.intercept(HttpReceivePipeline.After) { subject -> - if (context.response.status != HttpStatusCode.Unauthorized) { + override fun install(plugin: RefreshTokenPlugin, scope: HttpClient) { + scope.receivePipeline.intercept(HttpReceivePipeline.After) { + if (subject.status != HttpStatusCode.Unauthorized) { proceedWith(subject) return@intercept } - refreshTokenHttpFeatureMutex.lock() + refreshTokenHttpPluginMutex.lock() // If token of the request isn't actual, then token has already been updated and // let's just to try repeat request - if (!feature.isCredentialsActual(context.request)) { - refreshTokenHttpFeatureMutex.unlock() - val requestBuilder = HttpRequestBuilder().takeFrom(context.request) - val result: HttpResponse = context.client!!.request(requestBuilder) + if (!plugin.isCredentialsActual(subject.request)) { + refreshTokenHttpPluginMutex.unlock() + val requestBuilder = HttpRequestBuilder().takeFrom(subject.request) + val result: HttpResponse = scope.request(requestBuilder) proceedWith(result) return@intercept } // Else if token of the request is actual (same as in the storage), then need to send // refresh request. - if (feature.updateTokenHandler.invoke()) { + if (plugin.updateTokenHandler.invoke()) { // If the request refresh was successful, then let's just to try repeat request - refreshTokenHttpFeatureMutex.unlock() - val requestBuilder = HttpRequestBuilder().takeFrom(context.request) - val result: HttpResponse = context.client!!.request(requestBuilder) + refreshTokenHttpPluginMutex.unlock() + val requestBuilder = HttpRequestBuilder().takeFrom(subject.request) + val result: HttpResponse = scope.request(requestBuilder) proceedWith(result) } else { // If the request refresh was unsuccessful - refreshTokenHttpFeatureMutex.unlock() + refreshTokenHttpPluginMutex.unlock() proceedWith(subject) } } diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt similarity index 62% rename from network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt rename to network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt index 45fd183..5a5b2b3 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/features/TokenFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt @@ -2,15 +2,15 @@ * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -package dev.icerock.moko.network.features +package dev.icerock.moko.network.plugins import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpRequestPipeline import io.ktor.client.request.header import io.ktor.util.AttributeKey -class TokenFeature private constructor( +class TokenPlugin private constructor( private val tokenHeaderName: String, private val tokenProvider: TokenProvider ) { @@ -18,22 +18,22 @@ class TokenFeature private constructor( class Config { var tokenHeaderName: String? = null var tokenProvider: TokenProvider? = null - fun build() = TokenFeature( + fun build() = TokenPlugin( tokenHeaderName ?: throw IllegalArgumentException("HeaderName should be contain"), tokenProvider ?: throw IllegalArgumentException("TokenProvider should be contain") ) } - companion object Feature : HttpClientFeature { - override val key = AttributeKey("TokenFeature") + companion object Plugin : HttpClientPlugin { + override val key = AttributeKey("TokenPlugin") override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() - override fun install(feature: TokenFeature, scope: HttpClient) { + override fun install(plugin: TokenPlugin, scope: HttpClient) { scope.requestPipeline.intercept(HttpRequestPipeline.State) { - feature.tokenProvider.getToken()?.apply { - context.headers.remove(feature.tokenHeaderName) - context.header(feature.tokenHeaderName, this) + plugin.tokenProvider.getToken()?.apply { + context.headers.remove(plugin.tokenHeaderName) + context.header(plugin.tokenHeaderName, this) } } } diff --git a/network/src/commonTest/kotlin/ExceptionFeatureTest.kt b/network/src/commonTest/kotlin/ExceptionFeatureTest.kt index cac2f8d..dd9654c 100644 --- a/network/src/commonTest/kotlin/ExceptionFeatureTest.kt +++ b/network/src/commonTest/kotlin/ExceptionFeatureTest.kt @@ -8,11 +8,10 @@ import dev.icerock.moko.network.exceptionfactory.parser.ValidationExceptionParse import dev.icerock.moko.network.exceptions.ErrorException import dev.icerock.moko.network.exceptions.ResponseException import dev.icerock.moko.network.exceptions.ValidationException -import dev.icerock.moko.network.features.ExceptionFeature +import dev.icerock.moko.network.plugins.ExceptionPlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler -import io.ktor.client.engine.mock.respondBadRequest import io.ktor.client.engine.mock.respondError import io.ktor.client.engine.mock.respondOk import io.ktor.client.request.get @@ -247,7 +246,7 @@ class ExceptionFeatureTest { addHandler(handler) } - install(ExceptionFeature) { + install(ExceptionPlugin) { exceptionFactory = HttpExceptionFactory( defaultParser = ErrorExceptionParser(json), customParsers = mapOf( diff --git a/network/src/commonTest/kotlin/LanguageFeatureTest.kt b/network/src/commonTest/kotlin/LanguageFeatureTest.kt index 3e1aad1..9679889 100644 --- a/network/src/commonTest/kotlin/LanguageFeatureTest.kt +++ b/network/src/commonTest/kotlin/LanguageFeatureTest.kt @@ -2,8 +2,7 @@ * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -import dev.icerock.moko.network.features.LanguageFeature -import dev.icerock.moko.network.features.TokenFeature +import dev.icerock.moko.network.plugins.LanguagePlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler @@ -20,7 +19,7 @@ class LanguageFeatureTest { @Test fun `language added when exist`() { val client = createMockClient( - provider = object : LanguageFeature.LanguageCodeProvider { + provider = object : LanguagePlugin.LanguageCodeProvider { override fun getLanguageCode(): String = "ru" }, handler = { request -> @@ -39,7 +38,7 @@ class LanguageFeatureTest { @Test fun `language not added when not exist`() { val client = createMockClient( - provider = object : LanguageFeature.LanguageCodeProvider { + provider = object : LanguagePlugin.LanguageCodeProvider { override fun getLanguageCode(): String? = null }, handler = { request -> @@ -56,7 +55,7 @@ class LanguageFeatureTest { } private fun createMockClient( - provider: LanguageFeature.LanguageCodeProvider, + provider: LanguagePlugin.LanguageCodeProvider, handler: MockRequestHandler ): HttpClient { return HttpClient(MockEngine) { @@ -64,7 +63,7 @@ class LanguageFeatureTest { addHandler(handler) } - install(LanguageFeature) { + install(LanguagePlugin) { this.languageHeaderName = LANGUAGE_HEADER_NAME this.languageCodeProvider = provider } diff --git a/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt b/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt index c935660..54efba0 100644 --- a/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt +++ b/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt @@ -2,23 +2,19 @@ * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -import dev.icerock.moko.network.features.RefreshTokenFeature -import dev.icerock.moko.network.features.TokenFeature +import dev.icerock.moko.network.plugins.RefreshTokenFeature +import dev.icerock.moko.network.plugins.TokenPlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler -import io.ktor.client.engine.mock.respondBadRequest import io.ktor.client.engine.mock.respondError import io.ktor.client.engine.mock.respondOk import io.ktor.client.request.get -import io.ktor.client.request.header import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.request import io.ktor.http.HttpStatusCode import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -54,7 +50,7 @@ class RefreshTokenFeatureTest { val validToken = "124" val tokenHolder = MutableStateFlow(invalidToken) val client = createMockClient( - tokenProvider = object : TokenFeature.TokenProvider { + tokenProvider = object : TokenPlugin.TokenProvider { override fun getToken(): String? { return tokenHolder.value } @@ -84,7 +80,7 @@ class RefreshTokenFeatureTest { } private fun createMockClient( - tokenProvider: TokenFeature.TokenProvider? = null, + tokenProvider: TokenPlugin.TokenProvider? = null, featureConfig: RefreshTokenFeature.Config.() -> Unit, handler: MockRequestHandler ): HttpClient { @@ -94,7 +90,7 @@ class RefreshTokenFeatureTest { } if (tokenProvider != null) { - install(TokenFeature) { + install(TokenPlugin) { this.tokenHeaderName = AUTH_HEADER_NAME this.tokenProvider = tokenProvider } diff --git a/network/src/commonTest/kotlin/TokenFeatureTest.kt b/network/src/commonTest/kotlin/TokenFeatureTest.kt index 378612a..5fb826b 100644 --- a/network/src/commonTest/kotlin/TokenFeatureTest.kt +++ b/network/src/commonTest/kotlin/TokenFeatureTest.kt @@ -2,7 +2,7 @@ * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -import dev.icerock.moko.network.features.TokenFeature +import dev.icerock.moko.network.plugins.TokenPlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler @@ -12,8 +12,6 @@ import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.Json -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -21,7 +19,7 @@ class TokenFeatureTest { @Test fun `token added when exist`() { val client = createMockClient( - tokenProvider = object : TokenFeature.TokenProvider { + tokenProvider = object : TokenPlugin.TokenProvider { override fun getToken(): String { return "mytoken" } @@ -42,7 +40,7 @@ class TokenFeatureTest { @Test fun `token not added when not exist`() { val client = createMockClient( - tokenProvider = object : TokenFeature.TokenProvider { + tokenProvider = object : TokenPlugin.TokenProvider { override fun getToken(): String? { return null } @@ -61,7 +59,7 @@ class TokenFeatureTest { } private fun createMockClient( - tokenProvider: TokenFeature.TokenProvider, + tokenProvider: TokenPlugin.TokenProvider, handler: MockRequestHandler ): HttpClient { return HttpClient(MockEngine) { @@ -69,7 +67,7 @@ class TokenFeatureTest { addHandler(handler) } - install(TokenFeature) { + install(TokenPlugin) { this.tokenHeaderName = AUTH_HEADER_NAME this.tokenProvider = tokenProvider } diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt index 8ccf49b..f09217c 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt @@ -4,12 +4,12 @@ package dev.icerock.moko.network -import dev.icerock.moko.network.features.LanguageFeature +import dev.icerock.moko.network.plugins.LanguagePlugin import platform.Foundation.NSLocale import platform.Foundation.currentLocale import platform.Foundation.languageCode -actual class LanguageProvider : LanguageFeature.LanguageCodeProvider { +actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { override fun getLanguageCode(): String? { return NSLocale.currentLocale.languageCode } diff --git a/network/src/jvmMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt b/network/src/jvmMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt index e610310..86d2d8b 100644 --- a/network/src/jvmMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt +++ b/network/src/jvmMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt @@ -1,9 +1,9 @@ package dev.icerock.moko.network -import dev.icerock.moko.network.features.LanguageFeature +import dev.icerock.moko.network.plugins.LanguagePlugin import java.util.Locale -actual class LanguageProvider : LanguageFeature.LanguageCodeProvider { +actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { override fun getLanguageCode(): String? { return Locale.getDefault().displayLanguage } diff --git a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt index 727fe5b..6f27837 100644 --- a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt +++ b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt @@ -13,8 +13,8 @@ import dev.icerock.moko.mvvm.livedata.MutableLiveData import dev.icerock.moko.mvvm.livedata.readOnly import dev.icerock.moko.mvvm.viewmodel.ViewModel import dev.icerock.moko.network.LanguageProvider -import dev.icerock.moko.network.features.LanguageFeature -import dev.icerock.moko.network.features.TokenFeature +import dev.icerock.moko.network.plugins.LanguagePlugin +import dev.icerock.moko.network.plugins.TokenPlugin import dev.icerock.moko.network.generated.apis.PetApi import dev.icerock.moko.resources.desc.desc import io.ktor.client.HttpClient @@ -38,7 +38,7 @@ class TestViewModel : ViewModel() { ) private val httpClient = HttpClient { - install(LanguageFeature) { + install(LanguagePlugin) { languageHeaderName = "X-Language" languageCodeProvider = LanguageProvider() } @@ -51,9 +51,9 @@ class TestViewModel : ViewModel() { } } - install(TokenFeature) { + install(TokenPlugin) { tokenHeaderName = "Authorization" - tokenProvider = object : TokenFeature.TokenProvider { + tokenProvider = object : TokenPlugin.TokenProvider { override fun getToken(): String? = "ed155d0a445e4b4fbd878fe1f3bc1b7f" } } diff --git a/sample/mpp-library/src/commonTest/kotlin/createHttpClient.kt b/sample/mpp-library/src/commonTest/kotlin/createHttpClient.kt index aafa8de..7b776ae 100644 --- a/sample/mpp-library/src/commonTest/kotlin/createHttpClient.kt +++ b/sample/mpp-library/src/commonTest/kotlin/createHttpClient.kt @@ -5,7 +5,7 @@ import dev.icerock.moko.network.exceptionfactory.HttpExceptionFactory import dev.icerock.moko.network.exceptionfactory.parser.ErrorExceptionParser import dev.icerock.moko.network.exceptionfactory.parser.ValidationExceptionParser -import dev.icerock.moko.network.features.ExceptionFeature +import dev.icerock.moko.network.plugins.ExceptionPlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.MockRequestHandler @@ -23,7 +23,7 @@ fun createMockClient( addHandler(handler) } - install(ExceptionFeature) { + install(ExceptionPlugin) { exceptionFactory = HttpExceptionFactory( defaultParser = ErrorExceptionParser(json), customParsers = mapOf( From 3af4459ead875f9aea2cae1dab31c7320673574b Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Thu, 16 Jun 2022 09:35:10 +0700 Subject: [PATCH 2/9] #178 fixed generator template for ktor 2.0.1 --- .../main/resources/kotlin-ktor-client/api.mustache | 14 +++++++++----- .../kotlin/com/icerockdev/library/TestViewModel.kt | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/network-generator/src/main/resources/kotlin-ktor-client/api.mustache b/network-generator/src/main/resources/kotlin-ktor-client/api.mustache index e2a0286..4f35738 100755 --- a/network-generator/src/main/resources/kotlin-ktor-client/api.mustache +++ b/network-generator/src/main/resources/kotlin-ktor-client/api.mustache @@ -4,10 +4,12 @@ package {{apiPackage}} {{#imports}}import {{import}} {{/imports}} +import io.ktor.client.call.body import io.ktor.client.HttpClient import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.request import io.ktor.client.request.forms.FormDataContent +import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.HttpMethod import io.ktor.http.Parameters @@ -19,6 +21,8 @@ import kotlinx.serialization.builtins.SetSerializer import kotlinx.serialization.builtins.serializer import io.ktor.client.call.ReceivePipelineException import io.ktor.http.content.TextContent +import io.ktor.http.encodedPath +import io.ktor.http.path {{#operations}} {{>classes_modifiers}}interface {{classname}} { @@ -79,7 +83,7 @@ import io.ktor.http.content.TextContent {{#bodyParams}} @Suppress("SENSELESS_COMPARISON") if({{paramName}} != null) { - builder.body = TextContent( + builder.setBody(TextContent( {{#dataType}} {{#isArray}} {{#uniqueItems}} @@ -106,7 +110,7 @@ import io.ktor.http.content.TextContent {{paramName}}), {{/dataType}} ContentType.Application.Json.withoutParameters() - ) + )) } {{/bodyParams}} @@ -116,7 +120,7 @@ import io.ktor.http.content.TextContent {{paramName}}?.let { append("{{baseName}}", it.toString()) } {{/formParams}} }) - builder.body = formData + builder.setBody(formData) {{/hasFormParams}} with(builder.headers) { append("Accept", "application/json") @@ -127,12 +131,12 @@ import io.ktor.http.content.TextContent {{#vendorExtensions.x-successResponse}} val serializer = {{>property_serializer}} //not primitive type - val result: String = _httpClient.request(builder) + val result: String = _httpClient.request(builder).body() return _json.decodeFromString(serializer, result) {{/vendorExtensions.x-successResponse}} {{/returnType}} {{^returnType}} - return _httpClient.request(builder) + return _httpClient.request(builder).body() {{/returnType}} } catch (pipeline: ReceivePipelineException) { throw pipeline.cause diff --git a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt index 6f27837..b391101 100644 --- a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt +++ b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt @@ -18,9 +18,9 @@ import dev.icerock.moko.network.plugins.TokenPlugin import dev.icerock.moko.network.generated.apis.PetApi import dev.icerock.moko.resources.desc.desc import io.ktor.client.HttpClient -import io.ktor.client.features.logging.LogLevel -import io.ktor.client.features.logging.Logger -import io.ktor.client.features.logging.Logging +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import news.apis.NewsApi From 2c353b6deb963ad30fd6ec3586dbc915df764249 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Thu, 16 Jun 2022 10:17:07 +0700 Subject: [PATCH 3/9] #178 replace deprecated ios to darwin --- .../dev/icerock/moko/network/NetworkConnectionError.kt | 4 ++-- .../dev/icerock/moko/network/createHttpClientEngine.kt | 4 ++-- .../kotlin/dev/icerock/moko/network/isSSLException.kt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt index 9424124..d051375 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt @@ -4,11 +4,11 @@ package dev.icerock.moko.network -import io.ktor.client.engine.ios.IosHttpRequestException +import io.ktor.client.engine.darwin.DarwinHttpRequestException actual fun Throwable.isNetworkConnectionError(): Boolean { return when (this) { - is IosHttpRequestException -> isSSLException().not() + is DarwinHttpRequestException -> isSSLException().not() else -> false } } diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt index eace7d5..5e2670b 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt @@ -5,11 +5,11 @@ package dev.icerock.moko.network import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.ios.Ios +import io.ktor.client.engine.darwin.Darwin actual fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit): HttpClientEngine { val config = HttpClientEngineConfig().also(block) - return Ios.create { + return Darwin.create { this.configureSession { config.iosTimeoutIntervalForRequest?.let { setTimeoutIntervalForRequest(it) } config.iosTimeoutIntervalForResource?.let { setTimeoutIntervalForResource(it) } diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/isSSLException.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/isSSLException.kt index 6dea821..087c057 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/isSSLException.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/isSSLException.kt @@ -4,7 +4,7 @@ package dev.icerock.moko.network -import io.ktor.client.engine.ios.IosHttpRequestException +import io.ktor.client.engine.darwin.DarwinHttpRequestException import platform.Foundation.NSURLErrorCannotLoadFromNetwork import platform.Foundation.NSURLErrorClientCertificateRequired import platform.Foundation.NSURLErrorSecureConnectionFailed @@ -24,13 +24,13 @@ private val sslKeys = mapOf( ) actual fun Throwable.isSSLException(): Boolean { - val iosHttpException = this as? IosHttpRequestException ?: return false + val iosHttpException = this as? DarwinHttpRequestException ?: return false return sslKeys.keys.contains( iosHttpException.origin.code ) } actual fun Throwable.getSSLExceptionType(): SSLExceptionType? { - val iosHttpException = this as? IosHttpRequestException ?: return null + val iosHttpException = this as? DarwinHttpRequestException ?: return null return sslKeys[iosHttpException.origin.code] } From 65ccc0f004b10b23b91d8bc4e1c1c4ab7c7cbb39 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Thu, 7 Jul 2022 10:28:11 +0700 Subject: [PATCH 4/9] #178 fixed network tests --- .../moko/network/plugins/ExceptionPlugin.kt | 21 +++++++++---------- .../commonTest/kotlin/ExceptionFeatureTest.kt | 18 +++++++--------- .../commonTest/kotlin/LanguageFeatureTest.kt | 4 ++-- ...atureTest.kt => RefreshTokenPluginTest.kt} | 17 +++++++-------- .../src/commonTest/kotlin/TokenFeatureTest.kt | 5 ++--- 5 files changed, 30 insertions(+), 35 deletions(-) rename network/src/commonTest/kotlin/{RefreshTokenFeatureTest.kt => RefreshTokenPluginTest.kt} (88%) diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt index a20af64..99fd63a 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt @@ -7,10 +7,11 @@ package dev.icerock.moko.network.plugins import dev.icerock.moko.network.exceptionfactory.ExceptionFactory import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpClientPlugin -import io.ktor.client.statement.HttpResponsePipeline +import io.ktor.client.plugins.HttpSend +import io.ktor.client.plugins.plugin +import io.ktor.client.statement.bodyAsChannel import io.ktor.http.isSuccess import io.ktor.util.AttributeKey -import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.charsets.Charset import io.ktor.utils.io.core.readText @@ -31,20 +32,18 @@ class ExceptionPlugin(private val exceptionFactory: ExceptionFactory) { override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() override fun install(plugin: ExceptionPlugin, scope: HttpClient) { - scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { (_, body) -> - if (body !is ByteReadChannel) return@intercept - - val response = context.response - if (!response.status.isSuccess()) { - val packet = body.readRemaining() + scope.plugin(HttpSend).intercept { request -> + val call = execute(request) + if (!call.response.status.isSuccess()) { + val packet = call.response.bodyAsChannel().readRemaining() val responseString = packet.readText(charset = Charset.forName("UTF-8")) throw plugin.exceptionFactory.createException( - request = context.request, - response = context.response, + request = call.request, + response = call.response, responseBody = responseString ) } - proceedWith(subject) + call } } } diff --git a/network/src/commonTest/kotlin/ExceptionFeatureTest.kt b/network/src/commonTest/kotlin/ExceptionFeatureTest.kt index dd9654c..a7fdc6a 100644 --- a/network/src/commonTest/kotlin/ExceptionFeatureTest.kt +++ b/network/src/commonTest/kotlin/ExceptionFeatureTest.kt @@ -15,7 +15,6 @@ import io.ktor.client.engine.mock.MockRequestHandler import io.ktor.client.engine.mock.respondError import io.ktor.client.engine.mock.respondOk import io.ktor.client.request.get -import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json @@ -31,7 +30,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = false, actual = result.isFailure) @@ -54,7 +53,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -81,7 +80,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -106,7 +105,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -130,7 +129,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -165,7 +164,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -201,7 +200,7 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } assertEquals(expected = true, actual = result.isFailure) @@ -228,9 +227,8 @@ class ExceptionFeatureTest { } val result = runBlocking { - kotlin.runCatching { client.get("localhost") } + kotlin.runCatching { client.get("localhost") } } - assertEquals(expected = true, actual = result.isFailure) val exc = result.exceptionOrNull() assertTrue(actual = exc is ResponseException) diff --git a/network/src/commonTest/kotlin/LanguageFeatureTest.kt b/network/src/commonTest/kotlin/LanguageFeatureTest.kt index 9679889..9a9d2c7 100644 --- a/network/src/commonTest/kotlin/LanguageFeatureTest.kt +++ b/network/src/commonTest/kotlin/LanguageFeatureTest.kt @@ -29,7 +29,7 @@ class LanguageFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) @@ -48,7 +48,7 @@ class LanguageFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) diff --git a/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt b/network/src/commonTest/kotlin/RefreshTokenPluginTest.kt similarity index 88% rename from network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt rename to network/src/commonTest/kotlin/RefreshTokenPluginTest.kt index 54efba0..957db2a 100644 --- a/network/src/commonTest/kotlin/RefreshTokenFeatureTest.kt +++ b/network/src/commonTest/kotlin/RefreshTokenPluginTest.kt @@ -2,7 +2,7 @@ * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. */ -import dev.icerock.moko.network.plugins.RefreshTokenFeature +import dev.icerock.moko.network.plugins.RefreshTokenPlugin import dev.icerock.moko.network.plugins.TokenPlugin import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine @@ -10,7 +10,6 @@ import io.ktor.client.engine.mock.MockRequestHandler import io.ktor.client.engine.mock.respondError import io.ktor.client.engine.mock.respondOk import io.ktor.client.request.get -import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.request import io.ktor.http.HttpStatusCode import kotlinx.coroutines.flow.MutableStateFlow @@ -18,12 +17,12 @@ import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals -class RefreshTokenFeatureTest { +class RefreshTokenPluginTest { @Test fun `refresh token not called when request credentials not actual`() { val isFirstRequestHolder = MutableStateFlow(true) val client = createMockClient( - featureConfig = { + pluginConfig = { this.updateTokenHandler = { throw IllegalStateException("update token should not be called at all") } @@ -38,7 +37,7 @@ class RefreshTokenFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) @@ -55,7 +54,7 @@ class RefreshTokenFeatureTest { return tokenHolder.value } }, - featureConfig = { + pluginConfig = { this.updateTokenHandler = { tokenHolder.value = validToken true @@ -72,7 +71,7 @@ class RefreshTokenFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) @@ -81,7 +80,7 @@ class RefreshTokenFeatureTest { private fun createMockClient( tokenProvider: TokenPlugin.TokenProvider? = null, - featureConfig: RefreshTokenFeature.Config.() -> Unit, + pluginConfig: RefreshTokenPlugin.Config.() -> Unit, handler: MockRequestHandler ): HttpClient { return HttpClient(MockEngine) { @@ -95,7 +94,7 @@ class RefreshTokenFeatureTest { this.tokenProvider = tokenProvider } } - install(RefreshTokenFeature, featureConfig) + install(RefreshTokenPlugin, pluginConfig) } } diff --git a/network/src/commonTest/kotlin/TokenFeatureTest.kt b/network/src/commonTest/kotlin/TokenFeatureTest.kt index 5fb826b..49a1f94 100644 --- a/network/src/commonTest/kotlin/TokenFeatureTest.kt +++ b/network/src/commonTest/kotlin/TokenFeatureTest.kt @@ -9,7 +9,6 @@ import io.ktor.client.engine.mock.MockRequestHandler import io.ktor.client.engine.mock.respondBadRequest import io.ktor.client.engine.mock.respondOk import io.ktor.client.request.get -import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode import kotlinx.coroutines.runBlocking import kotlin.test.Test @@ -31,7 +30,7 @@ class TokenFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) @@ -52,7 +51,7 @@ class TokenFeatureTest { ) val result = runBlocking { - client.get("localhost") + client.get("localhost") } assertEquals(expected = HttpStatusCode.OK, actual = result.status) From efc11397daaea3a286ee91b05b7535edc9224df5 Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Thu, 7 Jul 2022 10:54:44 +0700 Subject: [PATCH 5/9] #178 fixed encoded path --- .../src/main/resources/kotlin-ktor-client/api.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network-generator/src/main/resources/kotlin-ktor-client/api.mustache b/network-generator/src/main/resources/kotlin-ktor-client/api.mustache index 4f35738..16121c4 100755 --- a/network-generator/src/main/resources/kotlin-ktor-client/api.mustache +++ b/network-generator/src/main/resources/kotlin-ktor-client/api.mustache @@ -64,7 +64,7 @@ import io.ktor.http.path takeFrom(_basePath) encodedPath = encodedPath.let { startingPath -> path("{{path}}") - return@let startingPath + encodedPath.substring(1) + return@let startingPath + encodedPath } {{#hasQueryParams}} @Suppress("UNNECESSARY_SAFE_CALL") From 1d40381989f2d2d075bc062ac2bb8d6fb2691b4f Mon Sep 17 00:00:00 2001 From: Andrey Dorofeev Date: Fri, 29 Jul 2022 16:17:01 +0700 Subject: [PATCH 6/9] #178 fixed dynamic user agent feature to plugin --- .../dev/icerock/moko/network/plugins/DynamicUserAgent.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/DynamicUserAgent.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/DynamicUserAgent.kt index cdce6e4..b693197 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/DynamicUserAgent.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/DynamicUserAgent.kt @@ -5,7 +5,7 @@ package dev.icerock.moko.network.features import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpRequestPipeline import io.ktor.client.request.header import io.ktor.http.HttpHeaders @@ -16,7 +16,7 @@ class DynamicUserAgent( ) { class Config(var agentProvider: () -> String? = { null }) - companion object Feature : HttpClientFeature { + companion object Feature : HttpClientPlugin { override val key: AttributeKey = AttributeKey("DynamicUserAgent") override fun prepare(block: Config.() -> Unit): DynamicUserAgent = From e83efae06e27d3bc82f1a6b2d9825cb9b2029051 Mon Sep 17 00:00:00 2001 From: Aleksey Mikhailov Date: Sun, 31 Jul 2022 19:57:22 +0700 Subject: [PATCH 7/9] #178 fix build --- gradle.properties | 2 + gradle/libs.versions.toml | 3 +- .../dev/icerock/moko/network/HttpExt.kt | 2 +- ...hTokenFeature.kt => RefreshTokenPlugin.kt} | 6 +- .../dev/icerock/moko/network/IosWebSocket.kt | 152 ------------------ .../icerock/moko/network/LanguageProvider.kt | 2 +- .../moko/network/ReceiveMessageException.kt | 9 -- .../moko/network/SendMessageException.kt | 9 -- .../moko/network/WSIosHttpClientEngine.kt | 76 --------- .../moko/network/createHttpClientEngine.kt | 2 +- sample/mpp-library/build.gradle.kts | 1 + .../com/icerockdev/library/TestViewModel.kt | 20 +-- 12 files changed, 22 insertions(+), 262 deletions(-) rename network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/{RefreshTokenFeature.kt => RefreshTokenPlugin.kt} (92%) delete mode 100644 network/src/iosMain/kotlin/dev/icerock/moko/network/IosWebSocket.kt delete mode 100644 network/src/iosMain/kotlin/dev/icerock/moko/network/ReceiveMessageException.kt delete mode 100644 network/src/iosMain/kotlin/dev/icerock/moko/network/SendMessageException.kt delete mode 100644 network/src/iosMain/kotlin/dev/icerock/moko/network/WSIosHttpClientEngine.kt diff --git a/gradle.properties b/gradle.properties index 8154519..17113cd 100755 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,8 @@ kotlin.native.enableDependencyPropagation=false kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.mpp.enableCompatibilityMetadataVariant=true +kotlin.native.binary.memoryModel=experimental + android.useAndroidX=true mobile.multiplatform.iosTargetWarning=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3f207b8..9e40415 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ kotlinxSerializationVersion = "1.3.2" coroutinesVersion = "1.6.0-native-mt" # moko -mokoResourcesVersion = "0.18.0" +mokoResourcesVersion = "0.20.1" mokoMvvmVersion = "0.12.0" mokoErrorsVersion = "0.6.0" mokoTestVersion = "0.6.1" @@ -48,6 +48,7 @@ coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version ktorClientOkHttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorClientVersion" } ktorClient = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientVersion" } ktorClientLogging = { module = "io.ktor:ktor-client-logging", version.ref = "ktorClientVersion" } +ktorClientWebSocket = { module = "io.ktor:ktor-client-websockets", version.ref = "ktorClientVersion" } ktorClientMock = { module = "io.ktor:ktor-client-mock", version.ref = "ktorClientVersion" } ktorClientIos = { module = "io.ktor:ktor-client-ios", version.ref = "ktorClientVersion" } diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt index 4c39958..5e5686b 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt @@ -9,8 +9,8 @@ import io.ktor.client.HttpClient import io.ktor.client.call.ReceivePipelineException import io.ktor.client.call.body import io.ktor.client.request.request -import io.ktor.client.request.url import io.ktor.client.request.setBody +import io.ktor.client.request.url import io.ktor.client.utils.EmptyContent import io.ktor.http.ContentType import io.ktor.http.Headers diff --git a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenPlugin.kt similarity index 92% rename from network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt rename to network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenPlugin.kt index 874b381..aadc94f 100644 --- a/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenFeature.kt +++ b/network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/RefreshTokenPlugin.kt @@ -27,8 +27,10 @@ class RefreshTokenPlugin( var isCredentialsActual: ((HttpRequest) -> Boolean)? = null fun build() = RefreshTokenPlugin( - updateTokenHandler ?: throw IllegalArgumentException("updateTokenHandler should be passed"), - isCredentialsActual ?: throw IllegalArgumentException("isCredentialsActual should be passed") + updateTokenHandler + ?: throw IllegalArgumentException("updateTokenHandler should be passed"), + isCredentialsActual + ?: throw IllegalArgumentException("isCredentialsActual should be passed") ) } diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/IosWebSocket.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/IosWebSocket.kt deleted file mode 100644 index e5a5d35..0000000 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/IosWebSocket.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.icerock.moko.network - -import io.ktor.client.features.websocket.WebSocketException -import io.ktor.http.cio.websocket.CloseReason -import io.ktor.http.cio.websocket.DefaultWebSocketSession -import io.ktor.http.cio.websocket.ExperimentalWebSocketExtensionApi -import io.ktor.http.cio.websocket.Frame -import io.ktor.http.cio.websocket.WebSocketExtension -import io.ktor.http.cio.websocket.readText -import io.ktor.util.InternalAPI -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.SendChannel -import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.launch -import platform.Foundation.NSData -import platform.Foundation.NSError -import platform.Foundation.NSOperationQueue -import platform.Foundation.NSPOSIXErrorDomain -import platform.Foundation.NSURL -import platform.Foundation.NSURLSession -import platform.Foundation.NSURLSessionConfiguration -import platform.Foundation.NSURLSessionWebSocketCloseCode -import platform.Foundation.NSURLSessionWebSocketDelegateProtocol -import platform.Foundation.NSURLSessionWebSocketMessage -import platform.Foundation.NSURLSessionWebSocketTask -import platform.darwin.NSObject -import kotlin.coroutines.CoroutineContext - -internal class IosWebSocket( - socketEndpoint: NSURL, - override val coroutineContext: CoroutineContext -) : DefaultWebSocketSession { - internal val originResponse: CompletableDeferred = CompletableDeferred() - - private val webSocket: NSURLSessionWebSocketTask - - private val _incoming = Channel() - private val _outgoing = Channel() - private val _closeReason = CompletableDeferred() - - override val incoming: ReceiveChannel = _incoming - override val outgoing: SendChannel = _outgoing - override val closeReason: Deferred = _closeReason - - @ExperimentalWebSocketExtensionApi - override val extensions: List> - get() = emptyList() - - override var maxFrameSize: Long - get() = throw WebSocketException("websocket doesn't support max frame size.") - set(_) = throw WebSocketException("websocket doesn't support max frame size.") - - override suspend fun flush() = Unit - - @OptIn(ExperimentalWebSocketExtensionApi::class) - @InternalAPI - override fun start(negotiatedExtensions: List>) { - require(negotiatedExtensions.isEmpty()) { "Extensions are not supported." } - } - - init { - val urlSession = NSURLSession.sessionWithConfiguration( - configuration = NSURLSessionConfiguration.defaultSessionConfiguration(), - delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol { - override fun URLSession( - session: NSURLSession, - webSocketTask: NSURLSessionWebSocketTask, - didOpenWithProtocol: String? - ) { - originResponse.complete(didOpenWithProtocol) - } - - override fun URLSession( - session: NSURLSession, - webSocketTask: NSURLSessionWebSocketTask, - didCloseWithCode: NSURLSessionWebSocketCloseCode, - reason: NSData? - ) { - val closeReason = CloseReason( - code = CloseReason.Codes.PROTOCOL_ERROR, - message = "$didCloseWithCode : ${reason.toString()}" - ) - _closeReason.complete(closeReason) - } - }, - delegateQueue = NSOperationQueue.currentQueue() - ) - webSocket = urlSession.webSocketTaskWithURL(socketEndpoint) - - CoroutineScope(coroutineContext).launch { - _outgoing.consumeEach { frame -> - if (frame is Frame.Text) { - val message = NSURLSessionWebSocketMessage(frame.readText()) - webSocket.sendMessage(message) { nsError -> - if (nsError == null) return@sendMessage - - nsError.closeSocketOrThrow { - throw SendMessageException(nsError.description ?: nsError.toString()) - } - } - } - } - } - - listenMessages() - } - - fun start() { - webSocket.resume() - } - - private fun listenMessages() { - webSocket.receiveMessageWithCompletionHandler { message, nsError -> - when { - nsError != null -> { - nsError.closeSocketOrThrow { - throw ReceiveMessageException(nsError.description ?: nsError.toString()) - } - } - message != null -> { - message.string?.let { _incoming.trySend(Frame.Text(it)) } - } - } - listenMessages() - } - } - - private fun NSError.closeSocketOrThrow(throwBlock: () -> Unit) { - if (domain !in listOf("kNWErrorDomainPOSIX", NSPOSIXErrorDomain)) return throwBlock() - if (code != 57L) return throwBlock() - - val closeReason = CloseReason( - code = CloseReason.Codes.NORMAL, - message = description ?: toString() - ) - _closeReason.complete(closeReason) - webSocket.cancel() - } - - override fun terminate() { - coroutineContext.cancel() - } -} diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt index f09217c..22e846d 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt @@ -10,7 +10,7 @@ import platform.Foundation.currentLocale import platform.Foundation.languageCode actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { - override fun getLanguageCode(): String? { + override fun getLanguageCode(): String { return NSLocale.currentLocale.languageCode } } diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/ReceiveMessageException.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/ReceiveMessageException.kt deleted file mode 100644 index c49b261..0000000 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/ReceiveMessageException.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.icerock.moko.network - -import io.ktor.utils.io.errors.IOException - -class ReceiveMessageException(message: String) : IOException(message) diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/SendMessageException.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/SendMessageException.kt deleted file mode 100644 index 993a4db..0000000 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/SendMessageException.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.icerock.moko.network - -import io.ktor.utils.io.errors.IOException - -class SendMessageException(message: String) : IOException(message) diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/WSIosHttpClientEngine.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/WSIosHttpClientEngine.kt deleted file mode 100644 index ac5e124..0000000 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/WSIosHttpClientEngine.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -package dev.icerock.moko.network - -import io.ktor.client.engine.HttpClientEngine -import io.ktor.client.engine.HttpClientEngineCapability -import io.ktor.client.engine.HttpClientEngineConfig -import io.ktor.client.engine.callContext -import io.ktor.client.features.websocket.WebSocketCapability -import io.ktor.client.request.HttpRequestData -import io.ktor.client.request.HttpResponseData -import io.ktor.client.request.isUpgradeRequest -import io.ktor.http.Headers -import io.ktor.http.HttpProtocolVersion -import io.ktor.http.HttpStatusCode -import io.ktor.util.InternalAPI -import io.ktor.util.date.GMTDate -import kotlinx.coroutines.CoroutineDispatcher -import platform.Foundation.NSURL -import kotlin.coroutines.CoroutineContext - -class WSIosHttpClientEngine( - private val wrappedEngine: HttpClientEngine -) : HttpClientEngine { - - override val supportedCapabilities: Set> - get() = wrappedEngine.supportedCapabilities + setOf(WebSocketCapability) - - override val config: HttpClientEngineConfig - get() = wrappedEngine.config - - override val dispatcher: CoroutineDispatcher - get() = wrappedEngine.dispatcher - - override val coroutineContext: CoroutineContext - get() = wrappedEngine.coroutineContext - - @InternalAPI - override suspend fun execute(data: HttpRequestData): HttpResponseData { - - val callContext = callContext() - return if (data.isUpgradeRequest()) { - executeWebSocketRequest(data, callContext) - } else { - wrappedEngine.execute(data) - } - } - - private suspend fun executeWebSocketRequest( - data: HttpRequestData, - callContext: CoroutineContext - ): HttpResponseData { - val requestTime = GMTDate() - val url: String = data.url.toString() - val socketEndpoint = NSURL.URLWithString(url)!! - - val session = IosWebSocket(socketEndpoint, callContext).apply { start() } - - val originResponse = session.originResponse.await() - - return HttpResponseData( - statusCode = HttpStatusCode.OK, - requestTime = requestTime, - headers = Headers.Empty, - version = HttpProtocolVersion.HTTP_1_0, // read from originResponse - body = session, - callContext = callContext - ) - } - - override fun close() { - wrappedEngine.close() - } -} diff --git a/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt b/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt index a5d5855..5e2670b 100644 --- a/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt +++ b/network/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt @@ -14,5 +14,5 @@ actual fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit): Htt config.iosTimeoutIntervalForRequest?.let { setTimeoutIntervalForRequest(it) } config.iosTimeoutIntervalForResource?.let { setTimeoutIntervalForResource(it) } } - }.let { WSIosHttpClientEngine(it) } + } } diff --git a/sample/mpp-library/build.gradle.kts b/sample/mpp-library/build.gradle.kts index 89cf8e7..b037e44 100644 --- a/sample/mpp-library/build.gradle.kts +++ b/sample/mpp-library/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { commonMainImplementation(libs.ktorClient) commonMainImplementation(libs.ktorClientLogging) commonMainImplementation(libs.kotlinSerialization) + commonMainImplementation(libs.ktorClientWebSocket) commonMainImplementation(libs.kbignum) commonMainApi(libs.mokoMvvmCore) diff --git a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt index b9ad62e..cb0b7e2 100644 --- a/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt +++ b/sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/TestViewModel.kt @@ -4,7 +4,6 @@ package com.icerockdev.library -import dev.icerock.moko.errors.MR import dev.icerock.moko.errors.handler.ExceptionHandler import dev.icerock.moko.errors.mappers.ExceptionMappersStorage import dev.icerock.moko.errors.presenters.AlertErrorPresenter @@ -13,14 +12,19 @@ import dev.icerock.moko.mvvm.livedata.MutableLiveData import dev.icerock.moko.mvvm.livedata.readOnly import dev.icerock.moko.mvvm.viewmodel.ViewModel import dev.icerock.moko.network.LanguageProvider +import dev.icerock.moko.network.createHttpClientEngine +import dev.icerock.moko.network.generated.apis.PetApi import dev.icerock.moko.network.plugins.LanguagePlugin import dev.icerock.moko.network.plugins.TokenPlugin -import dev.icerock.moko.network.generated.apis.PetApi -import dev.icerock.moko.resources.desc.desc import io.ktor.client.HttpClient import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging +import io.ktor.client.plugins.websocket.WebSockets +import io.ktor.client.plugins.websocket.webSocket +import io.ktor.websocket.Frame +import io.ktor.websocket.readText +import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import news.apis.NewsApi @@ -28,11 +32,7 @@ import news.apis.NewsApi class TestViewModel : ViewModel() { val exceptionHandler = ExceptionHandler( - errorPresenter = AlertErrorPresenter( - // temporary fix https://youtrack.jetbrains.com/issue/KT-41823 - alertTitle = MR.strings.moko_errors_presenters_alertDialogTitle.desc(), - positiveButtonText = MR.strings.moko_errors_presenters_alertPositiveButton.desc() - ), + errorPresenter = AlertErrorPresenter(), exceptionMapper = ExceptionMappersStorage.throwableMapper(), onCatch = { it.printStackTrace() } ) @@ -54,7 +54,7 @@ class TestViewModel : ViewModel() { install(TokenPlugin) { tokenHeaderName = "Authorization" tokenProvider = object : TokenPlugin.TokenProvider { - override fun getToken(): String? = "ed155d0a445e4b4fbd878fe1f3bc1b7f" + override fun getToken(): String = "ed155d0a445e4b4fbd878fe1f3bc1b7f" } } } @@ -125,7 +125,7 @@ class TestViewModel : ViewModel() { } } } - send("Hello world!") + send(Frame.Text("Hello world!")) _websocketInfo.value += "send first message\n" incomingJob.join() From 5793821b1b19d8ccb58ffe7bb3de9c1943f1837b Mon Sep 17 00:00:00 2001 From: Aleksey Mikhailov Date: Sun, 31 Jul 2022 20:20:17 +0700 Subject: [PATCH 8/9] #178 move test json to resources (reduce duplication) --- sample/mpp-library/build.gradle.kts | 29 +++++++++++++++++++ .../androidTest/kotlin/readResourceText.kt | 18 ++++++++++++ .../src/commonTest/kotlin/PetApiTest.kt | 3 +- .../kotlin/tests/utils/readResourceText.kt | 7 +++++ .../PetstoreSearchResponse.json} | 8 +---- .../src/iosTest/kotlin/readResourceText.kt | 19 ++++++++++++ 6 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 sample/mpp-library/src/androidTest/kotlin/readResourceText.kt create mode 100644 sample/mpp-library/src/commonTest/kotlin/tests/utils/readResourceText.kt rename sample/mpp-library/src/commonTest/{kotlin/PetstoreSearchResponse.kt => resources/PetstoreSearchResponse.json} (99%) create mode 100644 sample/mpp-library/src/iosTest/kotlin/readResourceText.kt diff --git a/sample/mpp-library/build.gradle.kts b/sample/mpp-library/build.gradle.kts index b037e44..0c7de12 100644 --- a/sample/mpp-library/build.gradle.kts +++ b/sample/mpp-library/build.gradle.kts @@ -99,3 +99,32 @@ mokoNetwork { inputSpec = file("src/requestHeaders.yaml") } } + +val copyIosX64TestResources = tasks.register("copyIosX64TestResources") { + from("src/commonTest/resources") + into("build/bin/iosX64/debugTest/resources") +} + +tasks.matching { it.name == "iosX64Test" }.configureEach { + dependsOn(copyIosX64TestResources) +} + +val copyIosArm64TestResources = tasks.register("copyIosArm64TestResources") { + from("src/commonTest/resources") + into("build/bin/iosSimulatorArm64/debugTest/resources") +} + +tasks.matching { it.name == "iosSimulatorArm64Test" }.configureEach { + dependsOn(copyIosArm64TestResources) +} + +tasks.withType() + .matching { it.name.contains("UnitTest") } + .configureEach { + doLast { + val testResourcesDir = File(projectDir, "src/commonTest/resources") + if (testResourcesDir.exists().not()) return@doLast + testResourcesDir.copyRecursively(destinationDir, overwrite = true) + } + } + diff --git a/sample/mpp-library/src/androidTest/kotlin/readResourceText.kt b/sample/mpp-library/src/androidTest/kotlin/readResourceText.kt new file mode 100644 index 0000000..a2b2c10 --- /dev/null +++ b/sample/mpp-library/src/androidTest/kotlin/readResourceText.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. + */ + +package tests.utils + +import java.io.InputStream +import kotlin.test.assertNotNull + +actual fun Any.readResourceText(path: String): String { + val classLoader: ClassLoader? = this.javaClass.classLoader + assertNotNull(classLoader, "can't get classLoader of $this") + val resource: InputStream? = classLoader.getResourceAsStream(path) + assertNotNull(resource, "can't find resource with path [$path]") + return resource + .bufferedReader() + .readText() +} diff --git a/sample/mpp-library/src/commonTest/kotlin/PetApiTest.kt b/sample/mpp-library/src/commonTest/kotlin/PetApiTest.kt index e5267dd..3051118 100644 --- a/sample/mpp-library/src/commonTest/kotlin/PetApiTest.kt +++ b/sample/mpp-library/src/commonTest/kotlin/PetApiTest.kt @@ -8,6 +8,7 @@ import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respondOk import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json +import tests.utils.readResourceText import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -24,7 +25,7 @@ class PetApiTest { httpClient = HttpClient(MockEngine) { engine { addHandler { request -> - respondOk(content = petstoreSearchResponse) + respondOk(content = readResourceText("PetstoreSearchResponse.json")) } } } diff --git a/sample/mpp-library/src/commonTest/kotlin/tests/utils/readResourceText.kt b/sample/mpp-library/src/commonTest/kotlin/tests/utils/readResourceText.kt new file mode 100644 index 0000000..9228e9e --- /dev/null +++ b/sample/mpp-library/src/commonTest/kotlin/tests/utils/readResourceText.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. + */ + +package tests.utils + +expect fun Any.readResourceText(path: String): String diff --git a/sample/mpp-library/src/commonTest/kotlin/PetstoreSearchResponse.kt b/sample/mpp-library/src/commonTest/resources/PetstoreSearchResponse.json similarity index 99% rename from sample/mpp-library/src/commonTest/kotlin/PetstoreSearchResponse.kt rename to sample/mpp-library/src/commonTest/resources/PetstoreSearchResponse.json index 994d5c7..ba4cbef 100644 --- a/sample/mpp-library/src/commonTest/kotlin/PetstoreSearchResponse.kt +++ b/sample/mpp-library/src/commonTest/resources/PetstoreSearchResponse.json @@ -1,8 +1,3 @@ -/* - * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. - */ - -val petstoreSearchResponse = """ [ { "id": 9222968140497256000, @@ -3891,5 +3886,4 @@ val petstoreSearchResponse = """ ], "status": "available" } -] -""".trimIndent() \ No newline at end of file +] \ No newline at end of file diff --git a/sample/mpp-library/src/iosTest/kotlin/readResourceText.kt b/sample/mpp-library/src/iosTest/kotlin/readResourceText.kt new file mode 100644 index 0000000..1ee506e --- /dev/null +++ b/sample/mpp-library/src/iosTest/kotlin/readResourceText.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. + */ + +package tests.utils + +import platform.Foundation.NSBundle +import platform.Foundation.NSString +import platform.Foundation.stringWithContentsOfFile +import kotlin.test.assertNotNull + +actual fun Any.readResourceText(path: String): String { + val pathWithoutExtension: String = path.substringBeforeLast(".") + val extension = path.substringAfterLast(".") + val filePath: String? = NSBundle.mainBundle + .pathForResource("resources/$pathWithoutExtension", extension) + assertNotNull(filePath, "can't find file on path [$filePath]") + return NSString.stringWithContentsOfFile(filePath) as String +} From 45acd5bf0363369dd4e83bcd025ecb5a520bb10c Mon Sep 17 00:00:00 2001 From: Aleksey Mikhailov Date: Sat, 27 Aug 2022 12:20:32 +0700 Subject: [PATCH 9/9] update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 11f7d41..c7070f1 100755 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ buildscript { } dependencies { - classpath "dev.icerock.moko:network-generator:0.18.0" + classpath "dev.icerock.moko:network-generator:0.19.0" } } @@ -53,9 +53,9 @@ project build.gradle apply plugin: "dev.icerock.mobile.multiplatform-network-generator" dependencies { - commonMainApi("dev.icerock.moko:network:0.18.0") - commonMainApi("dev.icerock.moko:network-bignum:0.18.0") // kbignum serializer - commonMainApi("dev.icerock.moko:network-errors:0.18.0") // moko-errors integration + commonMainApi("dev.icerock.moko:network:0.19.0") + commonMainApi("dev.icerock.moko:network-bignum:0.19.0") // kbignum serializer + commonMainApi("dev.icerock.moko:network-errors:0.19.0") // moko-errors integration } ```