-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from nomisRev/api-generation
API generation
- Loading branch information
Showing
77 changed files
with
3,024 additions
and
2,656 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
# OpenKTTP | ||
# OpenAPI-kt | ||
|
||
**WORK IN PROGRESS** | ||
|
||
OpenKTTP is a toolset for working with OpenAPI in Kotlin. | ||
This project exists out of several pieces, and they can be combined in different ways to achieve different goals. | ||
|
||
- Core: A OpenAPI parser, and typed ADT based on KotlinX Serialization | ||
- OpenAPI Typed: A version of the `Core` ADT, structures the data in a convenient way to retrieve. | ||
- Generic: A `Generic` ADT that allows working with content regardless of its format. | ||
- Generator: A code generator that generates code from the `OpenAPI Typed` ADT | ||
- Gradle Plugin: Gradle plugin to conveniently generate clients |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,59 @@ | ||
import com.diffplug.gradle.spotless.SpotlessExtension | ||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat | ||
import org.gradle.jvm.tasks.Jar | ||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
import org.jetbrains.kotlin.powerassert.gradle.PowerAssertGradleExtension | ||
|
||
plugins { | ||
alias(libs.plugins.multiplatform) apply false | ||
alias(libs.plugins.assert) | ||
alias(libs.plugins.publish) | ||
alias(libs.plugins.spotless) | ||
} | ||
|
||
configure<SpotlessExtension> { | ||
kotlin { | ||
target("**/*.kt") | ||
ktfmt().kotlinlangStyle().configure { | ||
it.setBlockIndent(2) | ||
it.setContinuationIndent(2) | ||
it.setRemoveUnusedImport(true) | ||
} | ||
trimTrailingWhitespace() | ||
endWithNewline() | ||
} | ||
} | ||
|
||
@Suppress("OPT_IN_USAGE") | ||
configure<PowerAssertGradleExtension> { | ||
functions = listOf( | ||
"kotlin.test.assertEquals", | ||
"kotlin.test.assertTrue" | ||
) | ||
} | ||
|
||
subprojects { | ||
tasks { | ||
withType(Jar::class.java) { | ||
manifest { | ||
attributes("Automatic-Module-Name" to "io.github.nomisrev") | ||
} | ||
} | ||
withType<JavaCompile> { | ||
options.release.set(8) | ||
} | ||
withType<KotlinCompile> { | ||
compilerOptions { | ||
jvmTarget.set(JvmTarget.JVM_1_8) | ||
} | ||
} | ||
withType<Test> { | ||
useJUnitPlatform() | ||
testLogging { | ||
exceptionFormat = TestExceptionFormat.FULL | ||
events("SKIPPED", "FAILED") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package io.github.nomisrev.openapi | ||
|
||
val x: OpenAPI = TODO() | ||
|
||
suspend fun main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
generation/src/commonMain/kotlin/io/github/nomisrev/openapi/ApiSorter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package io.github.nomisrev.openapi | ||
|
||
// TODO: make hard-coded. | ||
// We're opinionated about the API structure | ||
internal fun interface ApiSorter { | ||
fun sort(routes: Iterable<Route>): Root | ||
|
||
companion object { | ||
val ByPath: ApiSorter = ByPathApiSorter | ||
} | ||
} | ||
|
||
/** | ||
* ADT that models how to generate the API. Our OpenAPI document dictates the structure of the API, | ||
* so all operations are available as their path, with operationId. i.e. for `OpenAI` | ||
* `/chat/completions` with operationId `createChatCompletion`. | ||
* | ||
* interface OpenAI { val chat: Chat } interface Chat { val completions: Completions } interface | ||
* Completions { fun createChatCompletion(...): CreateChatCompletionResponse } | ||
* | ||
* // openAI.chat.completions.createChatCompletion(...) | ||
*/ | ||
data class Root( | ||
/* `info.title`, or custom name */ | ||
val name: String, | ||
val operations: List<Route>, | ||
val endpoints: List<API> | ||
) | ||
|
||
data class API(val name: String, val routes: List<Route>, val nested: List<API>) | ||
|
||
private data class RootBuilder( | ||
val name: String, | ||
val operations: MutableList<Route>, | ||
val nested: MutableList<APIBuilder> | ||
) { | ||
fun build(): Root = Root(name, operations, nested.map { it.build() }) | ||
} | ||
|
||
private data class APIBuilder( | ||
val name: String, | ||
val routes: MutableList<Route>, | ||
val nested: MutableList<APIBuilder> | ||
) { | ||
fun build(): API = API(name, routes, nested.map { it.build() }) | ||
} | ||
|
||
private object ByPathApiSorter : ApiSorter { | ||
override fun sort(routes: Iterable<Route>): Root { | ||
val root = RootBuilder("OpenAPI", mutableListOf(), mutableListOf()) | ||
routes.forEach { route -> | ||
// Reduce paths like `/threads/{thread_id}/runs/{run_id}/submit_tool_outputs` | ||
// into [threads, runs, submit_tool_outputs] | ||
val parts = route.path.replace(Regex("\\{.*?\\}"), "").split("/").filter { it.isNotEmpty() } | ||
|
||
val first = | ||
parts.getOrNull(0) | ||
?: run { | ||
// Root operation | ||
root.operations.add(route) | ||
return@forEach | ||
} | ||
// We need to find `chat` in root.operations, and find completions in chat.nested | ||
val api = | ||
root.nested.firstOrNull { it.name == first } | ||
?: run { | ||
val new = APIBuilder(first, mutableListOf(), mutableListOf()) | ||
root.nested.add(new) | ||
new | ||
} | ||
|
||
addRoute(api, parts.drop(1), route) | ||
} | ||
return root.build() | ||
} | ||
|
||
private fun addRoute(builder: APIBuilder, parts: List<String>, route: Route) { | ||
if (parts.isEmpty()) builder.routes.add(route) | ||
else { | ||
val part = parts[0] | ||
val api = builder.nested.firstOrNull { it.name == part } | ||
if (api == null) { | ||
val new = APIBuilder(part, mutableListOf(route), mutableListOf()) | ||
builder.nested.add(new) | ||
} else addRoute(api, parts.drop(1), route) | ||
} | ||
} | ||
} |
96 changes: 0 additions & 96 deletions
96
generation/src/commonMain/kotlin/io/github/nomisrev/openapi/GenerateClient.kt
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.