Giter VIP home page Giter VIP logo

moko-network's Introduction

moko-network
GitHub license Download kotlin-version

Mobile Kotlin network components

This is a Kotlin MultiPlatform library that provide network components for iOS & Android. Library is addition to ktor-client with gradle plugin to generate entities and api classes from OpenAPI (Swagger) specification file. Entities serialization done by kotlinx.serialization.

Table of Contents

Features

  • OpenAPI client code generation - just configure plugin then use generated entities and api classes;
  • TokenFeature feature to ktor-client with auth token header support;
  • ExceptionFeature feature to ktor-client that parse errors from server to domain exceptions.
  • RefreshTokenFeature feature to ktor-client that handle Unauthorized response from server, try to update token and repeat failed request in case, when token update was successful;

Requirements

  • Gradle version 6.8+
  • Android API 16+
  • iOS version 11.0+

Installation

root build.gradle

buildscript {
    repositories {
        gradlePluginPortal()
    }

    dependencies {
        classpath "dev.icerock.moko:network-generator:0.21.2"
    }
}


allprojects {
    repositories {
        mavenCentral()
    }
}

project build.gradle

apply plugin: "dev.icerock.mobile.multiplatform-network-generator"

dependencies {
    commonMainApi("dev.icerock.moko:network:0.21.2")
    commonMainApi("dev.icerock.moko:network-engine:0.21.2") // configured HttpClientEngine
    commonMainApi("dev.icerock.moko:network-bignum:0.21.2") // kbignum serializer
    commonMainApi("dev.icerock.moko:network-errors:0.21.2") // moko-errors integration
}

Usage

  1. E.g. put an OpenAPI Specification file to src/swagger.json path of the project Gradle module.

  2. Setup the project build.gradle:

mokoNetwork {
    spec("pets") {
        inputSpec = file("src/swagger.json")
    }
    spec("news") {
        inputSpec = file("src/newsApi.yaml")
        packageName = "news"
        isInternal = false
        isOpen = true
        configureTask {
            // here can be configuration of https://github.com/OpenAPITools/openapi-generator GenerateTask
        }
        enumFallbackNull = false
    }
}
  1. Then run openApiGenerate Gradle task and after completion you will get all generated classes in build/generated/moko-network directory.

  2. To import all generated classes of model put:

import dev.icerock.moko.network.generated.models.*
import dev.icerock.moko.network.generated.apis.*
  1. Then you can use generated API's in your application in the common sourceset:
import dev.icerock.moko.network.generated.apis.*

class TestViewModel : ViewModel() {
    // ..
    
    private val petApi = PetApi(
        basePath = "https://petstore.swagger.io/v2/", // Base API URL
        httpClient = ktorHttpClient, // Reference to Ktor HTTP client object
        json = kotlinxJsonParser // Reference to kotlinx.serialization.json parser object
    )

    fun apiRequest() {
        viewModelScope.launch {
            try {
                val pet = petApi.findPetsByStatus(listOf("available"))

                // ...
            } catch (error: Exception) {
                // ...
            }
        }
    }
}

For the moko-network specification generator, you can enable safe enum properties generation mode. To turn on the mode in build.gradle to a spec block add flag:

enumFallbackNull = true

The enabled mode will generate special wrapper Safeable for all properties with the enum type, that which in a situation for an unexpected enum value will return null.

Deprecated usage

Old way with OpenApi Generator Plugin available by:

apply plugin: "dev.icerock.mobile.multiplatform-network-generator-deprecated"

openApiGenerate {
    inputSpec.set(file("src/swagger.json").path)
    generatorName.set("kotlin-ktor-client")
}

but this way limited to one spec in one time.

moko-network-errors

There is module moko-network-errors for moko-errors library that contains built-in mappers for mapping moko-network exception classes into StringDesc objects (there are built-in resources for text in English and Russian).

To use the moko-network-errors module just call the registerAllNetworkMappers extension of ExceptionMappersStorage object:

fun initExceptionMappersStorage() {
    ExceptionMappersStorage.registerAllNetworkMappers()
}

And then you can use ExceptionHandler to automatically handle exceptions for network requests:

viewModelScope.launch {
    exceptionHandler.handle {
        val pet = petApi.findPetsByStatus(listOf("available"))

        // ...
    }.execute()
}

For more information about exception handling see moko-errors.

Samples

More examples can be found in the sample directory.

Set Up Locally

Contributing

All development (both new features and bug fixes) is performed in develop branch. This way master sources always contain sources of the most recently released version. Please send PRs with bug fixes to develop branch. Fixes to documentation in markdown files are an exception to this rule. They are updated directly in master.

The develop branch is pushed to master during release.

More detailed guide for contributers see in contributing guide.

License

Copyright 2019 IceRock MAG Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

moko-network's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

moko-network's Issues

Failed to notify project evaluation listener.

Adding this to a recent project I've been working on, and encountering the following errors when the Gradle import is occurring:

> Failed to notify project evaluation listener.
   > Cannot change dependencies of dependency configuration ':shared:androidDebugApi' after it has been included in dependency resolution.
   > Cannot change dependencies of dependency configuration ':shared:iosArm64Api' after it has been included in dependency resolution.
   > Cannot change attributes of dependency configuration ':shared:iosArm64ApiElements' after it has been resolved
   > Cannot change dependencies of dependency configuration ':shared:iosX64Api' after it has been included in dependency resolution.
   > Cannot change attributes of dependency configuration ':shared:iosX64ApiElements' after it has been resolved

The top part of the stack trace that gets output is:

* Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':shared'.
	at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:68)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.access$400(LifecycleProjectEvaluator.java:51)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:191)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:105)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:250)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:249)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:721)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:151)
	at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:41)
	at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:69)
	at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:46)
	at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:56)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)
	at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:226)
	at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:163)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:148)
	at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:124)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:67)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:63)
	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:77)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:54)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:53)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:29)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:49)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:32)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:27)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Caused by: org.gradle.internal.event.ListenerNotificationException: Failed to notify project evaluation listener.
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:89)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:346)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:249)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy47.afterEvaluate(Unknown Source)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:183)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:180)
	at org.gradle.api.internal.project.DefaultProject.stepEvaluationListener(DefaultProject.java:1465)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:189)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.lambda$run$0(LifecycleProjectEvaluator.java:105)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$applyToMutableState$0(DefaultProjectStateRegistry.java:250)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.applyToMutableState(DefaultProjectStateRegistry.java:249)
	at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:91)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:63)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:721)
	at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:151)
	at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:41)
	at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:69)
	at org.gradle.configuration.DefaultProjectsPreparer.prepareProjects(DefaultProjectsPreparer.java:46)
	at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:56)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.configuration.BuildOperationFiringProjectsPreparer.prepareProjects(BuildOperationFiringProjectsPreparer.java:40)
	at org.gradle.initialization.DefaultGradleLauncher.prepareProjects(DefaultGradleLauncher.java:226)
	at org.gradle.initialization.DefaultGradleLauncher.doClassicBuildStages(DefaultGradleLauncher.java:163)
	at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:148)
	at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:124)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:67)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:63)
	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:77)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:54)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:53)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:29)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:49)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:32)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:27)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
Cause 1: org.gradle.api.InvalidUserDataException: Cannot change dependencies of dependency configuration ':shared:androidDebugApi' after it has been included in dependency resolution.
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.preventIllegalMutation(DefaultConfiguration.java:1269)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.validateMutation(DefaultConfiguration.java:1229)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.extendsFrom(DefaultConfiguration.java:382)
	at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.doAddConfiguration(DefaultDependencyHandler.java:212)
	at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.doAdd(DefaultDependencyHandler.java:165)
	at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.add(DefaultDependencyHandler.java:120)
	at org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.add(DefaultDependencyHandler.java:114)
	at org.jetbrains.kotlin.gradle.utils.GradleConfigurationUtilsKt.addExtendsFromRelation(gradleConfigurationUtils.kt:13)
	at org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinCompilation.addExactSourceSetsEagerly$kotlin_gradle_plugin(kotlinCompilations.kt:125)
	at org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinCompilation$source$1.invoke(kotlinCompilations.kt:151)
	at org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinCompilation$source$1.invoke(kotlinCompilations.kt:43)
	at org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginKt.whenEvaluated(KotlinMultiplatformPlugin.kt:212)

I suspect I'm probably just not using it right. This is a kotlin gradle script, and I know there can sometimes be differences in how Gradle and Kotlin DSL are implemented. Any ideas?

Cannot generate public classes

I've tried to set nonPublicApi to false as an additional property and a config option value (it should be and additional property for kotlin language) but classes have always internal visibility.

nonPublicApi is set to false by default, but I've tried to force it anyway.

openApiGenerate {
    // https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator-gradle-plugin
    println("--> openapi-generator <-")
    println("additionalProperties.size ${additionalProperties.get().entries.size}")
    println("--additional-properties nonPublicApi=true")
    inputSpec.set(file("src/swagger.json").path)
    //inputSpec.set(file("src/openapi.yaml").path)
    additionalProperties.set(mutableMapOf("nonPublicApi" to "false"))
    configOptions.set(mutableMapOf("nonPublicApi" to "false"))
    generatorName.set("kotlin-ktor-client")
    verbose.set(true)
    println("additionalProperties $additionalProperties")
    println("additionalProperties.size ${additionalProperties.get().entries.size}")
    println("--> openapi-generator <-")
}

internal class PetApi(...

Generates incorrect code for array parameter with uniqueItems set to true

We have a parameter defined as follows where it takes a set of UUIDs:

    {
      "name" : "timeOffRequestIds",
      "in" : "body",
      "required" : true,
      "schema" : {
        "type" : "array",
        "items" : {
          "type" : "string",
          "format" : "uuid"
        },
        "uniqueItems" : true
      }
    }

The parameter to the API is declared as a Set due to the uniqueItems:

 timeOffRequestIds: kotlin.collections.Set<kotlin.String>

But the code to serialize is trying to use ListSerializer:

 _json.encodeToString(ListSerializer(kotlin.String.serializer()), timeOffRequestIds)

That should be using SetSerializer, not ListSerializer

Add nullable mark support

We have case where in specification field of model marked by required and also have nullable: true mark. in this case we should generate optional field, without default value. if required but not have nullable, or nullable: false - field should be generated as not optional.

RefreshTokenFeature try to refresh token twice for parallel requests

If we made two requests with invalid token, first of them will refresh correctly, but second will try to refresh with old refresh token and fire unauth action.

have this solution:

        install(RefreshTokenFeature) {
            updateTokenHandler = {
                // Локально сохраняем текущий токен авторизации
                val currentAuthToken = keyValueStorage.token

                // Блокируем выполнение корутины через мьютекс, чтобы одновременно не
                // отправлялось несколько запросов на обновление токена.
                refreshTokenHttpFeatureMutex.lock()

                // Если локальный токен авторизации currentAuthToken после захода в
                // критическую секцию уже не равен токену в хранилище (был изменен в результате
                // другого запроса), то посылать еще запрос на обновление токена скорее всего
                // не надо, поэтому пытаемся выполнить предыдущий запрос снова.
                if (currentAuthToken != keyValueStorage.token) {
                    refreshTokenHttpFeatureMutex.unlock()
                    true
                }
                // Если же локальный токен равен токену в хранилище, то значит его надо обновить,
                // отправляем запрос на обновление токена.
                else {
                    try {
                        authRepository.updateToken()
                        true
                    } catch (error: Exception) {
                        authRepository.clearUser()
                        profileRepository.clearProfile()
                        false
                    } finally {
                        refreshTokenHttpFeatureMutex.unlock()
                    }
                }
            }
        }

Implement configuration of plugin with multiple specs support

configuration spec should be changed to:

mokoNetwork {
    spec("petstore") {
        inputSpec = file("src/swagger")
        packageName = "com.icerockdev.library"
        isInternal = false
        isOpen = true
        configureTask { // here block that will be called at creating of generate task
            inputSpec.set(file("src/profile_openapi.yaml").path)
            generatorName.set("kotlin-ktor-client")
        }
    }
    spec("profile") {
        inputSpec = file("src/profile_openapi.yaml")
        packageName = "com.icerockdev.library.d2"
        isInternal = false
        isOpen = true
    }
}

inside plugin we should generate task by:

tasks.create("profileOpenApiGenerate", org.openapitools.generator.gradle.plugin.tasks.GenerateTask::class.java) {
    group = "openapi"

    inputSpec.set(file("src/profile_openapi.yaml").path)
    generatorName.set("kotlin-ktor-client")
}

Can't manage 400 response

I have an Api definition with this responses defined:

responses:
        '200':
          description: ok
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MyData'
        '400':
          description: Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequest' 

Everything works fine when I execute the openApiGenerate task but the generated Api class ends up doing something like this to manage results :

try {
      //not primitive type
      val result: String = _httpClient.request(builder)
      return _json.decodeFromString(MyData.serializer().let { ListSerializer(it) }, result)
} catch (pipeline: ReceivePipelineException) {
      throw pipeline.cause
}

This works fine if the result is suscessfull but if I get a BadRequest then it crash at the line _json.decodeFromString(MyData.serializer().let { ListSerializer(it) }, result).

Although my BadRequest class is generated, it is not used anywhere so, how are the errors managed?

GenerateTask's additionalProperties are not set when of type Boolean

OpenApi's GenerateTask additionalProperties are of type Map<String, String>. When Moko plugin tries to set a Boolean property it needs to put it as a String (i.e. "true") which is not evaluated as proper boolean value by mustache template.

This affects function of isOpen attribute that is passed to mustache template as openApiClasses property -> currently the attribute is always true in template (default behavior).

Will do a PR for OpenApi generator project soon. The type of additionalProperties need to be updated to Map<String, Object>

Invalid enum classes are generated by network-generator

For example for that scheme:

    StatusType:
      type: string
      enum:
        - need-action
        - accepted
        - rejected
        - maybe

Generates enum class with errors:

@Serializable
enum class StatusType(override val serialName: String? = null): SerialEnum {

    @SerialName("need-action")
    NEED_MINUS_ACTION,

    @SerialName("accepted")
    ACCEPTED,

    @SerialName("rejected")
    REJECTED,

    @SerialName("maybe")
    MAYBE;

}

Need to remove SerialEnum and property overriding from templates.

Make APIs optionally open to allow create API fakes

We were used to generate Retrofit interfaces from swagger and to make development easier (server is not always available) we created fake implementation of those Retrofit interfaces that serve static hardcoded responses without touching the server.

Now when we generate Api classes based on Ktor it is not possible to sub-class them for stubs anymore.

It would be great to have an optional switch to make both Api classes and all its functions open in api.mustache.

Current class definition: {{#nonPublicApi}}internal {{/nonPublicApi}}class {{classname}}

Suggested class definition: {{#openApi}}open {{/openApi}}{{#nonPublicApi}}internal {{/nonPublicApi}}class {{classname}}

RefreshTokenFeature is not working after kotlin 1.4 update

Feature installation:

            install(RefreshTokenFeature) {
                updateTokenHandler = {
                    println("===== Update token request =====")
                    try {
                        authRepository.updateToken()
                        println("===== Update token request completed =====")
                        true
                    } catch (error: Throwable) {
                        println("===== Update token error: ${error} =====")
                        authRepository.clearCache()
                        unauthorizedAction()
                        false
                    }
                }
                isCredentialsActual = { request ->
                    println("====== CheckCredentials ======")
                    println("====== request: ${request.headers["Authorization"]} ======")
                    println("====== current: ${keyValueStorage.token?.let { "Bearer $it" }} ======")
                    request.headers["Authorization"] == keyValueStorage.token?.let { "Bearer $it" }
                }
            }

authRepository.updateToken() method:

    suspend fun updateToken() {
        println("===== inside Update token request. Refresh: ${keyValueStorage.refreshToken ?: "<empty>"} =====")
        sessionsApi.apiV2SessionsRefreshPost(
            refreshToken = keyValueStorage.refreshToken ?: ""
        ).apply {
            keyValueStorage.token = this.jwt
            keyValueStorage.refreshToken = this.refreshToken
        }
    }

What is going on:

isCredentialsActual is called, all data inside are correct, returned value is correct. Then feature calls updateTokenHandler callback. It is print println("===== Update token request =====") log and println("===== inside Update token request. Refresh: ${keyValueStorage.refreshToken ?: "<empty>"} =====") log.

But there is no any activity after. No refresh request called, no error, no completion. No reaction on other requests of app which I can do manually (like pull to refresh actions).

Tried iOS only. Will try to reproduce on android and add info

Update API generation with openapi-generator (kotlin/multiplatform instead of Kotlin/jvm)

We use OpenAPI generator to generate ktor-client code from openapi. In openapi-generator 4.1.3 was added multiplatform support - OpenAPITools/openapi-generator#3900.
Now we should check differences between openapi-generator code and our. Then if code simmilar, or differences is small - we should move to use openapi-generator kotlin-multiplatform version.

If in generator used old version of ktor-client/serialization/coroutines - we should create fork of openapi-generator, make update and create pull request to openapi-generator. After it moko-network should just simplify multiplatform generation setup, but not add new generator templates.

links: https://openapi-generator.tech/docs/generators/kotlin

Generate interface of Api for tests

interface ProfileApi {
    suspend fun getProfile(profileId: Int): Profile
}

class ProfileApiImpl(...): ProfileApi {
    override suspend fun getProfile(profileId: Int): Profile
}

Allow controlling to which sourceSet(s) the generated code is added

The plugin automatically adds the generated source code as a srcDir to the commonMain sourceSet. For simple projects, that may be the only common sourceSet you have, but we are investigating dividing our code into sourceSets to enforce visibility along clean architecture lines and will not want the code added to the commonMain sourceSet and would like to instead use a different sourceSet. It would be nice to be able to specify that to the plugin.

Our alternative is to generate to a separate module, which is what we currently do.

allOf support

Now for schema

allOf:
                  - $ref: "#/components/schemas/LimitIdResponse"
                  - type: object
                    properties:
                      items:
                        type: array
                        items:
                          $ref: "#/components/schemas/Unit"

we got

@Serializable
public data class LimitIdResponse (
    @SerialName("unpassed_ids")
    val unpassedIds: kotlin.collections.List<kotlin.String>,

    @SerialName("limit")
    val limit: kotlin.Int,
    
    @SerialName("unpassed_external_ids")
    val unpassedExternalIds: kotlin.collections.List<kotlin.String>
) 

output. generator get only first item of allOf.

Incorrect generation for array property

Our API has an entity defined like this in the swagger:

"IgnoredActivityCategories" : {
  "type" : "object",
  "properties" : {
    "values" : {
      "type" : "array",
      "description" : "Activity categories list",
      "items" : {
        "type" : "string",
        "enum" : [ "OnQueueWork", "Break", "Meal", "Meeting", "OffQueueWork", "TimeOff", "Training", "Unavailable", "Unscheduled" ],
        "x-genesys-enum-members" : [ {
          "name" : "OnQueueWork"
        }, {
          "name" : "Break"
        }, {
          "name" : "Meal"
        }, {
          "name" : "Meeting"
        }, {
          "name" : "OffQueueWork"
        }, {
          "name" : "TimeOff"
        }, {
          "name" : "Training"
        }, {
          "name" : "Unavailable"
        }, {
          "name" : "Unscheduled"
        } ]
      }
    }
  }
},

and here is the code that is generated:

@Serializable
public data class IgnoredActivityCategories (
    /* Activity categories list */
    @SerialName("values")
    val values: IgnoredActivityCategories.Values? = null
) {

    /**
    * Activity categories list
    * Values: "OnQueueWork","Break","Meal","Meeting","OffQueueWork","TimeOff","Training","Unavailable","Unscheduled"
    */
    @Serializable
    enum class Values {
    
        @SerialName("OnQueueWork")
        ON_QUEUE_WORK,
    
        @SerialName("Break")
        BREAK,
    
        @SerialName("Meal")
        MEAL,
    
        @SerialName("Meeting")
        MEETING,
    
        @SerialName("OffQueueWork")
        OFF_QUEUE_WORK,
    
        @SerialName("TimeOff")
        TIME_OFF,
    
        @SerialName("Training")
        TRAINING,
    
        @SerialName("Unavailable")
        UNAVAILABLE,
    
        @SerialName("Unscheduled")
        UNSCHEDULED;
    
    }

}

The problem is that the values parameter to this object should be a collection of values, not just a single value

Ktor 1.5.0 support

This library does not build on iOS and break with the error kotlinx.serialization.json/Json|null[0] is not found like Kotlin/kotlinx.serialization#1110

Could you update your library to the latest ktor, serialization, coroutines and kotlin versions ?

Thank you

Syntax errors in generated source files

Hello!
I'm trying to use your moko-network-generator plugin to automatically generate a multi-platform ktor client. If I use the swagger.json from your example in the sample folder, then everything works fine. But if we take another example of the API, then compilation errors may occur when building the client. E. g. if you take a swagger file from petstore.swagger.io, then syntax errors are detected in the generated source files. For example:

  1. There is no import directive for the serializer() extension function, resulting in error "Unresolved reference: serializer".
  2. In some cases, instead of angle brackets containing a type parameter, the generator uses their codes. Instead of List<User> we have List&lt;User&gt;
  3. Instead of grave accent the generator uses its unicode hex character code &#x60
  4. Also in some cases we have errors like this: "Suspend function 'request' should be called only from a coroutine or another suspend function".
  5. Also, when serializing lists, instead of User.serializer().list generator writes List<User>.serializer().

Of course, there is still an option to edit those generated source files and build projects manually. But this is not as convenient as if the entire generation and building procedure would be performed automatically.

ErrorException and ValidationException should have additional information about requests

Now, when error exception throw and nobody not catch it we got in crashlytics this trace:

Fatal Exception: dev.icerock.moko.network.exceptions.ErrorException: Missed server heartbeat
       at dev.icerock.moko.network.exceptionfactory.parser.ErrorExceptionParser.parseException(ErrorExceptionParser.kt:34)
       at moto.domain.HttpExceptionFactory.createException(HttpExceptionFactory.kt:18)
       at dev.icerock.moko.network.features.ExceptionFeature$Feature$install$1.invokeSuspend(ExceptionFeature.kt:43)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)

we should pass additional info (request + response for example) into exception to improve debug experience

Support for oneOf in openapi generator

Need to implement supporting oneOf constructions like this:

User:
  type: object
  properties:
    birth_date:
      oneOf:
      - $ref: "#/components/schemas/UserBirthday"
      - description: Дата рождения
          type: string
          format: date

Filtering path operations by tags

Need to implement filtration path operations by tags.

For example I have this path:

  '/api/request':
    get:
      operationId: api_request_get
      description: ''
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Response'
      tags:
        - api
    delete:
      operationId: api_request_delete
      description: ''
      responses:
        '204':
          description: ''
      tags:
        - internal
        - api

And sometimes I need generate api without operations with tag internal. In this example, I don't need delete method in the api class, so I want to filter it by the internal tag

Add support to filter generated api by tags

for example in some api specification contained tag internal that only for internal backend usage, so we should not generate api for this tag. required ability to declare list of excluded tags for generation

Moving to maven central

I assume like moko-resources you need to move to maven central since bintray is going away.

When you do can you push versions back to 0.10.0 there? We are having trouble updating to issues with upgrading to Kotlin 1.4.31

Add support for decimal string format to generator

Now if openapi config has property like this:

amount:
  title: Amount
  type: string
  format: decimal

Then the class with this property will be generated with error:

@SerialName("amount")
val amount: java.math.BigDecimal // can't import java in common code

RefreshTokenFeature request loop

After token refreshed, RefreshTokenFeature retry failed request with new token. But instead of using new token, it adds new token to the end of old token. In result we have:

Authorization: Bearer oldToken; Bearer newToken
instead of:
Authorization: Bearer newToken

This causes an infinite loop of retry request

install(TokenFeature) {
                tokenHeaderName = "Authorization"
                tokenProvider = object : TokenFeature.TokenProvider {
                    override fun getToken(): String? = keyValueStorage.token?.let {
                        "Bearer $it"
                    }
                }
            }
install(RefreshTokenFeature) {
              updateTokenHandler = {
                    try {
                        authRepository.updateToken()
                        true
                    } catch (error: Exception) {
                        authRepository.clearUser()
                        profileRepository.clearProfile()
                        false
                    }
                }
            }

Map "object" property without internal properties to JsonObject type

this:

UserNotification:
  type: object
  properties:
    id:
      title: ID
      type: integer
      readOnly: true
    content:
      type: object

should be:

@Serializable
public data class UserNotification (
    @SerialName("id")
    val id: kotlin.Int? = null,
    @SerialName("content")
    val content: JsonObject? = null
)

Date transformer make crash with incorrect format

There are two expect methods for GMTDate:

expect fun String.toDate(format: String): GMTDate
expect fun GMTDate.toString(format: String): String

Both of them have non-optional result. On iOS side there is cruel force unwrap here:

val date: NSDate = formatter.dateFromString(this)!!

It will provide crash any time, when format of provided string argument not match format of current string.
i.e. try to parse string "13.12.2020" with format "dd.MM.YYYY'T'mm:ss". Maybe it would be better to make it optional? I really don't want crashes if backend change some symbols =(

Generate multiple APIs on the same module

I'm trying to generate classes from two different API specifications but it seems that I can only use openApiGenerate once for every module.

I've also found this issue in moko template, in which the same problem was reported a while ago. Has this been fixed? Is there any workaround now?

Map response type generated as primitive

We have a call that returns a result type of:

        "schema" : {
          "type" : "object",
          "additionalProperties" : {
            "type" : "boolean"
          }
        }

which is essentially a Json object with values of Boolean. The API call is generated with a correct return type of kotlin.collections.Map<kotlin.String, kotlin.Boolean>, but the internal deserialization does not work. Here is what it generates:

    try {
        //primitive type
        return _httpClient.request(builder)
    } catch (pipeline: ReceivePipelineException) {
        throw pipeline.cause
    }

So it is treating the Map as a primitive type and not doing any special deserialization. When ran you get an error

E/HttpClient: Failure(io.ktor.client.call.NoTransformationFoundException: No transformation found: class io.ktor.utils.io.ByteBufferChannel -> class kotlin.collections.Map

Note that this is with version 0.10.0 as we have been unable to upgrade to the latest as it requires upgrading to Kotlin 1.4.31 and that is causing us problems.

I notice that you have recently added code to handle map deserialization here but I don't think that would help here as the template is going to the case where it thinks the return type is primitive.

Api generation error for nullable enum types

If spec contains nullable enum type and one of items is null it will fail generation:

layout_name:
              title: Название шаблона отображения
              type: string
              enum:
                - null
                - layout_1
                - layout_2
                - layout_3
              nullable: true

For generation should remove - null item for success generation:

layout_name:
              title: Название шаблона отображения
              type: string
              enum:
                - layout_1
                - layout_2
                - layout_3
              nullable: true

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.