Skip to content

Commit 5cb7284

Browse files
committed
refactor: extract common model
1 parent e67ace2 commit 5cb7284

File tree

11 files changed

+317
-352
lines changed

11 files changed

+317
-352
lines changed

.sdkmanrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Enable auto-env through the sdkman_auto_env config
2+
# Add key=value pairs of SDKs to use below
3+
java=17.0.14-tem

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: *
22

33
# The first command will be invoked with `make` only and should be `all`
4-
all: clean build
4+
all: clean format build
55

66
build:
77
./gradlew build
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
@file:OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
2+
3+
package community.flock.kotlinx.openapi.bindings.common
4+
5+
import kotlinx.serialization.ExperimentalSerializationApi
6+
import kotlinx.serialization.InternalSerializationApi
7+
import kotlinx.serialization.Serializable
8+
import kotlinx.serialization.json.JsonElement
9+
import kotlinx.serialization.json.JsonPrimitive
10+
import kotlin.jvm.JvmInline
11+
12+
interface CommonModel {
13+
val info: InfoObject
14+
val paths: Map<Path, PathItemObject>
15+
val security: List<Map<String, List<String>>>?
16+
val tags: List<TagObject>?
17+
val externalDocs: ExternalDocumentationObject?
18+
val xProperties: Map<String, JsonElement>?
19+
}
20+
21+
@Serializable
22+
data class InfoObject(
23+
val title: String,
24+
val description: String? = null,
25+
val termsOfService: String? = null,
26+
val contact: ContactObject? = null,
27+
val license: LicenseObject? = null,
28+
val version: String,
29+
val xProperties: Map<String, JsonElement>? = null,
30+
)
31+
32+
@JvmInline
33+
@Serializable
34+
value class Path(val value: String)
35+
36+
interface PathItemObject {
37+
val ref: String?
38+
val summary: String?
39+
val description: String?
40+
val get: OperationObject?
41+
val put: OperationObject?
42+
val post: OperationObject?
43+
val delete: OperationObject?
44+
val options: OperationObject?
45+
val head: OperationObject?
46+
val patch: OperationObject?
47+
val trace: OperationObject?
48+
val servers: List<ServerObject>?
49+
val xProperties: Map<String, JsonElement>?
50+
}
51+
52+
@Serializable
53+
data class ServerObject(
54+
val url: String,
55+
val description: String? = null,
56+
val variables: Map<String, ServerVariableObject>? = null,
57+
)
58+
59+
@Serializable
60+
data class ServerVariableObject(
61+
val enum: List<JsonPrimitive>? = null,
62+
val default: JsonElement? = null,
63+
val description: String? = null,
64+
)
65+
66+
interface OperationObject {
67+
val tags: List<String?>?
68+
val summary: String?
69+
val description: String?
70+
val externalDocs: ExternalDocumentationObject?
71+
val operationId: String?
72+
val deprecated: Boolean?
73+
val security: List<Map<String, List<String>>>?
74+
val servers: List<ServerObject>?
75+
val xProperties: Map<String, JsonElement>?
76+
}
77+
78+
@Serializable
79+
data class TagObject(
80+
val name: String,
81+
val description: String? = null,
82+
val externalDocs: ExternalDocumentationObject? = null,
83+
)
84+
85+
@Serializable
86+
data class ExternalDocumentationObject(
87+
val description: String? = null,
88+
val url: String,
89+
)
90+
91+
@Serializable
92+
data class ContactObject(
93+
val name: String? = null,
94+
val url: String? = null,
95+
val email: String? = null,
96+
)
97+
98+
@Serializable
99+
data class LicenseObject(
100+
val name: String,
101+
val url: String? = null,
102+
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package community.flock.kotlinx.openapi.bindings.common
2+
3+
import community.flock.kotlinx.openapi.bindings.Version
4+
import community.flock.kotlinx.openapi.bindings.Version.V2
5+
import community.flock.kotlinx.openapi.bindings.Version.V3
6+
import kotlinx.serialization.json.JsonArray
7+
import kotlinx.serialization.json.JsonElement
8+
import kotlinx.serialization.json.JsonObject
9+
import kotlinx.serialization.json.JsonPrimitive
10+
import kotlinx.serialization.json.jsonObject
11+
12+
private val regex = """
13+
^\|x-[^\|]*\|
14+
^\|info\|x-[^\|]*\|$
15+
\|requestBody\|x-[^\|]*\|$
16+
\|responses\|x-[^\|]*\|$
17+
\|items\|x-[^\|]*\|$
18+
\|schema\|x-[^\|]*\|$
19+
\|schemas\|[^\|]*\|x-[^\|]*\|$
20+
\|headers\|[^\|]*\|x-[^\|]*\|$
21+
\|properties\|[^\|]*\|x-[^\|]*\|$
22+
\|properties\|[^\|]*\|[^\|]*\|x-[^\|]*\|$
23+
\|parameters\|[^\|]*\|x-[^\|]*\|$
24+
\|paths\|[^\|]*\|[^\|]*\|x-[^\|]*\|$
25+
""".trimIndent().split("\n").map { it.toRegex() }
26+
27+
interface CommonSpecification {
28+
29+
fun JsonObject.decode(version: Version): JsonElement = validate(version)
30+
.traverse { path, obj -> obj.encodeExtensions(path) }
31+
32+
fun JsonElement.encode(): JsonElement = traverse { _, obj -> obj.decodeExtensions() }
33+
}
34+
35+
private fun JsonObject.validate(version: Version) = apply {
36+
when (version) {
37+
V2 -> check("swagger" in keys) { "No valid openapi v2 element 'swagger' is missing" }
38+
V3 -> check("openapi" in keys) { "No valid openapi v3 element 'openapi' is missing" }
39+
}
40+
}
41+
42+
private fun JsonObject.encodeExtensions(path: String): JsonObject = toList()
43+
.partition { (key, _) -> !regex.hasMatchedRegex("$path$key|") }
44+
.let { (known, unknown) -> known.toMap() + unknown.toXProperties() }
45+
.let(::JsonObject)
46+
47+
private fun List<Pair<String, JsonElement>>.toXProperties() = takeIf { it.isNotEmpty() }
48+
?.let { mapOf("xProperties" to JsonObject(it.toMap())) }
49+
?: emptyMap()
50+
51+
private fun JsonObject.decodeExtensions() = JsonObject(filter { it.key != "xProperties" } + (get("xProperties")?.jsonObject ?: emptyMap()))
52+
53+
private fun List<Regex>.hasMatchedRegex(path: String): Boolean = find { regex -> regex.containsMatchIn(path) }.found()
54+
55+
private fun Any?.found() = this?.let { true } ?: false
56+
57+
private fun JsonElement.traverse(path: String = "|", block: (String, JsonObject) -> JsonObject): JsonElement = when (this) {
58+
is JsonPrimitive -> this
59+
is JsonArray -> JsonArray(mapIndexed { idx, value -> value.traverse("$path$idx|", block) })
60+
is JsonObject -> map { it.key to it.value.traverse("$path${it.key}|", block) }.toMap()
61+
.let(::JsonObject)
62+
.let { block(path, it) }
63+
}

src/commonMain/kotlin/community/flock/kotlinx/openapi/bindings/v2/OpenAPI.kt

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package community.flock.kotlinx.openapi.bindings.v2
2+
3+
import community.flock.kotlinx.openapi.bindings.Version.V2
4+
import community.flock.kotlinx.openapi.bindings.common.CommonSpecification
5+
import kotlinx.serialization.decodeFromString
6+
import kotlinx.serialization.encodeToString
7+
import kotlinx.serialization.json.Json
8+
import kotlinx.serialization.json.JsonObject
9+
import kotlinx.serialization.json.decodeFromJsonElement
10+
import kotlinx.serialization.json.encodeToJsonElement
11+
12+
open class Swagger(
13+
val json: Json = Json { prettyPrint = true },
14+
) : CommonSpecification {
15+
fun decodeFromString(string: String): SwaggerObject = json
16+
.decodeFromString<JsonObject>(string)
17+
.decode(V2)
18+
.let(json::decodeFromJsonElement)
19+
20+
fun encodeToString(value: SwaggerObject): String = json
21+
.encodeToJsonElement(value)
22+
.encode()
23+
.let(json::encodeToString)
24+
25+
companion object Default : Swagger()
26+
}

0 commit comments

Comments
 (0)