Skip to content

Commit

Permalink
BUGFIX: K1 vs K2 AutoCloseable incompatibility (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev authored Jul 4, 2024
1 parent da045eb commit fbfe55a
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 43 deletions.
1 change: 1 addition & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ openApiConfig { spec("OpenAI", file("openai.yaml")) {

kotlin {
jvm()
js {
browser()
nodejs()
}
iosArm64()
macosArm64()
linuxX64()
mingwX64()

sourceSets {
commonMain {
dependencies {
Expand All @@ -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")
}
}
}
}
26 changes: 11 additions & 15 deletions example/src/commonMain/kotlin/io/github/nomisrev/example/test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 }
}
}
11 changes: 8 additions & 3 deletions generation/src/main/kotlin/io/github/nomisrev/openapi/APIs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand Down
16 changes: 3 additions & 13 deletions generation/src/main/kotlin/io/github/nomisrev/openapi/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import java.util.concurrent.atomic.AtomicReference

interface OpenAPIContext : Naming, APIInterceptor {
val `package`: String
val isK2: Boolean

fun addAdditionalFileSpec(fileSpec: FileSpec)

fun additionalFiles(): List<FileSpec>
}

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<List<FileSpec>>(emptyList())

override fun additionalFiles(): List<FileSpec> = files.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Server> = emptyList(),
/** An array of Server Objects, which provide connectivity information to a target server. */
public val servers: List<Server> = listOf(Server(url = "/")),
/** The available paths and operations for the API. */
public val paths: Map<String, PathItem> = emptyMap(),
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ abstract class GenerateClientAction : WorkAction<GenerateClientAction.Parameters
path = parameters.file.get().asFile.path,
output = parameters.output.get().asFile.path,
`package` = parameters.packageName.get() ?: "io.github.nomisrev.openapi",
name = parameters.name.get()
name = parameters.name.get(),
isK2 = parameters.k2.get()
)
)
}
Expand All @@ -25,5 +26,7 @@ abstract class GenerateClientAction : WorkAction<GenerateClientAction.Parameters
val file: RegularFileProperty
val packageName: Property<String>
val output: DirectoryProperty
// isK2 results in runtime Gradle error..
val k2: Property<Boolean>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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)
}
}
}
Expand Down

0 comments on commit fbfe55a

Please sign in to comment.