From fbfe55af1922709fc315f017bc3c8129aec4b0df Mon Sep 17 00:00:00 2001 From: Simon Vergauwen Date: Thu, 4 Jul 2024 11:43:55 +0200 Subject: [PATCH] BUGFIX: K1 vs K2 AutoCloseable incompatibility (#52) --- example/.gitignore | 1 + example/build.gradle.kts | 39 +++++++++++++++++++ .../kotlin/io/github/nomisrev/example/test.kt | 26 ++++++------- .../kotlin/io/github/nomisrev/openapi/APIs.kt | 11 ++++-- .../kotlin/io/github/nomisrev/openapi/Main.kt | 16 ++------ .../github/nomisrev/openapi/OpenAPIContext.kt | 10 +++-- .../io/github/nomisrev/openapi/OpenAPI.kt | 9 +---- .../openapi/plugin/GenerateClientAction.kt | 5 ++- .../openapi/plugin/GenerateClientTask.kt | 6 +++ 9 files changed, 80 insertions(+), 43 deletions(-) diff --git a/example/.gitignore b/example/.gitignore index 550588b..c82ab10 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -216,5 +216,6 @@ gradle-app.setting #Kotlin .kotlin +kotlin-js-store # End of https://www.toptal.com/developers/gitignore/api/macos,windows,gradle,kotlin,java,intellij+all \ No newline at end of file diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 4efdb60..fe334b8 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -10,6 +10,15 @@ openApiConfig { spec("OpenAI", file("openai.yaml")) { kotlin { jvm() + js { + browser() + nodejs() + } + iosArm64() + macosArm64() + linuxX64() + mingwX64() + sourceSets { commonMain { dependencies { @@ -19,5 +28,35 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } } + val jvmMain by getting { + dependencies { + api("io.ktor:ktor-client-cio:2.3.6") + } + } + val jsMain by getting { + dependencies { + api("io.ktor:ktor-client-js:2.3.6") + } + } + val iosArm64Main by getting { + dependencies { + implementation("io.ktor:ktor-client-cio:2.3.6") + } + } + val linuxX64Main by getting { + dependencies { + implementation("io.ktor:ktor-client-cio:2.3.6") + } + } + val macosArm64Main by getting { + dependencies { + implementation("io.ktor:ktor-client-cio:2.3.6") + } + } + val mingwX64Main by getting { + dependencies { + implementation("io.ktor:ktor-client-winhttp:2.3.6") + } + } } } diff --git a/example/src/commonMain/kotlin/io/github/nomisrev/example/test.kt b/example/src/commonMain/kotlin/io/github/nomisrev/example/test.kt index 73a45fc..a90a491 100644 --- a/example/src/commonMain/kotlin/io/github/nomisrev/example/test.kt +++ b/example/src/commonMain/kotlin/io/github/nomisrev/example/test.kt @@ -8,12 +8,13 @@ import io.github.nomisrev.openai.OpenAI import io.ktor.client.* import io.ktor.client.plugins.* import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json suspend fun main() { - val ai = OpenAI(configuredClient()) + val ai = OpenAI(configuredClient("MY_API_KEY")) ai.chat.completions.createChatCompletion( CreateChatCompletionRequest( listOf( @@ -26,28 +27,23 @@ suspend fun main() { ) } -private fun configuredClient(): HttpClient = HttpClient { +private fun configuredClient( + token: String, + org: String? = null +): HttpClient = HttpClient { + defaultRequest { + url("https://api.openai.com/v1/") + org?.let { headers.append("OpenAI-Organization", it) } + bearerAuth(token) + } install(ContentNegotiation) { json( Json { ignoreUnknownKeys = true - prettyPrint = false isLenient = true @Suppress("OPT_IN_USAGE") explicitNulls = false - classDiscriminator = "_type_" } ) } - install(HttpTimeout) { - requestTimeoutMillis = 45_000 - connectTimeoutMillis = 45_000 - socketTimeoutMillis = 45_000 - } - install(HttpRequestRetry) { - maxRetries = 5 - retryIf { _, response -> !response.status.isSuccess() } - retryOnExceptionIf { _, _ -> true } - delayMillis { retry -> retry * 1000L } - } } diff --git a/generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt b/generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt index 1e8b204..9d2dd95 100644 --- a/generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt +++ b/generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt @@ -11,12 +11,12 @@ import com.squareup.kotlinpoet.MemberSpecHolder import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.TypeSpecHolder -import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.withIndent import io.github.nomisrev.openapi.NamingContext.Named import io.github.nomisrev.openapi.NamingContext.Nested import io.ktor.http.* +import io.ktor.utils.io.core.* fun configure(defaults: Boolean) = ParameterSpec( @@ -63,12 +63,17 @@ private fun Root.addInterface() { } val type = TypeSpec.interfaceBuilder(className()) - .addSuperinterface(AutoCloseable::class) + .addSuperinterface(autoCloseable()) .addProperties(properties) .build() addType(type) } +context(OpenAPIContext) +fun autoCloseable(): ClassName = + if (isK2) ClassName("kotlin", "AutoCloseable") + else ClassName("io.ktor.utils.io.core", "Closeable") + context(OpenAPIContext, FileSpec.Builder) private fun Root.smartConstructor() { val className = className() @@ -96,7 +101,7 @@ private fun Root.implementation() { addType( TypeSpec.classBuilder(className.postfix("Ktor")) .addModifiers(KModifier.PRIVATE) - .addSuperinterfaces(listOf(className, AutoCloseable::class.asClassName())) + .addSuperinterfaces(listOf(className, autoCloseable())) .apiConstructor() .addProperties(properties) .addFunction( diff --git a/generation/src/main/kotlin/io/github/nomisrev/openapi/Main.kt b/generation/src/main/kotlin/io/github/nomisrev/openapi/Main.kt index de7fd77..1689766 100644 --- a/generation/src/main/kotlin/io/github/nomisrev/openapi/Main.kt +++ b/generation/src/main/kotlin/io/github/nomisrev/openapi/Main.kt @@ -4,22 +4,12 @@ import kotlin.io.path.Path import okio.FileSystem import okio.Path.Companion.toPath -fun main() { - generate( - GenerationConfig( - "openai-api.yaml", - "generation/build/geneated", - "io.github.nomisrev.openapi", - "OpenAI" - ) - ) -} - data class GenerationConfig( val path: String, val output: String, val `package`: String, - val name: String + val name: String, + val isK2: Boolean ) @JvmOverloads @@ -32,7 +22,7 @@ fun generate(config: GenerationConfig, fileSystem: FileSystem = FileSystem.SYSTE "yml" -> OpenAPI.fromYaml(rawSpec) else -> throw IllegalArgumentException("Unsupported file extension: $extension") } - with(OpenAPIContext(config.`package`)) { + with(OpenAPIContext(config)) { val root = openAPI.root(config.name) val models = openAPI.models() val modelFileSpecs = models.toFileSpecs() diff --git a/generation/src/main/kotlin/io/github/nomisrev/openapi/OpenAPIContext.kt b/generation/src/main/kotlin/io/github/nomisrev/openapi/OpenAPIContext.kt index 5989af2..3a69388 100644 --- a/generation/src/main/kotlin/io/github/nomisrev/openapi/OpenAPIContext.kt +++ b/generation/src/main/kotlin/io/github/nomisrev/openapi/OpenAPIContext.kt @@ -5,6 +5,7 @@ import java.util.concurrent.atomic.AtomicReference interface OpenAPIContext : Naming, APIInterceptor { val `package`: String + val isK2: Boolean fun addAdditionalFileSpec(fileSpec: FileSpec) @@ -12,11 +13,12 @@ interface OpenAPIContext : Naming, APIInterceptor { } fun OpenAPIContext( - `package`: String, - interceptor: APIInterceptor = APIInterceptor.openAIStreaming(`package`) + config: GenerationConfig, + interceptor: APIInterceptor = APIInterceptor.openAIStreaming(config.`package`) ): OpenAPIContext = - object : OpenAPIContext, Naming by Naming(`package`), APIInterceptor by interceptor { - override val `package`: String = `package` + object : OpenAPIContext, Naming by Naming(config.`package`), APIInterceptor by interceptor { + override val `package`: String = config.`package` + override val isK2: Boolean = config.isK2 private val files = AtomicReference>(emptyList()) override fun additionalFiles(): List = files.get() diff --git a/parser/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPI.kt b/parser/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPI.kt index cfe2165..d575c4a 100644 --- a/parser/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPI.kt +++ b/parser/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPI.kt @@ -24,13 +24,8 @@ public data class OpenAPI( @EncodeDefault(ALWAYS) public val openapi: String = "3.1.0", /** Provides metadata about the API. The metadata can be used by the clients if needed. */ public val info: Info, - /** - * An array of Server Objects, which provide connectivity information to a target server. If the - * servers property is not provided, or is an empty array, the default value would be a 'Server' - * object with an url value of @/@. - */ - // Should this be a set?? - public val servers: List = emptyList(), + /** An array of Server Objects, which provide connectivity information to a target server. */ + public val servers: List = listOf(Server(url = "/")), /** The available paths and operations for the API. */ public val paths: Map = emptyMap(), /** diff --git a/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientAction.kt b/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientAction.kt index 18bee4c..355ac76 100644 --- a/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientAction.kt +++ b/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientAction.kt @@ -15,7 +15,8 @@ abstract class GenerateClientAction : WorkAction val output: DirectoryProperty + // isK2 results in runtime Gradle error.. + val k2: Property } } diff --git a/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientTask.kt b/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientTask.kt index bccf45a..f9077bd 100644 --- a/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientTask.kt +++ b/plugin/src/main/java/io/github/nomisrev/openapi/plugin/GenerateClientTask.kt @@ -10,6 +10,7 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import org.gradle.workers.WorkerExecutor +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension @CacheableTask abstract class GenerateClientTask : DefaultTask() { @@ -31,12 +32,17 @@ abstract class GenerateClientTask : DefaultTask() { val workQueue = getWorkerExecutor().noIsolation() val specPath = requireNotNull(spec.orNull) { "No OpenAPI Config found" } require(specPath.isNotEmpty()) { "No OpenAPI Config found" } + val isK2 = + (project.extensions.getByName("kotlin") as KotlinProjectExtension) + .coreLibrariesVersion + .startsWith("2") specPath.forEach { spec -> workQueue.submit(GenerateClientAction::class.java) { parameters -> parameters.name.set(spec.name) parameters.packageName.set(spec.packageName) parameters.file.set(spec.file) parameters.output.set(project.output) + parameters.k2.set(isK2) } } }