├── sample ├── gradlew ├── ios-app │ ├── src │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── TestViewController.swift │ │ ├── Info.plist │ │ └── Resources │ │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ ├── TestProj.xcodeproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ ├── TestProj.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Podfile.lock │ └── Podfile ├── mpp-library │ ├── src │ │ ├── androidMain │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── icerockdev │ │ │ │ └── library │ │ │ │ └── emulatorLocalhost.kt │ │ ├── commonTest │ │ │ └── kotlin │ │ │ │ ├── tests │ │ │ │ └── utils │ │ │ │ │ └── readResourceText.kt │ │ │ │ ├── HeadersTest.kt │ │ │ │ ├── createHttpClient.kt │ │ │ │ ├── AllOfTest.kt │ │ │ │ ├── PetApiTest.kt │ │ │ │ ├── MapResponseTest.kt │ │ │ │ ├── AnyTypeTest.kt │ │ │ │ ├── FormDataTest.kt │ │ │ │ ├── AnyOfTest.kt │ │ │ │ └── EnumFallbackNullTest.kt │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── icerockdev │ │ │ │ └── library │ │ │ │ ├── emulatorLocalhost.kt │ │ │ │ └── ExceptionStorage.kt │ │ ├── iosMain │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── icerockdev │ │ │ │ └── library │ │ │ │ └── emulatorLocalhost.kt │ │ ├── AnyType.yaml │ │ ├── androidUnitTest │ │ │ └── kotlin │ │ │ │ └── readResourceText.kt │ │ ├── mapResponse.yaml │ │ ├── oneOf.yaml │ │ ├── iosTest │ │ │ └── kotlin │ │ │ │ └── readResourceText.kt │ │ ├── requestHeaders.yaml │ │ ├── anyOf.yaml │ │ ├── allOf.yaml │ │ └── enumFallbackNull.yaml │ ├── MultiPlatformLibrary.podspec │ └── build.gradle.kts ├── android-app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── logo.png │ │ │ │ └── loading.xml │ │ │ ├── drawable-xxhdpi │ │ │ │ └── spinner_image.png │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── icerockdev │ │ │ │ └── app │ │ │ │ ├── App.kt │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle.kts ├── form-data-binary-server │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── application.conf │ │ │ └── logback.xml │ │ │ └── kotlin │ │ │ └── com │ │ │ └── icerockdev │ │ │ └── server │ │ │ └── Application.kt │ └── build.gradle.kts └── websocket-echo-server │ ├── src │ └── main │ │ ├── resources │ │ ├── application.conf │ │ └── logback.xml │ │ └── kotlin │ │ └── com │ │ └── icerockdev │ │ └── server │ │ └── Application.kt │ └── build.gradle.kts ├── img └── logo.png ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── network-generator ├── src │ └── main │ │ ├── resources │ │ ├── META-INF │ │ │ ├── services │ │ │ │ └── org.openapitools.codegen.CodegenConfig │ │ │ └── gradle-plugins │ │ │ │ └── dev.icerock.mobile.multiplatform-network-generator-deprecated.properties │ │ └── kotlin-ktor-client │ │ │ ├── classes_modifiers.mustache │ │ │ ├── model_doc.mustache │ │ │ ├── enum_doc.mustache │ │ │ ├── licenseInfo.mustache │ │ │ ├── data_class_non_req_null_var.mustache │ │ │ ├── property_serializer.mustache │ │ │ ├── enum_class.mustache │ │ │ ├── data_class_req_var.mustache │ │ │ ├── model.mustache │ │ │ ├── data_class_opt_var.mustache │ │ │ ├── data_class_non_req_non_null_var.mustache │ │ │ ├── class_doc.mustache │ │ │ ├── data_class.mustache │ │ │ ├── data_class_allof.mustache │ │ │ ├── data_class_anyof.mustache │ │ │ └── api_doc.mustache │ │ └── kotlin │ │ └── dev │ │ └── icerock │ │ └── moko │ │ └── network │ │ ├── OpenApiSchemaProcessor.kt │ │ ├── SpecConfig.kt │ │ ├── SpecInfo.kt │ │ ├── OneOfOperatorProcessor.kt │ │ ├── SchemaEnumNullProcessor.kt │ │ ├── tasks │ │ └── GenerateTask.kt │ │ ├── MultiPlatformNetworkGeneratorDeprecatedPlugin.kt │ │ ├── SchemaContext.kt │ │ ├── MultiPlatformNetworkGeneratorPlugin.kt │ │ └── ComposedSchemaProcessor.kt ├── gradle.properties ├── settings.gradle.kts └── build.gradle.kts ├── .gitignore ├── network-errors ├── src │ ├── iosMain │ │ └── kotlin │ │ │ └── Dummy.kt │ └── commonMain │ │ ├── resources │ │ └── MR │ │ │ ├── base │ │ │ └── strings.xml │ │ │ └── ru │ │ │ └── strings.xml │ │ └── kotlin │ │ └── dev │ │ └── icerock │ │ └── moko │ │ └── network │ │ └── errors │ │ ├── NetworkErrorsTexts.kt │ │ └── NetworkExceptionMappers.kt └── build.gradle.kts ├── .idea └── copyright │ ├── profiles_settings.xml │ └── IceRock.xml ├── network ├── src │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ ├── NetworkConnectionError.kt │ │ │ ├── ParserUtils.kt │ │ │ ├── LanguageProvider.kt │ │ │ ├── nullable │ │ │ ├── Nullable.kt │ │ │ └── NullableSerializer.kt │ │ │ ├── NetworkResponse.kt │ │ │ ├── exceptions │ │ │ ├── DataNotFitAnyOfSchema.kt │ │ │ ├── DataNotFitOneOfSchema.kt │ │ │ ├── ValidationException.kt │ │ │ ├── ErrorException.kt │ │ │ └── ResponseException.kt │ │ │ ├── safeable │ │ │ ├── Safeable.kt │ │ │ └── SafeableSerializer.kt │ │ │ ├── GMTDateExt.kt │ │ │ ├── exceptionfactory │ │ │ ├── ExceptionFactory.kt │ │ │ ├── HttpExceptionFactory.kt │ │ │ └── parser │ │ │ │ ├── ErrorExceptionParser.kt │ │ │ │ └── ValidationExceptionParser.kt │ │ │ ├── isSSLException.kt │ │ │ ├── plugins │ │ │ ├── DynamicUserAgent.kt │ │ │ ├── TokenPlugin.kt │ │ │ ├── LanguagePlugin.kt │ │ │ ├── ExceptionPlugin.kt │ │ │ └── RefreshTokenPlugin.kt │ │ │ ├── HttpExt.kt │ │ │ ├── schemas │ │ │ └── ComposedSchemaSerializer.kt │ │ │ └── multipart │ │ │ └── MultiPartContent.kt │ ├── jvmMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ └── LanguageProvider.kt │ ├── iosMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ ├── LanguageProvider.kt │ │ │ ├── ParserUtils.kt │ │ │ ├── ThrowableToNSErrorMapper.kt │ │ │ ├── NetworkConnectionError.kt │ │ │ ├── GMTDateExt.kt │ │ │ └── isSSLException.kt │ ├── commonJvmAndroid │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ ├── ParserUtils.kt │ │ │ ├── GMTDateExt.kt │ │ │ ├── isNetworkConnectionError.kt │ │ │ └── isSSLException.kt │ ├── androidMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ ├── LanguageProvider.kt │ │ │ └── ParcelExt.kt │ └── commonTest │ │ └── kotlin │ │ ├── TokenFeatureTest.kt │ │ ├── NullableTest.kt │ │ ├── LanguageFeatureTest.kt │ │ ├── AllOfTest.kt │ │ ├── SafeableTest.kt │ │ └── OneOfTest.kt └── build.gradle.kts ├── network-bignum ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── icerock │ └── moko │ └── network │ └── bignum │ └── BigNumSerializer.kt ├── settings.gradle.kts ├── network-engine ├── src │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ └── HttpClientEngineConfig.kt │ ├── commonJvmAndroid │ │ └── kotlin │ │ │ └── dev │ │ │ └── icerock │ │ │ └── moko │ │ │ └── network │ │ │ └── createHttpClientEngine.kt │ └── iosMain │ │ └── kotlin │ │ └── dev │ │ └── icerock │ │ └── moko │ │ └── network │ │ └── createHttpClientEngine.kt └── build.gradle.kts ├── gradle.properties ├── .github └── workflows │ ├── compilation-check.yml │ └── publish.yml ├── CONTRIBUTING.md └── gradlew.bat /sample/gradlew: -------------------------------------------------------------------------------- 1 | ../gradlew -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-network/HEAD/img/logo.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-network/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /network-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig: -------------------------------------------------------------------------------- 1 | dev.icerock.moko.network.KtorCodegen -------------------------------------------------------------------------------- /sample/ios-app/src/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /sample/mpp-library/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-network/HEAD/sample/android-app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/classes_modifiers.mustache: -------------------------------------------------------------------------------- 1 | {{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}public {{/nonPublicApi}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .project 4 | .classpath 5 | .vscode 6 | .idea 7 | build 8 | *.iml 9 | Pods 10 | xcuserdata 11 | local.properties 12 | local.gradle -------------------------------------------------------------------------------- /sample/android-app/src/main/res/drawable-xxhdpi/spinner_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icerockdev/moko-network/HEAD/sample/android-app/src/main/res/drawable-xxhdpi/spinner_image.png -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/model_doc.mustache: -------------------------------------------------------------------------------- 1 | {{#models}}{{#model}} 2 | {{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}} 3 | {{/model}}{{/models}} 4 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/META-INF/gradle-plugins/dev.icerock.mobile.multiplatform-network-generator-deprecated.properties: -------------------------------------------------------------------------------- 1 | implementation-class=dev.icerock.moko.network.MultiPlatformNetworkGeneratorDeprecatedPlugin -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/enum_doc.mustache: -------------------------------------------------------------------------------- 1 | # {{classname}} 2 | 3 | ## Enum 4 | 5 | {{#allowableValues}}{{#enumVars}} 6 | * `{{name}}` (value: `{{{value}}}`) 7 | {{/enumVars}}{{/allowableValues}} 8 | -------------------------------------------------------------------------------- /network-errors/src/iosMain/kotlin/Dummy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | // required for produce `metadata/iosMain` 6 | internal val sDummyVar: Int? = null 7 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /sample/ios-app/TestProj.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/form-data-binary-server/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | port = ${?PORT} 5 | } 6 | application { 7 | modules = [ com.icerockdev.server.ApplicationKt.module ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/websocket-echo-server/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | ktor { 2 | deployment { 3 | port = 8080 4 | port = ${?PORT} 5 | } 6 | application { 7 | modules = [ com.icerockdev.server.ApplicationKt.module ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/tests/utils/readResourceText.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package tests.utils 6 | 7 | expect fun Any.readResourceText(path: String): String 8 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | expect fun Throwable.isNetworkConnectionError(): Boolean 8 | -------------------------------------------------------------------------------- /.idea/copyright/IceRock.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/emulatorLocalhost.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package com.icerockdev.library 8 | 9 | expect val emulatorLocalhost: String 10 | -------------------------------------------------------------------------------- /sample/ios-app/TestProj.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/mpp-library/src/iosMain/kotlin/com/icerockdev/library/emulatorLocalhost.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package com.icerockdev.library 8 | 9 | actual val emulatorLocalhost = "0.0.0.0" 10 | -------------------------------------------------------------------------------- /sample/ios-app/TestProj.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/ParserUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | 9 | expect fun String.formatToDate(parseFormat: String): GMTDate 10 | -------------------------------------------------------------------------------- /sample/mpp-library/src/androidMain/kotlin/com/icerockdev/library/emulatorLocalhost.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package com.icerockdev.library 8 | 9 | @Suppress("MayBeConst") 10 | actual val emulatorLocalhost = "10.0.2.2" 11 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import dev.icerock.moko.network.plugins.LanguagePlugin 8 | 9 | @Suppress("EmptyDefaultConstructor") 10 | expect class LanguageProvider() : LanguagePlugin.LanguageCodeProvider 11 | -------------------------------------------------------------------------------- /sample/ios-app/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MultiPlatformLibrary (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - MultiPlatformLibrary (from `../mpp-library`) 6 | 7 | EXTERNAL SOURCES: 8 | MultiPlatformLibrary: 9 | :path: "../mpp-library" 10 | 11 | SPEC CHECKSUMS: 12 | MultiPlatformLibrary: 2a9f43df7bd018c32611a2087c1e2ef74847394c 13 | 14 | PODFILE CHECKSUM: 68b1a7b3453f9dbea0e91a6439f872724d5c91ce 15 | 16 | COCOAPODS: 1.11.3 17 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/licenseInfo.mustache: -------------------------------------------------------------------------------- 1 | /** 2 | * {{{appName}}} 3 | * {{{appDescription}}} 4 | * 5 | * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} 6 | * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} 7 | * 8 | * NOTE: This class is auto generated by the swagger code generator program. 9 | * https://github.com/swagger-api/swagger-codegen.git 10 | * Do not edit the class manually. 11 | */ -------------------------------------------------------------------------------- /sample/android-app/src/main/java/com/icerockdev/app/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.app 6 | 7 | import android.app.Application 8 | import com.icerockdev.library.initExceptionStorage 9 | 10 | class App : Application() { 11 | override fun onCreate() { 12 | super.onCreate() 13 | initExceptionStorage() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/nullable/Nullable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.nullable 6 | 7 | import kotlinx.serialization.Serializable 8 | 9 | @Serializable(with = NullableSerializer::class) 10 | data class Nullable(val value: T?) 11 | 12 | fun T?.asNullable(): Nullable = Nullable(this) 13 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonMain/kotlin/com/icerockdev/library/ExceptionStorage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.library 6 | 7 | import dev.icerock.moko.errors.mappers.ExceptionMappersStorage 8 | import dev.icerock.moko.network.errors.registerAllNetworkMappers 9 | 10 | fun initExceptionStorage() { 11 | ExceptionMappersStorage.registerAllNetworkMappers() 12 | } 13 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/OpenApiSchemaProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.swagger.v3.oas.models.OpenAPI 8 | import io.swagger.v3.oas.models.media.Schema 9 | 10 | fun interface OpenApiSchemaProcessor { 11 | fun process(openApi: OpenAPI, schema: Schema<*>, context: SchemaContext): Schema<*> 12 | } 13 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/NetworkResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.client.statement.HttpResponse 8 | 9 | data class NetworkResponse( 10 | val httpResponse: HttpResponse, 11 | private val bodyReader: suspend (HttpResponse) -> T 12 | ) { 13 | suspend fun body(): T = bodyReader(httpResponse) 14 | } 15 | -------------------------------------------------------------------------------- /network-generator/gradle.properties: -------------------------------------------------------------------------------- 1 | moko.publish.name=MOKO network 2 | moko.publish.description=Network components with codegeneration of rest api for mobile (android & ios) Kotlin Multiplatform development 3 | moko.publish.repo.org=icerockdev 4 | moko.publish.repo.name=moko-network 5 | moko.publish.license=Apache-2.0 6 | moko.publish.developers=alex009|Aleksey Mikhailov|Aleksey.Mikhailov@icerockdev.com,Tetraquark|Vladislav Areshkin|vareshkin@icerockdev.com,Dorofeev|Andrey Dorofeev|adorofeev@icerockdev.com 7 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptions/DataNotFitAnyOfSchema.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptions 6 | 7 | import kotlinx.serialization.SerializationException 8 | import kotlinx.serialization.json.JsonElement 9 | 10 | class DataNotFitAnyOfSchema( 11 | val data: JsonElement, 12 | val causes: List 13 | ) : SerializationException() 14 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptions/DataNotFitOneOfSchema.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptions 6 | 7 | import kotlinx.serialization.SerializationException 8 | import kotlinx.serialization.json.JsonElement 9 | 10 | class DataNotFitOneOfSchema( 11 | val data: JsonElement, 12 | val results: List> 13 | ) : SerializationException() 14 | -------------------------------------------------------------------------------- /sample/websocket-echo-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_non_req_null_var.mustache: -------------------------------------------------------------------------------- 1 | {{#description}} 2 | /* {{{description}}} */ 3 | {{/description}} 4 | {{#isDecimal}}@Serializable(with = BigNumSerializer::class) {{/isDecimal}} 5 | @SerialName("{{{baseName}}}") 6 | val {{{name}}}: dev.icerock.moko.network.nullable.Nullable<{{#isEnum}}{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{nameInCamelCase}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}>? = null -------------------------------------------------------------------------------- /network/src/jvmMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import dev.icerock.moko.network.plugins.LanguagePlugin 8 | import java.util.Locale 9 | 10 | actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { 11 | override fun getLanguageCode(): String? { 12 | return Locale.getDefault().displayLanguage 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/form-data-binary-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/mpp-library/src/AnyType.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /dynamic: 7 | get: 8 | responses: 9 | '200': 10 | description: Updated 11 | content: 12 | application/json: 13 | schema: 14 | $ref: '#/components/schemas/Resp' 15 | components: 16 | schemas: 17 | Resp: 18 | type: object 19 | properties: 20 | anyProp: {} 21 | anyList: 22 | type: array 23 | items: {} 24 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/property_serializer.mustache: -------------------------------------------------------------------------------- 1 | {{#isMap}} 2 | {{#additionalProperties}}{{>property_serializer}}{{/additionalProperties}}.let { 3 | MapSerializer(keySerializer = String.serializer(), valueSerializer = it) 4 | } 5 | {{/isMap}} 6 | {{#isArray}} 7 | {{#items}}{{>property_serializer}}{{/items}}.let { 8 | {{#uniqueItems}}SetSerializer(it){{/uniqueItems}} 9 | {{^uniqueItems}}ListSerializer(it){{/uniqueItems}} 10 | } 11 | {{/isArray}} 12 | {{^containerType}} 13 | {{dataType}}.serializer() 14 | {{/containerType}} -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/safeable/Safeable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.safeable 6 | 7 | import kotlinx.serialization.Serializable 8 | 9 | @Serializable(with = SafeableSerializer::class) 10 | data class Safeable(val value: T?) 11 | 12 | fun T?.asSafeable(): Safeable = Safeable(this) 13 | 14 | fun Collection>.extractSafeables(): Collection = map { it.value } 15 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/GMTDateExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | 9 | /** 10 | * Parses string from the beginning of the given string to produce a date by the [format]. 11 | * 12 | * @throws IllegalArgumentException if the date [format] is incorrect. 13 | */ 14 | expect fun String.toDate(format: String): GMTDate 15 | 16 | expect fun GMTDate.toString(format: String): String 17 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/enum_class.mustache: -------------------------------------------------------------------------------- 1 | import kotlinx.serialization.SerialName 2 | 3 | /** 4 | * {{{description}}} 5 | * Values: {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} 6 | */ 7 | @Serializable 8 | {{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{classname}}() { 9 | {{#allowableValues}}{{#enumVars}} 10 | @SerialName({{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) 11 | {{name}}{{^-last}},{{/-last}}{{#-last}};{{/-last}} 12 | {{/enumVars}}{{/allowableValues}} 13 | } 14 | -------------------------------------------------------------------------------- /sample/form-data-binary-server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotlin") 3 | id("application") 4 | } 5 | 6 | group = "com.example" 7 | version = "0.0.1" 8 | 9 | application { 10 | mainClass.set("io.ktor.server.netty.EngineMain") 11 | } 12 | 13 | java { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | dependencies { 19 | implementation("io.ktor:ktor-server-netty:2.2.1") 20 | implementation("io.ktor:ktor-server-core:2.2.1") 21 | implementation("ch.qos.logback:logback-classic:1.2.11") 22 | } 23 | -------------------------------------------------------------------------------- /sample/ios-app/src/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import MultiPlatformLibrary 6 | import UIKit 7 | 8 | @UIApplicationMain 9 | class AppDelegate: NSObject, UIApplicationDelegate { 10 | 11 | var window: UIWindow? 12 | 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 14 | ExceptionStorageKt.doInitExceptionStorage() 15 | 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/drawable/loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import dev.icerock.moko.network.plugins.LanguagePlugin 8 | import platform.Foundation.NSLocale 9 | import platform.Foundation.currentLocale 10 | import platform.Foundation.languageCode 11 | 12 | actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { 13 | override fun getLanguageCode(): String { 14 | return NSLocale.currentLocale.languageCode 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /network/src/commonJvmAndroid/kotlin/dev/icerock/moko/network/ParserUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | import java.text.SimpleDateFormat 9 | import java.util.Locale 10 | import java.util.TimeZone 11 | 12 | actual fun String.formatToDate(parseFormat: String): GMTDate { 13 | val date = SimpleDateFormat(parseFormat, Locale.ROOT).apply { 14 | timeZone = TimeZone.getTimeZone("GMT") 15 | }.parse(this) 16 | return GMTDate(date.time) 17 | } 18 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptionfactory/ExceptionFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptionfactory 6 | 7 | import dev.icerock.moko.network.exceptions.ResponseException 8 | import io.ktor.client.request.HttpRequest 9 | import io.ktor.client.statement.HttpResponse 10 | 11 | interface ExceptionFactory { 12 | fun createException( 13 | request: HttpRequest, 14 | response: HttpResponse, 15 | responseBody: String? 16 | ): ResponseException 17 | } 18 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptions/ValidationException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptions 6 | 7 | import io.ktor.client.request.HttpRequest 8 | import io.ktor.client.statement.HttpResponse 9 | 10 | class ValidationException( 11 | request: HttpRequest, 12 | response: HttpResponse, 13 | message: String, 14 | val errors: List 15 | ) : ResponseException(request, response, message) { 16 | data class Error(val field: String, val message: String) 17 | } 18 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/HeadersTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import io.ktor.client.HttpClient 6 | import io.ktor.client.engine.mock.respondOk 7 | import kotlin.test.Test 8 | import kotlin.test.assertTrue 9 | 10 | class HeadersTest { 11 | private lateinit var httpClient: HttpClient 12 | 13 | @Test 14 | fun `auth test`() { 15 | httpClient = createMockClient { request -> 16 | assertTrue { request.headers.contains("Authorization") } 17 | respondOk() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /network/src/androidMain/kotlin/dev/icerock/moko/network/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import android.content.res.Resources 8 | import androidx.core.os.ConfigurationCompat 9 | import dev.icerock.moko.network.plugins.LanguagePlugin 10 | 11 | actual class LanguageProvider : LanguagePlugin.LanguageCodeProvider { 12 | override fun getLanguageCode(): String? { 13 | return ConfigurationCompat.getLocales(Resources.getSystem().configuration).get(0)?.language 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/websocket-echo-server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotlin") 3 | id("application") 4 | } 5 | 6 | group = "com.example" 7 | version = "0.0.1" 8 | 9 | application { 10 | mainClass.set("io.ktor.server.netty.EngineMain") 11 | } 12 | 13 | java { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | dependencies { 19 | implementation("io.ktor:ktor-server-netty:1.5.1") 20 | implementation("io.ktor:ktor-server-core:1.5.1") 21 | implementation("io.ktor:ktor-websockets:1.5.1") 22 | implementation("ch.qos.logback:logback-classic:1.2.9") 23 | } 24 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptions/ErrorException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptions 6 | 7 | import io.ktor.client.request.HttpRequest 8 | import io.ktor.client.statement.HttpResponse 9 | 10 | class ErrorException( 11 | request: HttpRequest, 12 | response: HttpResponse, 13 | val code: Int, 14 | val description: String? 15 | ) : ResponseException(request, response, description.orEmpty()) { 16 | override val message: String? 17 | get() = description ?: super.message 18 | } 19 | -------------------------------------------------------------------------------- /network-bignum/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("dev.icerock.moko.gradle.detekt") 8 | id("dev.icerock.moko.gradle.publication") 9 | id("dev.icerock.moko.gradle.stub.javadoc") 10 | } 11 | 12 | android { 13 | namespace = "dev.icerock.moko.network.bignum" 14 | } 15 | 16 | kotlin { 17 | jvm() 18 | } 19 | 20 | dependencies { 21 | commonMainImplementation(libs.kotlinSerialization) 22 | commonMainApi(libs.kbignum) 23 | 24 | commonMainImplementation(projects.network) 25 | } 26 | -------------------------------------------------------------------------------- /network-generator/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | enableFeaturePreview("VERSION_CATALOGS") 5 | 6 | pluginManagement { 7 | repositories { 8 | mavenCentral() 9 | google() 10 | 11 | gradlePluginPortal() 12 | } 13 | } 14 | 15 | dependencyResolutionManagement { 16 | repositories { 17 | mavenCentral() 18 | google() 19 | } 20 | 21 | versionCatalogs { 22 | create("libs") { 23 | from(files("../gradle/libs.versions.toml")) 24 | } 25 | } 26 | } 27 | 28 | rootProject.name = "network-generator" 29 | -------------------------------------------------------------------------------- /sample/ios-app/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://cdn.cocoapods.org/' 2 | 3 | # ignore all warnings from all pods 4 | inhibit_all_warnings! 5 | 6 | use_frameworks! 7 | platform :ios, '11.0' 8 | 9 | # workaround for https://github.com/CocoaPods/CocoaPods/issues/8073 10 | # need for correct invalidate of cache MultiPlatformLibrary.framework 11 | install! 'cocoapods', :disable_input_output_paths => true 12 | 13 | target 'TestProj' do 14 | # MultiPlatformLibrary 15 | # для корректной установки фреймворка нужно сначала скомпилировать котлин библиотеку - compile-kotlin-framework.sh (в корневой директории репозитория) 16 | pod 'MultiPlatformLibrary', :path => '../mpp-library' 17 | end 18 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/SpecConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import org.gradle.api.NamedDomainObjectContainer 8 | import org.gradle.api.model.ObjectFactory 9 | import javax.inject.Inject 10 | 11 | open class SpecConfig @Inject constructor(objectFactory: ObjectFactory) { 12 | 13 | internal val specs: NamedDomainObjectContainer = 14 | objectFactory.domainObjectContainer(SpecInfo::class.java) 15 | 16 | fun spec(name: String, setup: SpecInfo.() -> Unit) { 17 | specs.create(name).setup() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/mpp-library/src/androidUnitTest/kotlin/readResourceText.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package tests.utils 6 | 7 | import java.io.InputStream 8 | import kotlin.test.assertNotNull 9 | 10 | actual fun Any.readResourceText(path: String): String { 11 | val classLoader: ClassLoader? = this.javaClass.classLoader 12 | assertNotNull(classLoader, "can't get classLoader of $this") 13 | val resource: InputStream? = classLoader.getResourceAsStream(path) 14 | assertNotNull(resource, "can't find resource with path [$path]") 15 | return resource 16 | .bufferedReader() 17 | .readText() 18 | } 19 | -------------------------------------------------------------------------------- /sample/mpp-library/src/mapResponse.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /dynamic: 7 | get: 8 | responses: 9 | '200': 10 | description: Updated 11 | content: 12 | application/json: 13 | schema: 14 | $ref: '#/components/schemas/DogsByGroup' 15 | components: 16 | schemas: 17 | DogsByGroup: 18 | type: object 19 | additionalProperties: 20 | type: array 21 | items: 22 | $ref: '#/components/schemas/Dog' 23 | Dog: 24 | type: object 25 | properties: 26 | bark: 27 | type: boolean 28 | breed: 29 | type: string 30 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/isSSLException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | expect fun Throwable.isSSLException(): Boolean 10 | 11 | expect fun Throwable.getSSLExceptionType(): SSLExceptionType? 12 | 13 | enum class SSLExceptionType { 14 | SecureConnectionFailed, 15 | ServerCertificateHasBadDate, 16 | ServerCertificateUntrusted, 17 | ServerCertificateHasUnknownRoot, 18 | ServerCertificateNotYetValid, 19 | ClientCertificateRejected, 20 | ClientCertificateRequired, 21 | CannotLoadFromNetwork 22 | } 23 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_req_var.mustache: -------------------------------------------------------------------------------- 1 | {{#description}} 2 | /* {{{description}}} */ 3 | {{/description}} 4 | {{#isDecimal}}@Serializable(with = BigNumSerializer::class) {{/isDecimal}} 5 | @SerialName("{{{baseName}}}") 6 | val {{{name}}}: {{#isEnum}}{{#isArray}}kotlin.collections.List<{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{{nameInCamelCase}}}>{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}{{/isArray}}{{^isArray}}{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{nameInCamelCase}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}{{/isArray}}{{#isNullable}}?{{/isNullable}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{#isNullable}}?{{/isNullable}}{{/isEnum}} -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/ParserUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | import platform.Foundation.NSDateFormatter 9 | import platform.Foundation.timeIntervalSince1970 10 | 11 | actual fun String.formatToDate(parseFormat: String): GMTDate { 12 | val formatter = NSDateFormatter() 13 | formatter.dateFormat = parseFormat 14 | return GMTDate( 15 | formatter.dateFromString(this)?.timeIntervalSince1970?.toLong() 16 | ?: throw IllegalArgumentException("Failed, to parse $this for format $parseFormat") 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /sample/android-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /opt/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | enableFeaturePreview("VERSION_CATALOGS") 6 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 7 | 8 | dependencyResolutionManagement { 9 | repositories { 10 | mavenCentral() 11 | google() 12 | } 13 | } 14 | 15 | rootProject.name = "moko-network" 16 | 17 | includeBuild("network-generator") 18 | 19 | include(":network") 20 | include(":network-errors") 21 | include(":network-bignum") 22 | include(":network-engine") 23 | 24 | include(":sample:android-app") 25 | include(":sample:websocket-echo-server") 26 | include(":sample:form-data-binary-server") 27 | include(":sample:mpp-library") 28 | -------------------------------------------------------------------------------- /network-engine/src/commonMain/kotlin/dev/icerock/moko/network/HttpClientEngineConfig.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.client.engine.HttpClientEngine 8 | 9 | class HttpClientEngineConfig { 10 | var androidConnectTimeoutSeconds: Long? = null 11 | var androidCallTimeoutSeconds: Long? = null 12 | var androidReadTimeoutSeconds: Long? = null 13 | var androidWriteTimeoutSeconds: Long? = null 14 | var iosTimeoutIntervalForRequest: Double? = null 15 | var iosTimeoutIntervalForResource: Double? = null 16 | } 17 | 18 | expect fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit = {}): HttpClientEngine 19 | -------------------------------------------------------------------------------- /network/src/commonJvmAndroid/kotlin/dev/icerock/moko/network/GMTDateExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | import java.text.ParseException 9 | import java.text.SimpleDateFormat 10 | import java.util.Date 11 | import java.util.Locale 12 | 13 | actual fun String.toDate(format: String) = try { 14 | GMTDate(SimpleDateFormat(format, Locale.getDefault()).parse(this).time) 15 | } catch (parseException: ParseException) { 16 | throw IllegalArgumentException("Parsing error: the date format is incorrect") 17 | } 18 | 19 | actual fun GMTDate.toString(format: String): String = 20 | SimpleDateFormat(format, Locale.getDefault()).format(Date(timestamp)) 21 | -------------------------------------------------------------------------------- /network/src/commonJvmAndroid/kotlin/dev/icerock/moko/network/isNetworkConnectionError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import java.net.ConnectException 10 | import java.net.SocketException 11 | import java.net.SocketTimeoutException 12 | import java.net.UnknownHostException 13 | import java.util.concurrent.TimeoutException 14 | 15 | actual fun Throwable.isNetworkConnectionError(): Boolean { 16 | return when (this) { 17 | is ConnectException, 18 | is SocketException, 19 | is SocketTimeoutException, 20 | is TimeoutException, 21 | is UnknownHostException -> true 22 | else -> false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/model.mustache: -------------------------------------------------------------------------------- 1 | {{>licenseInfo}} 2 | package {{modelPackage}} 3 | 4 | {{#imports}}import {{import}} 5 | {{/imports}} 6 | import kotlinx.serialization.Serializable 7 | 8 | {{#models}} 9 | {{#model}} 10 | {{#isEnum}}{{>enum_class}}{{/isEnum}} 11 | {{#isAlias}}typealias {{classname}} = {{dataType}}{{/isAlias}} 12 | {{#vendorExtensions.x-allOfGeneration}}{{>data_class_allof}}{{/vendorExtensions.x-allOfGeneration}} 13 | {{#vendorExtensions.x-anyOfGeneration}}{{>data_class_anyof}}{{/vendorExtensions.x-anyOfGeneration}} 14 | 15 | {{^isEnum}}{{^isAlias}}{{^vendorExtensions.x-allOfGeneration}}{{^vendorExtensions.x-anyOfGeneration}}{{>data_class}}{{/vendorExtensions.x-anyOfGeneration}}{{/vendorExtensions.x-allOfGeneration}}{{/isAlias}}{{/isEnum}} 16 | {{/model}} 17 | {{/models}} 18 | -------------------------------------------------------------------------------- /sample/android-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.android.application") 7 | id("dev.icerock.moko.gradle.detekt") 8 | id("kotlin-kapt") 9 | } 10 | 11 | android { 12 | buildFeatures.dataBinding = true 13 | 14 | defaultConfig { 15 | applicationId = "dev.icerock.moko.samples.network" 16 | 17 | multiDexEnabled = true 18 | versionCode = 1 19 | versionName = "0.1.0" 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation(libs.coreKtx) 25 | implementation(libs.appCompat) 26 | implementation(libs.mokoMvvmDataBinding) 27 | implementation(libs.multidex) 28 | implementation(projects.sample.mppLibrary) 29 | } 30 | -------------------------------------------------------------------------------- /sample/mpp-library/src/oneOf.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /pets: 7 | patch: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | oneOf: 13 | - $ref: '#/components/schemas/Cat' 14 | - $ref: '#/components/schemas/Dog' 15 | responses: 16 | '200': 17 | description: Updated 18 | components: 19 | schemas: 20 | Dog: 21 | type: object 22 | properties: 23 | bark: 24 | type: boolean 25 | breed: 26 | type: string 27 | enum: [Dingo, Husky, Retriever, Shepherd] 28 | Cat: 29 | type: object 30 | properties: 31 | hunts: 32 | type: boolean 33 | age: 34 | type: integer -------------------------------------------------------------------------------- /sample/mpp-library/src/iosTest/kotlin/readResourceText.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package tests.utils 6 | 7 | import platform.Foundation.NSBundle 8 | import platform.Foundation.NSString 9 | import platform.Foundation.stringWithContentsOfFile 10 | import kotlin.test.assertNotNull 11 | 12 | actual fun Any.readResourceText(path: String): String { 13 | val pathWithoutExtension: String = path.substringBeforeLast(".") 14 | val extension = path.substringAfterLast(".") 15 | val filePath: String? = NSBundle.mainBundle 16 | .pathForResource("resources/$pathWithoutExtension", extension) 17 | assertNotNull(filePath, "can't find file on path [$filePath]") 18 | return NSString.stringWithContentsOfFile(filePath) as String 19 | } 20 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_opt_var.mustache: -------------------------------------------------------------------------------- 1 | {{#description}} 2 | /* {{{description}}} */ 3 | {{/description}} 4 | {{#isDecimal}}@Serializable(with = BigNumSerializer::class) {{/isDecimal}} 5 | @SerialName("{{{baseName}}}") 6 | val {{{name}}}: {{#isEnum}}{{#isArray}}kotlin.collections.List<{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{{nameInCamelCase}}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}>{{/isArray}}{{^isArray}}{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{{nameInCamelCase}}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{#isEnum}}{{#isEnumFallbackNull}}.asSafeable(){{/isEnumFallbackNull}}{{/isEnum}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} -------------------------------------------------------------------------------- /network-errors/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("dev.icerock.mobile.multiplatform-resources") 8 | id("dev.icerock.moko.gradle.detekt") 9 | id("dev.icerock.moko.gradle.publication") 10 | id("dev.icerock.moko.gradle.stub.javadoc") 11 | } 12 | 13 | android { 14 | namespace = "dev.icerock.moko.network.errors" 15 | } 16 | 17 | dependencies { 18 | commonMainImplementation(libs.kotlinSerialization) 19 | 20 | commonMainApi(libs.mokoErrors) 21 | commonMainApi(libs.mokoResources) 22 | 23 | commonMainImplementation(projects.network) 24 | } 25 | 26 | multiplatformResources { 27 | multiplatformResourcesPackage = "dev.icerock.moko.network.errors" 28 | } 29 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_non_req_non_null_var.mustache: -------------------------------------------------------------------------------- 1 | {{#description}} 2 | /* {{{description}}} */ 3 | {{/description}} 4 | {{#isDecimal}}@Serializable(with = BigNumSerializer::class) {{/isDecimal}} 5 | @SerialName("{{{baseName}}}") 6 | val {{{name}}}: {{#isEnum}}{{#isArray}}kotlin.collections.List<{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{{nameInCamelCase}}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}>{{/isArray}}{{^isArray}}{{#isEnumFallbackNull}}Safeable<{{/isEnumFallbackNull}}{{classname}}.{{nameInCamelCase}}{{#isEnumFallbackNull}}>{{/isEnumFallbackNull}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}{{#defaultValue}} = {{{defaultValue}}}{{#isEnum}}{{#isEnumFallbackNull}}.asSafeable(){{/isEnumFallbackNull}}{{/isEnum}}{{/defaultValue}}{{^defaultValue}}? = null{{/defaultValue}} -------------------------------------------------------------------------------- /network/src/androidMain/kotlin/dev/icerock/moko/network/ParcelExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import android.os.Parcel 8 | import io.ktor.util.date.GMTDate 9 | 10 | fun Parcel.writeGMTDate(date: GMTDate) { 11 | writeLong(date.timestamp) 12 | } 13 | 14 | fun Parcel.readGMTDate(): GMTDate { 15 | return GMTDate(timestamp = readLong()) 16 | } 17 | 18 | fun Parcel.writeGMTDateSafe(value: GMTDate?) { 19 | if (value == null) { 20 | writeByte(0) 21 | } else { 22 | writeByte(1) 23 | writeGMTDate(value) 24 | } 25 | } 26 | 27 | fun Parcel.readGMTDateSafe(): GMTDate? { 28 | return if (readByte() == 1.toByte()) { 29 | readGMTDate() 30 | } else { 31 | null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/mpp-library/src/requestHeaders.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /user: 7 | get: 8 | tags: 9 | - Auth 10 | summary: sign in with token 11 | operationId: 'auth' 12 | security: 13 | - bearerAuth: [ ] 14 | parameters: 15 | - name: Authorization 16 | in: header 17 | required: true 18 | schema: 19 | type: string 20 | responses: 21 | '200': 22 | description: 'Ok' 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/UserInfoResponse' 27 | components: 28 | schemas: 29 | UserInfoResponse: 30 | description: 'authorization response' 31 | required: 32 | - login 33 | properties: 34 | login: 35 | type: string 36 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/class_doc.mustache: -------------------------------------------------------------------------------- 1 | # {{classname}} 2 | 3 | ## Properties 4 | Name | Type | Description | Notes 5 | ------------ | ------------- | ------------- | ------------- 6 | {{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} 7 | {{/vars}} 8 | {{#vars}}{{#isEnum}} 9 | 10 | {{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}} 11 | ## Enum: {{baseName}} 12 | Name | Value 13 | ---- | -----{{#allowableValues}} 14 | {{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} 15 | {{/isEnum}}{{/vars}} 16 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/SpecInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 8 | import org.openapitools.generator.gradle.plugin.tasks.GenerateTask 9 | import java.io.File 10 | 11 | open class SpecInfo(val name: String) { 12 | var inputSpec: File? = null 13 | var sourceSet: String = KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME 14 | var packageName: String? = "dev.icerock.moko.network.generated" 15 | var isInternal = true 16 | var isOpen = false 17 | var filterTags: List = listOf() 18 | var enumFallbackNull = false 19 | internal var configureTask: (GenerateTask.() -> Unit)? = null 20 | 21 | fun configureTask(block: GenerateTask.() -> Unit) { configureTask = block } 22 | } 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m 2 | org.gradle.configureondemand=false 3 | org.gradle.parallel=true 4 | 5 | kotlin.code.style=official 6 | 7 | kotlin.mpp.stability.nowarn=true 8 | kotlin.mpp.androidSourceSetLayoutVersion=2 9 | 10 | android.useAndroidX=true 11 | 12 | mobile.multiplatform.iosTargetWarning=false 13 | 14 | xcodeproj=./sample/ios-app 15 | 16 | moko.android.targetSdk=33 17 | moko.android.compileSdk=33 18 | moko.android.minSdk=16 19 | 20 | moko.publish.name=MOKO network 21 | moko.publish.description=Network components with codegeneration of rest api for mobile (android & ios) Kotlin Multiplatform development 22 | moko.publish.repo.org=icerockdev 23 | moko.publish.repo.name=moko-network 24 | moko.publish.license=Apache-2.0 25 | moko.publish.developers=alex009|Aleksey Mikhailov|Aleksey.Mikhailov@icerockdev.com,Tetraquark|Vladislav Areshkin|vareshkin@icerockdev.com,Dorofeev|Andrey Dorofeev|adorofeev@icerockdev.com 26 | -------------------------------------------------------------------------------- /sample/android-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/compilation-check.yml: -------------------------------------------------------------------------------- 1 | name: KMP library compilation check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | build: 11 | runs-on: macOS-11 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | - name: Check plugin 20 | run: ./gradlew -p network-generator build publishToMavenLocal 21 | - name: Check runtime 22 | run: ./gradlew build publishToMavenLocal syncMultiPlatformLibraryDebugFrameworkIosX64 23 | - name: Install pods 24 | run: cd sample/ios-app && pod install 25 | - name: Check iOS 26 | run: cd sample/ios-app && set -o pipefail && xcodebuild -scheme TestProj -workspace TestProj.xcworkspace -configuration Debug -sdk iphonesimulator -arch x86_64 build CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcpretty 27 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/OneOfOperatorProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.swagger.v3.oas.models.OpenAPI 8 | import io.swagger.v3.oas.models.media.ComposedSchema 9 | import io.swagger.v3.oas.models.media.Schema 10 | 11 | /** 12 | * OpenApi schema processor that replace oneOf operator to the temporary type [propertyNewType]. 13 | */ 14 | internal class OneOfOperatorProcessor( 15 | private val propertyNewType: String 16 | ) : OpenApiSchemaProcessor { 17 | 18 | @Suppress("ReturnCount") 19 | override fun process(openApi: OpenAPI, schema: Schema<*>, context: SchemaContext): Schema<*> { 20 | if (schema !is ComposedSchema) return schema 21 | if (schema.oneOf.isNullOrEmpty()) return schema 22 | 23 | return Schema().apply { 24 | type = propertyNewType 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/SchemaEnumNullProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.swagger.v3.oas.models.OpenAPI 8 | import io.swagger.v3.oas.models.media.Schema 9 | 10 | /** 11 | * OpenApi schema processor that removes all [null] items from enum fields of the schema. 12 | */ 13 | internal class SchemaEnumNullProcessor : OpenApiSchemaProcessor { 14 | override fun process(openApi: OpenAPI, schema: Schema<*>, context: SchemaContext): Schema<*> { 15 | val schemaProperties = schema.properties ?: return schema 16 | 17 | schemaProperties.forEach { (_, propSchema) -> 18 | val enumField = propSchema.enum 19 | if (enumField != null && enumField.isNotEmpty()) { 20 | propSchema.enum = enumField.filterNotNull() 21 | } 22 | } 23 | 24 | return schema 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/ThrowableToNSErrorMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("MaximumLineLength") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import platform.Foundation.NSError 10 | import kotlin.native.concurrent.AtomicReference 11 | 12 | object ThrowableToNSErrorMapper : (Throwable) -> NSError? { 13 | private val mapperRef: AtomicReference<((Throwable) -> NSError?)?> = AtomicReference(null) 14 | 15 | override fun invoke(throwable: Throwable): NSError? { 16 | @Suppress("MaxLineLength") 17 | return requireNotNull(mapperRef.value) { 18 | "please setup ThrowableToNSErrorMapper by call ThrowableToNSErrorMapper.setup() in iosMain or use dev.icerock.moko.network.createHttpClientEngine" 19 | }.invoke(throwable) 20 | } 21 | 22 | fun setup(block: (Throwable) -> NSError?) { 23 | mapperRef.value = block 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /network-engine/src/commonJvmAndroid/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import io.ktor.client.engine.HttpClientEngine 10 | import io.ktor.client.engine.okhttp.OkHttp 11 | import java.util.concurrent.TimeUnit 12 | 13 | actual fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit): HttpClientEngine { 14 | val config = HttpClientEngineConfig().also(block) 15 | return OkHttp.create { 16 | this.config { 17 | config.androidConnectTimeoutSeconds?.let { connectTimeout(it, TimeUnit.SECONDS) } 18 | config.androidCallTimeoutSeconds?.let { callTimeout(it, TimeUnit.SECONDS) } 19 | config.androidReadTimeoutSeconds?.let { readTimeout(it, TimeUnit.SECONDS) } 20 | config.androidWriteTimeoutSeconds?.let { writeTimeout(it, TimeUnit.SECONDS) } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptions/ResponseException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptions 6 | 7 | import io.ktor.client.request.HttpRequest 8 | import io.ktor.client.statement.HttpResponse 9 | import io.ktor.http.HttpStatusCode 10 | 11 | open class ResponseException( 12 | val request: HttpRequest, 13 | val response: HttpResponse, 14 | val responseMessage: String 15 | ) : Exception("Request: ${request.url}; Response: $responseMessage [$response.status.value]") { 16 | 17 | val httpStatusCode: Int 18 | get() = response.status.value 19 | 20 | val isUnauthorized: Boolean 21 | get() = httpStatusCode == HttpStatusCode.Unauthorized.value 22 | 23 | val isAccessDenied: Boolean 24 | get() = httpStatusCode == HttpStatusCode.Forbidden.value 25 | 26 | val isNotFound: Boolean 27 | get() = httpStatusCode == HttpStatusCode.NotFound.value 28 | } 29 | -------------------------------------------------------------------------------- /network-engine/src/iosMain/kotlin/dev/icerock/moko/network/createHttpClientEngine.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import io.ktor.client.engine.HttpClientEngine 10 | import io.ktor.client.engine.darwin.Darwin 11 | import io.ktor.client.engine.darwin.DarwinHttpRequestException 12 | 13 | actual fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit): HttpClientEngine { 14 | // configure darwin throwable mapper 15 | ThrowableToNSErrorMapper.setup { (it as? DarwinHttpRequestException)?.origin } 16 | // configure darwin engine 17 | val config = HttpClientEngineConfig().also(block) 18 | return Darwin.create { 19 | this.configureSession { 20 | config.iosTimeoutIntervalForRequest?.let { setTimeoutIntervalForRequest(it) } 21 | config.iosTimeoutIntervalForResource?.let { setTimeoutIntervalForResource(it) } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /network/src/commonJvmAndroid/kotlin/dev/icerock/moko/network/isSSLException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import javax.net.ssl.SSLException 10 | import javax.net.ssl.SSLHandshakeException 11 | import javax.net.ssl.SSLKeyException 12 | import javax.net.ssl.SSLPeerUnverifiedException 13 | import javax.net.ssl.SSLProtocolException 14 | 15 | actual fun Throwable.isSSLException(): Boolean { 16 | return this is SSLException 17 | } 18 | 19 | actual fun Throwable.getSSLExceptionType(): SSLExceptionType? { 20 | return when (this) { 21 | is SSLHandshakeException -> SSLExceptionType.ServerCertificateUntrusted 22 | is SSLKeyException -> SSLExceptionType.ClientCertificateRejected 23 | is SSLPeerUnverifiedException -> SSLExceptionType.ClientCertificateRequired 24 | is SSLProtocolException -> SSLExceptionType.SecureConnectionFailed 25 | is SSLException -> SSLExceptionType.SecureConnectionFailed 26 | else -> null 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /network-bignum/src/commonMain/kotlin/dev/icerock/moko/network/bignum/BigNumSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.bignum 6 | 7 | import com.soywiz.kbignum.BigNum 8 | import kotlinx.serialization.KSerializer 9 | import kotlinx.serialization.Serializer 10 | import kotlinx.serialization.descriptors.PrimitiveKind 11 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 12 | import kotlinx.serialization.encoding.Decoder 13 | import kotlinx.serialization.encoding.Encoder 14 | 15 | @Serializer(forClass = BigNum::class) 16 | object BigNumSerializer : KSerializer { 17 | override val descriptor = PrimitiveSerialDescriptor( 18 | serialName = "dev.icerock.moko.network.bignum.BigNumSerializer", 19 | kind = PrimitiveKind.STRING 20 | ) 21 | 22 | override fun serialize(encoder: Encoder, value: BigNum) { 23 | encoder.encodeString(value.toString()) 24 | } 25 | 26 | override fun deserialize(decoder: Decoder): BigNum { 27 | val string = decoder.decodeString() 28 | return BigNum(string) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /network-engine/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("dev.icerock.moko.gradle.detekt") 8 | id("dev.icerock.moko.gradle.publication") 9 | id("dev.icerock.moko.gradle.stub.javadoc") 10 | id("dev.icerock.moko.gradle.tests") 11 | } 12 | 13 | android { 14 | namespace = "dev.icerock.moko.network.engine" 15 | } 16 | 17 | kotlin { 18 | jvm() 19 | 20 | sourceSets { 21 | val commonMain by getting 22 | 23 | val commonJvmAndroid = create("commonJvmAndroid") { 24 | dependsOn(commonMain) 25 | dependencies { 26 | api(libs.ktorClientOkHttp) 27 | } 28 | } 29 | 30 | val androidMain by getting { 31 | dependsOn(commonJvmAndroid) 32 | } 33 | 34 | val jvmMain by getting { 35 | dependsOn(commonJvmAndroid) 36 | } 37 | } 38 | } 39 | 40 | dependencies { 41 | commonMainImplementation(libs.coroutines) 42 | commonMainApi(projects.network) 43 | iosMainApi(libs.ktorClientIos) 44 | } 45 | -------------------------------------------------------------------------------- /sample/ios-app/src/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/ios-app/src/TestViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import UIKit 6 | import MultiPlatformLibrary 7 | 8 | class TestViewController: UIViewController { 9 | 10 | @IBOutlet private var restText: UITextView! 11 | @IBOutlet private var webSocketText: UITextView! 12 | 13 | private var viewModel: TestViewModel! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | viewModel = TestViewModel() 19 | viewModel.exceptionHandler.bind(viewController: self) 20 | 21 | viewModel.petInfo.addObserver { [weak self] info in 22 | self?.restText.text = info as String? 23 | } 24 | 25 | viewModel.websocketInfo.addObserver { [weak self] info in 26 | self?.webSocketText.text = info as String? 27 | } 28 | } 29 | 30 | @IBAction func onRefreshPressed() { 31 | viewModel.onRefreshPetPressed() 32 | } 33 | 34 | @IBAction func onRefreshWebsocketPressed() { 35 | viewModel.onRefreshWebsocketPressed() 36 | } 37 | 38 | deinit { 39 | viewModel.onCleared() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/DynamicUserAgent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.plugins 6 | 7 | import io.ktor.client.HttpClient 8 | import io.ktor.client.plugins.HttpClientPlugin 9 | import io.ktor.client.request.HttpRequestPipeline 10 | import io.ktor.client.request.header 11 | import io.ktor.http.HttpHeaders 12 | import io.ktor.util.AttributeKey 13 | 14 | class DynamicUserAgent( 15 | val agentProvider: () -> String? 16 | ) { 17 | class Config(var agentProvider: () -> String? = { null }) 18 | 19 | companion object Feature : HttpClientPlugin { 20 | override val key: AttributeKey = AttributeKey("DynamicUserAgent") 21 | 22 | override fun prepare(block: Config.() -> Unit): DynamicUserAgent = 23 | DynamicUserAgent(Config().apply(block).agentProvider) 24 | 25 | override fun install(plugin: DynamicUserAgent, scope: HttpClient) { 26 | scope.requestPipeline.intercept(HttpRequestPipeline.State) { 27 | plugin.agentProvider()?.let { context.header(HttpHeaders.UserAgent, it) } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptionfactory/HttpExceptionFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptionfactory 6 | 7 | import dev.icerock.moko.network.exceptions.ResponseException 8 | import io.ktor.client.request.HttpRequest 9 | import io.ktor.client.statement.HttpResponse 10 | 11 | class HttpExceptionFactory( 12 | private val defaultParser: HttpExceptionParser, 13 | private val customParsers: Map 14 | ) : ExceptionFactory { 15 | 16 | override fun createException( 17 | request: HttpRequest, 18 | response: HttpResponse, 19 | responseBody: String? 20 | ): ResponseException { 21 | val parser = customParsers[response.status.value] ?: defaultParser 22 | 23 | val exception = parser.parseException(request, response, responseBody) 24 | 25 | return exception ?: ResponseException(request, response, responseBody.orEmpty()) 26 | } 27 | 28 | fun interface HttpExceptionParser { 29 | fun parseException( 30 | request: HttpRequest, 31 | response: HttpResponse, 32 | responseBody: String? 33 | ): ResponseException? 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/nullable/NullableSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.nullable 6 | 7 | import kotlinx.serialization.KSerializer 8 | import kotlinx.serialization.builtins.nullable 9 | import kotlinx.serialization.descriptors.SerialDescriptor 10 | import kotlinx.serialization.descriptors.buildClassSerialDescriptor 11 | import kotlinx.serialization.encoding.Decoder 12 | import kotlinx.serialization.encoding.Encoder 13 | 14 | class NullableSerializer( 15 | tSerializer: KSerializer 16 | ) : KSerializer> { 17 | private val nullableTypeSerializer = tSerializer.nullable 18 | 19 | override val descriptor: SerialDescriptor = buildClassSerialDescriptor( 20 | serialName = "dev.icerock.moko.network.nullable.Nullable", 21 | nullableTypeSerializer.descriptor 22 | ) { } 23 | 24 | override fun deserialize(decoder: Decoder): Nullable { 25 | val value = nullableTypeSerializer.deserialize(decoder) 26 | return Nullable(value) 27 | } 28 | 29 | override fun serialize(encoder: Encoder, value: Nullable) { 30 | nullableTypeSerializer.serialize(encoder, value.value) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class.mustache: -------------------------------------------------------------------------------- 1 | import kotlinx.serialization.SerialName 2 | {{#hasEnums}}{{#isEnumFallbackNull}}import dev.icerock.moko.network.safeable.*{{/isEnumFallbackNull}}{{/hasEnums}} 3 | 4 | /** 5 | * {{{description}}} 6 | {{#vars}} 7 | * @param {{name}} {{{description}}} 8 | {{/vars}} 9 | */ 10 | @Serializable 11 | {{>classes_modifiers}}data class {{classname}} ( 12 | {{#allVars}} 13 | {{#required}}{{#optional}}{{>data_class_opt_var}}{{/optional}}{{^optional}}{{>data_class_req_var}}{{/optional}}{{/required}}{{^required}}{{#isNullable}}{{>data_class_non_req_null_var}}{{/isNullable}}{{^isNullable}}{{>data_class_non_req_non_null_var}}{{/isNullable}}{{/required}}{{^-last}},{{/-last}} 14 | {{/allVars}} 15 | 16 | ) { 17 | {{#hasEnums}}{{#vars}}{{#isEnum}} 18 | /** 19 | * {{{description}}} 20 | * Values: {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}} 21 | */ 22 | @Serializable 23 | enum class {{nameInCamelCase}} { 24 | {{#allowableValues}}{{#enumVars}} 25 | @SerialName({{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) 26 | {{name}}{{^-last}},{{/-last}}{{#-last}};{{/-last}} 27 | {{/enumVars}}{{/allowableValues}} 28 | } 29 | {{/isEnum}}{{/vars}}{{/hasEnums}} 30 | } 31 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_allof.mustache: -------------------------------------------------------------------------------- 1 | import kotlinx.serialization.SerialName 2 | import kotlinx.serialization.json.Json 3 | import kotlinx.serialization.json.JsonElement 4 | import kotlinx.serialization.json.JsonObject 5 | import kotlinx.serialization.json.jsonObject 6 | import dev.icerock.moko.network.schemas.ComposedSchemaSerializer 7 | 8 | @Serializable(with = {{classname}}Serializer::class) 9 | {{>classes_modifiers}}data class {{classname}} ( 10 | {{#allVars}} 11 | val {{{name}}}: {{{datatype}}}{{^-last}},{{/-last}} 12 | {{/allVars}} 13 | ) 14 | 15 | {{>classes_modifiers}}object {{classname}}Serializer : ComposedSchemaSerializer<{{classname}}>("{{classname}}Serializer") { 16 | 17 | override fun decodeJson(json: Json, element: JsonElement): {{classname}} { 18 | return {{classname}}( 19 | {{#allVars}} 20 | {{{name}}} = json.decodeFromJsonElement({{{datatype}}}.serializer(), element){{^-last}},{{/-last}} 21 | {{/allVars}} 22 | ) 23 | } 24 | 25 | override fun encodeJson(json: Json, value: {{classname}}): List { 26 | return listOf( 27 | {{#allVars}} 28 | json.encodeToJsonElement({{{datatype}}}.serializer(), value.{{{name}}}).jsonObject{{^-last}},{{/-last}} 29 | {{/allVars}} 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/createHttpClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import dev.icerock.moko.network.exceptionfactory.HttpExceptionFactory 6 | import dev.icerock.moko.network.exceptionfactory.parser.ErrorExceptionParser 7 | import dev.icerock.moko.network.exceptionfactory.parser.ValidationExceptionParser 8 | import dev.icerock.moko.network.plugins.ExceptionPlugin 9 | import io.ktor.client.HttpClient 10 | import io.ktor.client.engine.mock.MockEngine 11 | import io.ktor.client.engine.mock.MockRequestHandler 12 | import io.ktor.http.HttpStatusCode 13 | import kotlinx.serialization.json.Json 14 | 15 | fun createMockClient( 16 | json: Json = Json { 17 | ignoreUnknownKeys = true 18 | }, 19 | handler: MockRequestHandler 20 | ): HttpClient { 21 | return HttpClient(MockEngine) { 22 | engine { 23 | addHandler(handler) 24 | } 25 | 26 | install(ExceptionPlugin) { 27 | exceptionFactory = HttpExceptionFactory( 28 | defaultParser = ErrorExceptionParser(json), 29 | customParsers = mapOf( 30 | HttpStatusCode.UnprocessableEntity.value to ValidationExceptionParser(json) 31 | ) 32 | ) 33 | } 34 | 35 | expectSuccess = false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sample/mpp-library/src/anyOf.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /pets: 7 | patch: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | anyOf: 13 | - $ref: '#/components/schemas/PetByAge' 14 | - $ref: '#/components/schemas/PetByType' 15 | responses: 16 | '200': 17 | description: Updated 18 | content: 19 | application/json: 20 | schema: 21 | anyOf: 22 | - $ref: '#/components/schemas/PetByAge' 23 | - $ref: '#/components/schemas/PetByType' 24 | components: 25 | schemas: 26 | PetByAge: 27 | type: object 28 | properties: 29 | age: 30 | type: integer 31 | nickname: 32 | type: string 33 | required: 34 | - age 35 | 36 | PetByType: 37 | type: object 38 | properties: 39 | pet_type: 40 | type: string 41 | enum: [Cat, Dog] 42 | hunts: 43 | type: boolean 44 | required: 45 | - pet_type 46 | TimeLineList: 47 | type: object 48 | properties: 49 | results: 50 | type: array 51 | items: 52 | anyOf: 53 | - $ref: '#/components/schemas/PetByType' 54 | - $ref: '#/components/schemas/PetByAge' -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/AllOfTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import io.ktor.client.engine.mock.MockRequestHandler 6 | import io.ktor.client.engine.mock.respondOk 7 | import kotlinx.coroutines.runBlocking 8 | import kotlinx.serialization.json.Json 9 | import openapi.allof.apis.DefaultApi 10 | import openapi.allof.models.DogAllOf 11 | import openapi.allof.models.DogComposed 12 | import openapi.allof.models.Pet 13 | import kotlin.test.Test 14 | import kotlin.test.assertEquals 15 | 16 | class AllOfTest { 17 | @Test 18 | fun `allOf - both items`() { 19 | val allOfApi = createAllOfApi { 20 | respondOk("""{"pet_type":"Dog","bark":false}""") 21 | } 22 | 23 | val result = runBlocking { allOfApi.petsPatch() } 24 | 25 | assertEquals( 26 | expected = DogComposed( 27 | item0 = Pet(petType = "Dog"), 28 | item1 = DogAllOf(bark = false) 29 | ), 30 | actual = result 31 | ) 32 | } 33 | 34 | private fun createAllOfApi(mock: MockRequestHandler): DefaultApi { 35 | val json = Json.Default 36 | val httpClient = createMockClient(json, mock) 37 | return DefaultApi( 38 | basePath = "https://localhost", 39 | httpClient = httpClient, 40 | json = json 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /network-errors/src/commonMain/resources/MR/base/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | No network connection 4 | Authorization error 5 | The requested data was not found 6 | Access denied 7 | Internal server error (%d) 8 | Invalid server data received 9 | The SSL certificate error. Secure connection failed 10 | The SSL certificate error. Server certificate has bad date 11 | The SSL certificate error. Server certificate untrusted 12 | The SSL certificate error. Server certificate has unknown root 13 | The SSL certificate error. Server certificate not yet valid 14 | The SSL certificate error. Client certificate rejected 15 | The SSL certificate error. Client certificate required 16 | The SSL certificate error. Cannot load from network 17 | -------------------------------------------------------------------------------- /network-errors/src/commonMain/resources/MR/ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ошибка сетевого соединения 4 | Ошибка авторизации 5 | Запрашиваемые данные не найдены 6 | Доступ запрещен 7 | Внутренняя ошибка сервера (%d) 8 | Получены некорректные данные сервера 9 | Ошибка SSL-сертификата. Сбой безопасного соединения 10 | Ошибка SSL-сертификата. Сертификат сервера имеет неверную дату 11 | Ошибка SSL-сертификата. Сертификат сервера ненадежный 12 | Ошибка SSL-сертификата. Сертификат сервера имеет неизвестный корень 13 | Ошибка SSL-сертификата. Сертификат сервера еще не действителен 14 | Ошибка SSL-сертификата. Клиентский сертификат отклонен 15 | Ошибка SSL-сертификата. Требуется сертификат клиента 16 | Ошибка SSL-сертификата. Не удается загрузить из сети 17 | 18 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/PetApiTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import dev.icerock.moko.network.generated.apis.PetApi 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.engine.mock.MockEngine 8 | import io.ktor.client.engine.mock.respondOk 9 | import kotlinx.coroutines.runBlocking 10 | import kotlinx.serialization.json.Json 11 | import tests.utils.readResourceText 12 | import kotlin.test.BeforeTest 13 | import kotlin.test.Test 14 | import kotlin.test.assertEquals 15 | 16 | class PetApiTest { 17 | private lateinit var httpClient: HttpClient 18 | private lateinit var json: Json 19 | private lateinit var petApi: PetApi 20 | 21 | @BeforeTest 22 | fun setup() { 23 | json = Json.Default 24 | 25 | httpClient = HttpClient(MockEngine) { 26 | engine { 27 | addHandler { request -> 28 | respondOk(content = readResourceText("PetstoreSearchResponse.json")) 29 | } 30 | } 31 | } 32 | 33 | petApi = PetApi( 34 | httpClient = httpClient, 35 | json = json, 36 | basePath = "https://localhost" 37 | ) 38 | } 39 | 40 | @Test 41 | fun `search test`() { 42 | val result = runBlocking { 43 | petApi.findPetsByStatus(listOf("available")) 44 | } 45 | 46 | assertEquals(expected = 217, actual = result.size) 47 | } 48 | } -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/NetworkConnectionError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import platform.Foundation.NSError 8 | import platform.Foundation.NSURLErrorCannotConnectToHost 9 | import platform.Foundation.NSURLErrorCannotFindHost 10 | import platform.Foundation.NSURLErrorCannotLoadFromNetwork 11 | import platform.Foundation.NSURLErrorDNSLookupFailed 12 | import platform.Foundation.NSURLErrorDataNotAllowed 13 | import platform.Foundation.NSURLErrorDomain 14 | import platform.Foundation.NSURLErrorNetworkConnectionLost 15 | import platform.Foundation.NSURLErrorNotConnectedToInternet 16 | import platform.Foundation.NSURLErrorResourceUnavailable 17 | import platform.Foundation.NSURLErrorTimedOut 18 | 19 | actual fun Throwable.isNetworkConnectionError(): Boolean { 20 | val nsError: NSError? = ThrowableToNSErrorMapper(this) 21 | 22 | return when { 23 | nsError?.domain == NSURLErrorDomain && nsError?.code in listOf( 24 | NSURLErrorTimedOut, 25 | NSURLErrorCannotFindHost, 26 | NSURLErrorCannotConnectToHost, 27 | NSURLErrorNetworkConnectionLost, 28 | NSURLErrorDNSLookupFailed, 29 | NSURLErrorResourceUnavailable, 30 | NSURLErrorNotConnectedToInternet, 31 | NSURLErrorDataNotAllowed, 32 | NSURLErrorCannotLoadFromNetwork, 33 | ) -> true 34 | 35 | else -> false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/tasks/GenerateTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.tasks 6 | 7 | import dev.icerock.moko.network.KtorCodegen 8 | import dev.icerock.moko.network.SpecInfo 9 | import org.openapitools.generator.gradle.plugin.tasks.GenerateTask 10 | import java.io.File 11 | 12 | open class GenerateTask : GenerateTask() { 13 | init { 14 | group = "moko-network" 15 | } 16 | 17 | fun configure(specInfo: SpecInfo, generatedDir: String) { 18 | inputSpec.set(specInfo.inputSpec?.path) 19 | packageName.set(specInfo.packageName) 20 | 21 | val excludedTags = specInfo.filterTags.joinToString(",") 22 | val props = mapOf( 23 | KtorCodegen.ADDITIONAL_OPTIONS_KEY_IS_INTERNAL to "${specInfo.isInternal}", 24 | KtorCodegen.ADDITIONAL_OPTIONS_KEY_IS_OPEN to "${specInfo.isOpen}", 25 | KtorCodegen.ADDITIONAL_OPTIONS_KEY_EXCLUDED_TAGS to excludedTags, 26 | KtorCodegen.ADDITIONAL_OPTIONS_KEY_ENUM_FALLBACK_NULL to "${specInfo.enumFallbackNull}" 27 | ) 28 | additionalProperties.set(props) 29 | 30 | generatorName.set("kotlin-ktor-client") 31 | outputDir.set(generatedDir) 32 | 33 | specInfo.configureTask?.invoke(this) 34 | 35 | doFirst { 36 | // clean directory before generate new code 37 | File(outputDir.get()).deleteRecursively() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version' 8 | default: '0.1.0' 9 | required: true 10 | 11 | jobs: 12 | publish: 13 | name: Publish library at mavenCentral 14 | runs-on: macOS-11 15 | env: 16 | OSSRH_USER: ${{ secrets.OSSRH_USER }} 17 | OSSRH_KEY: ${{ secrets.OSSRH_KEY }} 18 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEYID }} 19 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 20 | SIGNING_KEY: ${{ secrets.GPG_KEY_CONTENTS }} 21 | 22 | steps: 23 | - uses: actions/checkout@v1 24 | - name: Set up JDK 11 25 | uses: actions/setup-java@v1 26 | with: 27 | java-version: 11 28 | - name: Publish plugin 29 | run: ./gradlew -p network-generator publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PLUGIN_PORTAL_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PLUGIN_PORTAL_SECRET }} 30 | - name: Publish library 31 | run: ./gradlew publish 32 | release: 33 | name: Create release 34 | needs: publish 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Create Release 38 | id: create_release 39 | uses: actions/create-release@v1 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | with: 43 | commitish: ${{ github.ref }} 44 | tag_name: release/${{ github.event.inputs.version }} 45 | release_name: ${{ github.event.inputs.version }} 46 | body: "Will be filled later" 47 | draft: true 48 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/MapResponseTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import io.ktor.client.engine.mock.MockRequestHandler 6 | import io.ktor.client.engine.mock.respondOk 7 | import kotlinx.coroutines.runBlocking 8 | import kotlinx.serialization.json.Json 9 | import openapi.mapResponse.apis.DefaultApi 10 | import openapi.mapResponse.models.Dog 11 | import kotlin.test.Test 12 | import kotlin.test.assertEquals 13 | 14 | class MapResponseTest { 15 | @Test 16 | fun `map in response`() { 17 | val anyOfApi = createMapResponseApi { 18 | respondOk( 19 | """ 20 | { 21 | "first": [ 22 | { 23 | "bark": false, 24 | "breed": "test" 25 | } 26 | ] 27 | } 28 | """.trimIndent() 29 | ) 30 | } 31 | 32 | val result = runBlocking { 33 | anyOfApi.dynamicGet() 34 | } 35 | 36 | assertEquals( 37 | expected = mapOf( 38 | "first" to listOf( 39 | Dog(bark = false, breed = "test") 40 | ) 41 | ), 42 | actual = result 43 | ) 44 | } 45 | 46 | private fun createMapResponseApi(mock: MockRequestHandler): DefaultApi { 47 | val json = Json.Default 48 | val httpClient = createMockClient(json, mock) 49 | return DefaultApi( 50 | basePath = "https://localhost", 51 | httpClient = httpClient, 52 | json = json 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sample/websocket-echo-server/src/main/kotlin/com/icerockdev/server/Application.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.server 6 | 7 | import io.ktor.application.Application 8 | import io.ktor.application.call 9 | import io.ktor.application.install 10 | import io.ktor.http.ContentType 11 | import io.ktor.http.cio.websocket.Frame 12 | import io.ktor.http.cio.websocket.pingPeriod 13 | import io.ktor.http.cio.websocket.readText 14 | import io.ktor.http.cio.websocket.timeout 15 | import io.ktor.response.respondText 16 | import io.ktor.routing.get 17 | import io.ktor.routing.routing 18 | import io.ktor.websocket.WebSockets 19 | import io.ktor.websocket.webSocket 20 | import java.time.Duration 21 | 22 | fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) 23 | 24 | fun Application.module() { 25 | install(WebSockets) { 26 | pingPeriod = Duration.ofSeconds(15) 27 | timeout = Duration.ofSeconds(15) 28 | maxFrameSize = Long.MAX_VALUE 29 | masking = false 30 | } 31 | 32 | routing { 33 | get("/") { 34 | call.respondText("HELLO WORLD!", contentType = ContentType.Text.Plain) 35 | } 36 | 37 | webSocket("/myws/echo") { 38 | send(Frame.Text("Hi from server")) 39 | for (i in 1..20) { 40 | val frame = incoming.receive() 41 | if (frame is Frame.Text) { 42 | send(Frame.Text("Client said: " + frame.readText())) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /network-generator/src/main/resources/kotlin-ktor-client/data_class_anyof.mustache: -------------------------------------------------------------------------------- 1 | import kotlinx.serialization.SerialName 2 | import kotlinx.serialization.json.Json 3 | import kotlinx.serialization.json.JsonElement 4 | import kotlinx.serialization.json.JsonObject 5 | import kotlinx.serialization.json.jsonObject 6 | import dev.icerock.moko.network.schemas.ComposedSchemaSerializer 7 | import dev.icerock.moko.network.exceptions.DataNotFitAnyOfSchema 8 | 9 | @Serializable(with = {{classname}}Serializer::class) 10 | {{>classes_modifiers}}data class {{classname}} ( 11 | {{#allVars}} 12 | val {{{name}}}: {{{datatype}}}?{{^-last}},{{/-last}} 13 | {{/allVars}} 14 | ) 15 | 16 | {{>classes_modifiers}}object {{classname}}Serializer : ComposedSchemaSerializer<{{classname}}>("{{classname}}Serializer") { 17 | 18 | override fun decodeJson(json: Json, element: JsonElement): {{classname}} { 19 | {{#allVars}} 20 | val {{{name}}} = runCatching { json.decodeFromJsonElement({{{datatype}}}.serializer(), element) } 21 | {{/allVars}} 22 | 23 | ensureAnyItemIsSuccess(element, listOf({{#allVars}}{{{name}}}{{^-last}},{{/-last}}{{/allVars}})) 24 | 25 | return {{classname}}( 26 | {{#allVars}} 27 | {{{name}}} = {{{name}}}.getOrNull(){{^-last}},{{/-last}} 28 | {{/allVars}} 29 | ) 30 | } 31 | 32 | override fun encodeJson(json: Json, value: {{classname}}): List { 33 | return listOfNotNull( 34 | {{#allVars}} 35 | value.{{{name}}}?.let { json.encodeToJsonElement({{{datatype}}}.serializer(), it) }{{^-last}},{{/-last}} 36 | {{/allVars}} 37 | ).map { it.jsonObject } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/TokenPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.plugins 6 | 7 | import io.ktor.client.HttpClient 8 | import io.ktor.client.plugins.HttpClientPlugin 9 | import io.ktor.client.request.HttpRequestPipeline 10 | import io.ktor.client.request.header 11 | import io.ktor.util.AttributeKey 12 | 13 | class TokenPlugin private constructor( 14 | private val tokenHeaderName: String, 15 | private val tokenProvider: TokenProvider 16 | ) { 17 | 18 | class Config { 19 | var tokenHeaderName: String? = null 20 | var tokenProvider: TokenProvider? = null 21 | fun build() = TokenPlugin( 22 | tokenHeaderName ?: throw IllegalArgumentException("HeaderName should be contain"), 23 | tokenProvider ?: throw IllegalArgumentException("TokenProvider should be contain") 24 | ) 25 | } 26 | 27 | companion object Plugin : HttpClientPlugin { 28 | override val key = AttributeKey("TokenPlugin") 29 | 30 | override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() 31 | 32 | override fun install(plugin: TokenPlugin, scope: HttpClient) { 33 | scope.requestPipeline.intercept(HttpRequestPipeline.State) { 34 | plugin.tokenProvider.getToken()?.apply { 35 | context.headers.remove(plugin.tokenHeaderName) 36 | context.header(plugin.tokenHeaderName, this) 37 | } 38 | } 39 | } 40 | } 41 | 42 | fun interface TokenProvider { 43 | fun getToken(): String? 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/GMTDateExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.ktor.util.date.GMTDate 8 | import platform.Foundation.NSDate 9 | import platform.Foundation.NSDateFormatter 10 | import platform.Foundation.NSLocale 11 | import platform.Foundation.currentLocale 12 | import platform.Foundation.timeIntervalSince1970 13 | 14 | private const val MILLISECONDS_IN_SECONDS = 1000 15 | 16 | @Suppress("TooGenericExceptionCaught") 17 | actual fun String.toDate(format: String): GMTDate { 18 | val formatter = NSDateFormatter() 19 | val locale = NSLocale.currentLocale() 20 | formatter.setDateFormat(format) 21 | formatter.setLocale(locale) 22 | return try { 23 | val date: NSDate = formatter.dateFromString(this)!! 24 | val timestamp: Long = (date.timeIntervalSince1970 * MILLISECONDS_IN_SECONDS).toLong() 25 | GMTDate(timestamp) 26 | } catch (npe: NullPointerException) { 27 | throw IllegalArgumentException("Parsing error: the date format is incorrect", npe) 28 | } 29 | } 30 | 31 | actual fun GMTDate.toString(format: String): String { 32 | val formatter = NSDateFormatter() 33 | val locale: NSLocale = NSLocale.currentLocale() 34 | formatter.setDateFormat(format) 35 | formatter.setLocale(locale) 36 | val timeInSeconds: Double = this.timestamp.toDouble() / MILLISECONDS_IN_SECONDS 37 | val nowSeconds: Double = 38 | NSDate().timeIntervalSince1970 - NSDate().timeIntervalSinceReferenceDate 39 | val timestamp: Double = timeInSeconds - nowSeconds 40 | val date = NSDate(timestamp) 41 | return formatter.stringFromDate(date) 42 | } 43 | -------------------------------------------------------------------------------- /network/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | plugins { 6 | id("dev.icerock.moko.gradle.multiplatform.mobile") 7 | id("org.jetbrains.kotlin.plugin.serialization") 8 | id("dev.icerock.moko.gradle.detekt") 9 | id("dev.icerock.moko.gradle.publication") 10 | id("dev.icerock.moko.gradle.stub.javadoc") 11 | id("dev.icerock.moko.gradle.tests") 12 | } 13 | 14 | android { 15 | namespace = "dev.icerock.moko.network" 16 | } 17 | 18 | kotlin { 19 | jvm() 20 | 21 | sourceSets { 22 | val commonMain by getting 23 | 24 | val commonJvmAndroid = create("commonJvmAndroid") { 25 | dependsOn(commonMain) 26 | } 27 | 28 | val androidMain by getting { 29 | dependsOn(commonJvmAndroid) 30 | } 31 | 32 | val jvmMain by getting { 33 | dependsOn(commonJvmAndroid) 34 | } 35 | 36 | val jvmTest by getting { 37 | dependencies { 38 | implementation(libs.kotlinTestJUnit) 39 | } 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | commonMainImplementation(libs.coroutines) 46 | commonMainApi(libs.kotlinSerialization) 47 | commonMainApi(libs.ktorClient) 48 | 49 | androidMainImplementation(libs.appCompat) 50 | 51 | commonTestImplementation(libs.ktorClientMock) 52 | commonTestImplementation(libs.kotlinTest) 53 | commonTestImplementation(libs.kotlinTestAnnotations) 54 | 55 | androidTestImplementation(libs.kotlinTestJUnit) 56 | } 57 | 58 | tasks.named("publishToMavenLocal") { 59 | val pluginPublish = gradle.includedBuild("network-generator") 60 | .task(":publishToMavenLocal") 61 | dependsOn(pluginPublish) 62 | } 63 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/LanguagePlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.plugins 6 | 7 | import io.ktor.client.HttpClient 8 | import io.ktor.client.plugins.HttpClientPlugin 9 | import io.ktor.client.request.HttpRequestPipeline 10 | import io.ktor.client.request.header 11 | import io.ktor.util.AttributeKey 12 | 13 | class LanguagePlugin private constructor( 14 | private val languageHeaderName: String, 15 | private val languageProvider: LanguagePlugin.LanguageCodeProvider 16 | ) { 17 | class Config { 18 | var languageHeaderName: String? = null 19 | var languageCodeProvider: LanguageCodeProvider? = null 20 | fun build() = LanguagePlugin( 21 | languageHeaderName ?: throw IllegalArgumentException("HeaderName should be contain"), 22 | languageCodeProvider 23 | ?: throw IllegalArgumentException("LanguageCodeProvider should be contain") 24 | ) 25 | } 26 | 27 | companion object Plugin : HttpClientPlugin { 28 | override val key = AttributeKey("LanguagePlugin") 29 | 30 | override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() 31 | 32 | override fun install(plugin: LanguagePlugin, scope: HttpClient) { 33 | scope.requestPipeline.intercept(HttpRequestPipeline.State) { 34 | plugin.languageProvider.getLanguageCode()?.apply { 35 | context.header(plugin.languageHeaderName, this) 36 | } 37 | } 38 | } 39 | } 40 | 41 | interface LanguageCodeProvider { 42 | fun getLanguageCode(): String? 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sample/mpp-library/src/allOf.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: API 4 | version: v1 5 | paths: 6 | /pets: 7 | patch: 8 | requestBody: 9 | content: 10 | application/json: 11 | schema: 12 | oneOf: 13 | - $ref: '#/components/schemas/Cat' 14 | - $ref: '#/components/schemas/Dog' 15 | discriminator: 16 | propertyName: pet_type 17 | responses: 18 | '200': 19 | description: Updated 20 | content: 21 | application/json: 22 | schema: 23 | $ref: '#/components/schemas/Dog' 24 | components: 25 | schemas: 26 | Pet: 27 | type: object 28 | required: 29 | - pet_type 30 | properties: 31 | pet_type: 32 | type: string 33 | discriminator: 34 | propertyName: pet_type 35 | Dog: # "Dog" is a value for the pet_type property (the discriminator value) 36 | allOf: # Combines the main `Pet` schema with `Dog`-specific properties 37 | - $ref: '#/components/schemas/Pet' 38 | - type: object 39 | # all other properties specific to a `Dog` 40 | properties: 41 | bark: 42 | type: boolean 43 | breed: 44 | type: string 45 | enum: [Dingo, Husky, Retriever, Shepherd] 46 | Cat: # "Cat" is a value for the pet_type property (the discriminator value) 47 | allOf: # Combines the main `Pet` schema with `Cat`-specific properties 48 | - $ref: '#/components/schemas/Pet' 49 | - type: object 50 | # all other properties specific to a `Cat` 51 | properties: 52 | hunts: 53 | type: boolean 54 | age: 55 | type: integer -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/MultiPlatformNetworkGeneratorDeprecatedPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import org.gradle.api.Plugin 8 | import org.gradle.api.Project 9 | import org.gradle.api.tasks.Delete 10 | import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension 11 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 12 | import org.openapitools.generator.gradle.plugin.OpenApiGeneratorPlugin 13 | 14 | class MultiPlatformNetworkGeneratorDeprecatedPlugin : Plugin { 15 | private val openApiGenerator = OpenApiGeneratorPlugin() 16 | 17 | override fun apply(target: Project) { 18 | openApiGenerator.apply(target) 19 | 20 | val generatedDir = "${target.buildDir}/generate-resources/main" 21 | 22 | target.afterEvaluate { it.setupProject(generatedDir) } 23 | } 24 | 25 | private fun Project.setupProject(generatedDir: String) { 26 | extensions.findByType(KotlinMultiplatformExtension::class.java)?.run { 27 | val sourceSet = sourceSets.getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) 28 | val sources = "$generatedDir/src/main/kotlin" 29 | sourceSet.kotlin.srcDir(sources) 30 | } 31 | 32 | val removeGeneratedCodeTask = 33 | tasks.create("removeGeneratedOpenApiCode", Delete::class.java) { 34 | delete(file(generatedDir)) 35 | } 36 | 37 | tasks.findByName("openApiGenerate")?.let { 38 | it.dependsOn(removeGeneratedCodeTask) 39 | 40 | tasks.findByName("preBuild")?.dependsOn(it) 41 | tasks.findByName("compileKotlinIosX64")?.dependsOn(it) 42 | tasks.findByName("compileKotlinIosArm64")?.dependsOn(it) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /network-errors/src/commonMain/kotlin/dev/icerock/moko/network/errors/NetworkErrorsTexts.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.errors 6 | 7 | import dev.icerock.moko.resources.StringResource 8 | 9 | data class NetworkErrorsTexts( 10 | val networkConnectionErrorText: StringResource = MR.strings.networkConnectionErrorText, 11 | val serializationErrorText: StringResource = MR.strings.serializationErrorText, 12 | val httpNetworkErrorsTexts: HttpNetworkErrorsTexts = HttpNetworkErrorsTexts(), 13 | val sslNetworkErrorsTexts: SSLNetworkErrorsTexts = SSLNetworkErrorsTexts() 14 | ) 15 | 16 | data class HttpNetworkErrorsTexts( 17 | val unauthorizedErrorText: StringResource = MR.strings.unauthorizedErrorText, 18 | val notFoundErrorText: StringResource = MR.strings.notFoundErrorText, 19 | val accessDeniedErrorText: StringResource = MR.strings.accessDeniedErrorText, 20 | val internalServerErrorText: StringResource = MR.strings.internalServerErrorText 21 | ) 22 | 23 | data class SSLNetworkErrorsTexts( 24 | val secureConnectionFailed: StringResource = MR.strings.secureConnectionFailedText, 25 | val serverCertificateHasBadDate: StringResource = MR.strings.serverCertificateHasBadDateText, 26 | val serverCertificateUntrusted: StringResource = MR.strings.serverCertificateUntrustedText, 27 | val serverCertificateHasUnknownRoot: StringResource = MR.strings.serverCertificateHasUnknownRootText, 28 | val serverCertificateNotYetValid: StringResource = MR.strings.serverCertificateNotYetValidText, 29 | val clientCertificateRejected: StringResource = MR.strings.clientCertificateRejectedText, 30 | val clientCertificateRequired: StringResource = MR.strings.clientCertificateRequiredText, 31 | val cannotLoadFromNetwork: StringResource = MR.strings.cannotLoadFromNetworkText 32 | ) 33 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/HttpExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import dev.icerock.moko.network.exceptions.ResponseException 8 | import io.ktor.client.HttpClient 9 | import io.ktor.client.call.ReceivePipelineException 10 | import io.ktor.client.call.body 11 | import io.ktor.client.request.request 12 | import io.ktor.client.request.setBody 13 | import io.ktor.client.request.url 14 | import io.ktor.client.utils.EmptyContent 15 | import io.ktor.http.ContentType 16 | import io.ktor.http.Headers 17 | import io.ktor.http.HttpMethod 18 | import io.ktor.http.contentType 19 | 20 | operator fun Headers.plus(other: Headers): Headers = when { 21 | this.isEmpty() -> other 22 | other.isEmpty() -> this 23 | else -> Headers.build { 24 | appendAll(this@plus) 25 | appendAll(other) 26 | } 27 | } 28 | 29 | suspend inline fun HttpClient.createRequest( 30 | path: String, 31 | methodType: HttpMethod = HttpMethod.Get, 32 | body: Any = EmptyContent, 33 | contentType: ContentType? = null 34 | ): Value { 35 | @Suppress("SwallowedException") 36 | try { 37 | return request { 38 | method = methodType 39 | url(path) 40 | if (contentType != null) contentType(contentType) 41 | setBody(body) 42 | }.body() 43 | } catch (e: ReceivePipelineException) { 44 | if (e.cause is ResponseException) { 45 | throw e.cause 46 | } else { 47 | throw e 48 | } 49 | } 50 | } 51 | 52 | suspend inline fun HttpClient.createJsonRequest( 53 | path: String, 54 | methodType: HttpMethod = HttpMethod.Get, 55 | body: Any = EmptyContent 56 | ): Value = 57 | createRequest(path, methodType, body, ContentType.Application.Json) 58 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/safeable/SafeableSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.safeable 6 | 7 | import kotlinx.serialization.KSerializer 8 | import kotlinx.serialization.SerializationException 9 | import kotlinx.serialization.descriptors.SerialDescriptor 10 | import kotlinx.serialization.descriptors.buildClassSerialDescriptor 11 | import kotlinx.serialization.encoding.Decoder 12 | import kotlinx.serialization.encoding.Encoder 13 | import kotlin.native.concurrent.ThreadLocal 14 | 15 | class SafeableSerializer( 16 | tSerializer: KSerializer 17 | ) : KSerializer> { 18 | private val typeSerializer = tSerializer 19 | 20 | override val descriptor: SerialDescriptor = buildClassSerialDescriptor( 21 | serialName = "dev.icerock.moko.network.safeable.Safeable", 22 | typeSerializer.descriptor 23 | ) { } 24 | 25 | override fun deserialize(decoder: Decoder): Safeable { 26 | return try { 27 | val result = typeSerializer.deserialize(decoder) 28 | Safeable(result) 29 | } catch (cause: SerializationException) { 30 | val handler = deserializeExceptionHandler ?: return Safeable(null) 31 | 32 | if (handler(cause)) { 33 | Safeable(null) 34 | } else { 35 | throw cause 36 | } 37 | } 38 | } 39 | 40 | override fun serialize(encoder: Encoder, value: Safeable) { 41 | if (value.value == null) { 42 | throw SerializationException("Can't encode Safeable with null value") 43 | } 44 | 45 | typeSerializer.serialize(encoder, value.value) 46 | } 47 | 48 | @ThreadLocal 49 | companion object { 50 | var deserializeExceptionHandler: ((SerializationException) -> Boolean)? = null 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Do’s and Don’ts 2 | 3 | * **Search tickets before you file a new one.** Add to tickets if you have new information about the issue. 4 | * **Keep tickets short but sweet.** Make sure you include all the context needed to solve the issue. Don't overdo it. Great tickets allow us to focus on solving problems instead of discussing them. 5 | * **Take care of your ticket.** When you spend time to report a ticket with care we'll enjoy fixing it for you. 6 | * **Use [GitHub-flavored Markdown](https://help.github.com/articles/markdown-basics/).** Especially put code blocks and console outputs in backticks (```` ``` ````). That increases the readability. Bonus points for applying the appropriate syntax highlighting. 7 | 8 | ## Bug Reports 9 | 10 | In short, since you are most likely a developer, provide a ticket that you _yourself_ would _like_ to receive. 11 | 12 | First check if you are using the latest library version and Kotlin version before filing a ticket. 13 | 14 | Please include steps to reproduce and _all_ other relevant information, including any other relevant dependency and version information. 15 | 16 | ## Feature Requests 17 | 18 | Please try to be precise about the proposed outcome of the feature and how it 19 | would related to existing features. 20 | 21 | 22 | ## Pull Requests 23 | 24 | We **love** pull requests! 25 | 26 | All contributions _will_ be licensed under the Apache 2 license. 27 | 28 | Code/comments should adhere to the following rules: 29 | 30 | * Names should be descriptive and concise. 31 | * Use four spaces and no tabs. 32 | * Remember that source code usually gets written once and read often: ensure 33 | the reader doesn't have to make guesses. Make sure that the purpose and inner 34 | logic are either obvious to a reasonably skilled professional, or add a 35 | comment that explains it. 36 | * Please add a detailed description. 37 | 38 | If you consistently contribute improvements and/or bug fixes, we're happy to make you a maintainer. -------------------------------------------------------------------------------- /sample/form-data-binary-server/src/main/kotlin/com/icerockdev/server/Application.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package com.icerockdev.server 6 | 7 | import io.ktor.http.content.PartData 8 | import io.ktor.http.content.forEachPart 9 | import io.ktor.http.content.streamProvider 10 | import io.ktor.server.application.Application 11 | import io.ktor.server.application.call 12 | import io.ktor.server.request.receiveMultipart 13 | import io.ktor.server.response.respondText 14 | import io.ktor.server.routing.post 15 | import io.ktor.server.routing.routing 16 | import java.io.File 17 | import kotlin.collections.mutableMapOf 18 | import kotlin.collections.set 19 | 20 | fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) 21 | 22 | fun Application.module() { 23 | routing { 24 | post("/v1/auth/signup") { 25 | val map = mutableMapOf() 26 | val multipartData = call.receiveMultipart() 27 | 28 | multipartData.forEachPart { part -> 29 | if (part is PartData.FormItem) { 30 | map[part.name] = part.value 31 | } 32 | 33 | if (part is PartData.FileItem) { 34 | writePartFile(part) 35 | } 36 | 37 | part.dispose() 38 | } 39 | 40 | call.respondText(successResponse) 41 | } 42 | } 43 | } 44 | 45 | private fun writePartFile(part: PartData.FileItem) { 46 | val fileName = part.originalFileName as String 47 | val fileBytes = part.streamProvider().readBytes() 48 | val dir = File("uploads") 49 | dir.mkdir() 50 | File(dir, "$fileName.jpg").writeBytes(fileBytes) 51 | } 52 | 53 | val successResponse = """ 54 | { 55 | "status": 200, 56 | "message": "avatar uploaded", 57 | "timestamp": 123.0, 58 | "success": true 59 | } 60 | """.trimIndent() -------------------------------------------------------------------------------- /network/src/iosMain/kotlin/dev/icerock/moko/network/isSSLException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | @file:Suppress("Filename") 6 | 7 | package dev.icerock.moko.network 8 | 9 | import platform.Foundation.NSError 10 | import platform.Foundation.NSURLErrorCannotLoadFromNetwork 11 | import platform.Foundation.NSURLErrorClientCertificateRequired 12 | import platform.Foundation.NSURLErrorDomain 13 | import platform.Foundation.NSURLErrorSecureConnectionFailed 14 | import platform.Foundation.NSURLErrorServerCertificateHasBadDate 15 | import platform.Foundation.NSURLErrorServerCertificateHasUnknownRoot 16 | import platform.Foundation.NSURLErrorServerCertificateNotYetValid 17 | import platform.Foundation.NSURLErrorServerCertificateUntrusted 18 | 19 | private val sslKeys = mapOf( 20 | NSURLErrorSecureConnectionFailed to SSLExceptionType.SecureConnectionFailed, 21 | NSURLErrorServerCertificateHasBadDate to SSLExceptionType.ServerCertificateHasBadDate, 22 | NSURLErrorServerCertificateUntrusted to SSLExceptionType.ServerCertificateUntrusted, 23 | NSURLErrorServerCertificateHasUnknownRoot to SSLExceptionType.ServerCertificateHasUnknownRoot, 24 | NSURLErrorServerCertificateNotYetValid to SSLExceptionType.ServerCertificateNotYetValid, 25 | NSURLErrorClientCertificateRequired to SSLExceptionType.ClientCertificateRequired, 26 | NSURLErrorCannotLoadFromNetwork to SSLExceptionType.CannotLoadFromNetwork 27 | ) 28 | 29 | actual fun Throwable.isSSLException(): Boolean { 30 | val nsError: NSError = ThrowableToNSErrorMapper(this) ?: return false 31 | 32 | return nsError.domain == NSURLErrorDomain && sslKeys.keys.contains(nsError.code) 33 | } 34 | 35 | @Suppress("ReturnCount") 36 | actual fun Throwable.getSSLExceptionType(): SSLExceptionType? { 37 | val nsError: NSError = ThrowableToNSErrorMapper(this) ?: return null 38 | if (nsError.domain != NSURLErrorDomain) return null 39 | 40 | return sslKeys[nsError.code] 41 | } 42 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/plugins/ExceptionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.plugins 6 | 7 | import dev.icerock.moko.network.exceptionfactory.ExceptionFactory 8 | import io.ktor.client.HttpClient 9 | import io.ktor.client.plugins.HttpClientPlugin 10 | import io.ktor.client.plugins.HttpSend 11 | import io.ktor.client.plugins.plugin 12 | import io.ktor.client.statement.bodyAsChannel 13 | import io.ktor.http.isSuccess 14 | import io.ktor.util.AttributeKey 15 | import io.ktor.utils.io.charsets.Charset 16 | import io.ktor.utils.io.core.readText 17 | 18 | class ExceptionPlugin(private val exceptionFactory: ExceptionFactory) { 19 | 20 | class Config { 21 | var exceptionFactory: ExceptionFactory? = null 22 | fun build() = ExceptionPlugin( 23 | exceptionFactory 24 | ?: throw IllegalArgumentException("Exception factory should be contain") 25 | ) 26 | } 27 | 28 | companion object Plugin : HttpClientPlugin { 29 | 30 | override val key = AttributeKey("ExceptionPlugin") 31 | 32 | override fun prepare(block: Config.() -> Unit) = Config().apply(block).build() 33 | 34 | override fun install(plugin: ExceptionPlugin, scope: HttpClient) { 35 | scope.plugin(HttpSend).intercept { request -> 36 | val call = execute(request) 37 | if (!call.response.status.isSuccess()) { 38 | val packet = call.response.bodyAsChannel().readRemaining() 39 | val responseString = packet.readText(charset = Charset.forName("UTF-8")) 40 | throw plugin.exceptionFactory.createException( 41 | request = call.request, 42 | response = call.response, 43 | responseBody = responseString 44 | ) 45 | } 46 | call 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sample/ios-app/src/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | moko-network 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(BUNDLE_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSMainStoryboardFile 33 | Main 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIRequiresFullScreen 43 | 44 | UIStatusBarHidden 45 | 46 | UIStatusBarHidden~ipad 47 | 48 | UIStatusBarStyle 49 | UIStatusBarStyleLightContent 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationLandscapeRight 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationPortraitUpsideDown 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /sample/mpp-library/src/commonTest/kotlin/AnyTypeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import dev.icerock.moko.network.nullable.asNullable 6 | import io.ktor.client.engine.mock.MockRequestHandler 7 | import io.ktor.client.engine.mock.respondOk 8 | import kotlinx.coroutines.runBlocking 9 | import kotlinx.serialization.json.Json 10 | import kotlinx.serialization.json.JsonNull 11 | import kotlinx.serialization.json.JsonPrimitive 12 | import kotlinx.serialization.json.buildJsonObject 13 | import kotlinx.serialization.json.put 14 | import openapi.anyType.apis.DefaultApi 15 | import openapi.anyType.models.Resp 16 | import kotlin.test.Test 17 | import kotlin.test.assertEquals 18 | 19 | class AnyTypeTest { 20 | @Test 21 | fun `any type in response`() { 22 | val api = createApi { 23 | respondOk( 24 | """ 25 | { 26 | "anyProp": "test", 27 | "anyList": [ 28 | "test2", 29 | 3, 30 | { 31 | "name": "none" 32 | }, 33 | null 34 | ] 35 | } 36 | """.trimIndent() 37 | ) 38 | } 39 | 40 | val result = runBlocking { 41 | api.dynamicGet() 42 | } 43 | 44 | assertEquals( 45 | expected = Resp( 46 | anyProp = JsonPrimitive("test").asNullable(), 47 | anyList = listOf( 48 | JsonPrimitive("test2"), 49 | JsonPrimitive(3), 50 | buildJsonObject { 51 | put("name", "none") 52 | }, 53 | JsonNull 54 | ) 55 | ), 56 | actual = result 57 | ) 58 | } 59 | 60 | private fun createApi(mock: MockRequestHandler): DefaultApi { 61 | val json = Json.Default 62 | val httpClient = createMockClient(json, mock) 63 | return DefaultApi( 64 | basePath = "https://localhost", 65 | httpClient = httpClient, 66 | json = json 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /network-generator/src/main/kotlin/dev/icerock/moko/network/SchemaContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network 6 | 7 | import io.swagger.v3.oas.models.Operation 8 | import io.swagger.v3.oas.models.PathItem 9 | import io.swagger.v3.oas.models.media.MediaType 10 | import io.swagger.v3.oas.models.parameters.Parameter 11 | import io.swagger.v3.oas.models.parameters.RequestBody 12 | import io.swagger.v3.oas.models.responses.ApiResponse 13 | 14 | sealed class SchemaContext { 15 | data class OperationResponse( 16 | val pathName: String, 17 | val pathItem: PathItem, 18 | val method: PathItem.HttpMethod, 19 | val operation: Operation, 20 | val responseName: String, 21 | val response: ApiResponse, 22 | val contentName: String, 23 | val mediaType: MediaType 24 | ) : SchemaContext() 25 | 26 | data class Response( 27 | val responseName: String, 28 | val response: ApiResponse, 29 | val contentName: String, 30 | val mediaType: MediaType 31 | ) : SchemaContext() 32 | 33 | data class OperationRequest( 34 | val pathName: String, 35 | val pathItem: PathItem, 36 | val method: PathItem.HttpMethod, 37 | val operation: Operation, 38 | val requestBody: RequestBody, 39 | val contentName: String, 40 | val mediaType: MediaType 41 | ) : SchemaContext() 42 | 43 | data class Request( 44 | val requestName: String, 45 | val requestBody: RequestBody, 46 | val contentName: String, 47 | val mediaType: MediaType 48 | ) : SchemaContext() 49 | 50 | data class ParameterComponent( 51 | val parameterName: String, 52 | val parameter: Parameter 53 | ) : SchemaContext() 54 | 55 | data class SchemaComponent( 56 | val schemaName: String 57 | ) : SchemaContext() 58 | 59 | data class PropertyComponent( 60 | val schemaName: String?, 61 | val propertyName: String 62 | ) : SchemaContext() 63 | 64 | data class Child( 65 | val parent: SchemaContext, 66 | val child: SchemaContext 67 | ) : SchemaContext() 68 | } 69 | -------------------------------------------------------------------------------- /sample/mpp-library/MultiPlatformLibrary.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'MultiPlatformLibrary' 3 | spec.version = '0.1.0' 4 | spec.homepage = 'Link to a Kotlin/Native module homepage' 5 | spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" } 6 | spec.authors = 'IceRock Development' 7 | spec.license = '' 8 | spec.summary = 'Shared code between iOS and Android' 9 | 10 | spec.vendored_frameworks = "build/cocoapods/framework/#{spec.name}.framework" 11 | spec.libraries = "c++" 12 | spec.module_name = "#{spec.name}_umbrella" 13 | 14 | spec.ios.deployment_target = '11.0' 15 | spec.osx.deployment_target = '10.6' 16 | 17 | spec.pod_target_xcconfig = { 18 | 'KOTLIN_FRAMEWORK_BUILD_TYPE[config=*ebug]' => 'debug', 19 | 'KOTLIN_FRAMEWORK_BUILD_TYPE[config=*elease]' => 'release', 20 | 'CURENT_SDK[sdk=iphoneos*]' => 'iphoneos', 21 | 'CURENT_SDK[sdk=iphonesimulator*]' => 'iphonesimulator', 22 | 'CURENT_SDK[sdk=macosx*]' => 'macos' 23 | } 24 | 25 | spec.script_phases = [ 26 | { 27 | :name => 'Compile Kotlin/Native', 28 | :execution_position => :before_compile, 29 | :shell_path => '/bin/sh', 30 | :script => <<-SCRIPT 31 | if [ "$KOTLIN_FRAMEWORK_BUILD_TYPE" == "debug" ]; then 32 | CONFIG="Debug" 33 | else 34 | CONFIG="Release" 35 | fi 36 | 37 | if [ "$CURENT_SDK" == "iphoneos" ]; then 38 | TARGET="Ios" 39 | ARCH="Arm64" 40 | elif [ "$CURENT_SDK" == "macos" ]; then 41 | TARGET="Macos" 42 | if [ "$NATIVE_ARCH" == "arm64" ]; then 43 | ARCH="Arm64" 44 | else 45 | ARCH="X64" 46 | fi 47 | else 48 | if [ "$NATIVE_ARCH" == "arm64" ]; then 49 | TARGET="IosSimulator" 50 | ARCH="Arm64" 51 | else 52 | TARGET="Ios" 53 | ARCH="X64" 54 | fi 55 | fi 56 | 57 | MPP_PROJECT_ROOT="$SRCROOT/../../mpp-library" 58 | GRADLE_TASK="syncMultiPlatformLibrary${CONFIG}Framework${TARGET}${ARCH}" 59 | 60 | "$MPP_PROJECT_ROOT/../gradlew" -p "$MPP_PROJECT_ROOT" "$GRADLE_TASK" 61 | SCRIPT 62 | } 63 | ] 64 | end 65 | -------------------------------------------------------------------------------- /network/src/commonTest/kotlin/TokenFeatureTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | import dev.icerock.moko.network.plugins.TokenPlugin 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.engine.mock.MockEngine 8 | import io.ktor.client.engine.mock.MockRequestHandler 9 | import io.ktor.client.engine.mock.respondBadRequest 10 | import io.ktor.client.engine.mock.respondOk 11 | import io.ktor.client.request.get 12 | import io.ktor.http.HttpStatusCode 13 | import kotlinx.coroutines.runBlocking 14 | import kotlin.test.Test 15 | import kotlin.test.assertEquals 16 | 17 | class TokenFeatureTest { 18 | @Test 19 | fun `token added when exist`() { 20 | val client = createMockClient( 21 | tokenProvider = { "mytoken" } 22 | ) { request -> 23 | if (request.headers[AUTH_HEADER_NAME] == "mytoken") respondOk() 24 | else respondBadRequest() 25 | } 26 | 27 | val result = runBlocking { 28 | client.get("localhost") 29 | } 30 | 31 | assertEquals(expected = HttpStatusCode.OK, actual = result.status) 32 | } 33 | 34 | @Test 35 | fun `token not added when not exist`() { 36 | val client = createMockClient( 37 | tokenProvider = { null } 38 | ) { request -> 39 | if (request.headers.contains(AUTH_HEADER_NAME).not()) respondOk() 40 | else respondBadRequest() 41 | } 42 | 43 | val result = runBlocking { 44 | client.get("localhost") 45 | } 46 | 47 | assertEquals(expected = HttpStatusCode.OK, actual = result.status) 48 | } 49 | 50 | private fun createMockClient( 51 | tokenProvider: TokenPlugin.TokenProvider, 52 | handler: MockRequestHandler 53 | ): HttpClient { 54 | return HttpClient(MockEngine) { 55 | engine { 56 | addHandler(handler) 57 | } 58 | 59 | install(TokenPlugin) { 60 | this.tokenHeaderName = AUTH_HEADER_NAME 61 | this.tokenProvider = tokenProvider 62 | } 63 | } 64 | } 65 | 66 | private companion object { 67 | const val AUTH_HEADER_NAME = "Auth" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /network/src/commonMain/kotlin/dev/icerock/moko/network/exceptionfactory/parser/ErrorExceptionParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | package dev.icerock.moko.network.exceptionfactory.parser 6 | 7 | import dev.icerock.moko.network.exceptionfactory.HttpExceptionFactory 8 | import dev.icerock.moko.network.exceptions.ErrorException 9 | import dev.icerock.moko.network.exceptions.ResponseException 10 | import io.ktor.client.request.HttpRequest 11 | import io.ktor.client.statement.HttpResponse 12 | import kotlinx.serialization.json.Json 13 | import kotlinx.serialization.json.contentOrNull 14 | import kotlinx.serialization.json.intOrNull 15 | import kotlinx.serialization.json.jsonObject 16 | import kotlinx.serialization.json.jsonPrimitive 17 | 18 | class ErrorExceptionParser(private val json: Json) : HttpExceptionFactory.HttpExceptionParser { 19 | 20 | override fun parseException( 21 | request: HttpRequest, 22 | response: HttpResponse, 23 | responseBody: String? 24 | ): ResponseException? { 25 | @Suppress("TooGenericExceptionCaught", "SwallowedException") 26 | try { 27 | val body = responseBody.orEmpty() 28 | val jsonRoot = json.parseToJsonElement(body) 29 | var jsonObject = jsonRoot.jsonObject 30 | if (jsonObject.containsKey(JSON_ERROR_KEY)) { 31 | jsonObject = jsonObject.getValue(JSON_ERROR_KEY).jsonObject 32 | } 33 | 34 | var message: String? = null 35 | var code = response.status.value 36 | 37 | if (jsonObject.containsKey(JSON_MESSAGE_KEY)) { 38 | message = jsonObject[JSON_MESSAGE_KEY]?.jsonPrimitive?.contentOrNull 39 | } 40 | if (jsonObject.containsKey(JSON_CODE_KEY)) { 41 | val newCode = jsonObject[JSON_CODE_KEY]?.jsonPrimitive?.intOrNull 42 | if (newCode != null) { 43 | code = newCode 44 | } 45 | } 46 | return ErrorException(request, response, code, message) 47 | } catch (e: Exception) { 48 | return null 49 | } 50 | } 51 | 52 | companion object { 53 | private const val JSON_MESSAGE_KEY = "message" 54 | private const val JSON_CODE_KEY = "code" 55 | private const val JSON_ERROR_KEY = "error" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sample/android-app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 17 | 18 | 24 | 25 | 31 | 32 | 36 | 37 |