mirror of
https://github.com/jellyfin/jellyfin-sdk-kotlin.git
synced 2024-11-23 05:49:59 +00:00
Add kotlinx.serialization
This commit is contained in:
parent
5e87381b8d
commit
132e2cafaa
@ -10,7 +10,7 @@ dependencies {
|
||||
// HTTP
|
||||
implementation(Dependencies.KotlinX.coroutinesCore)
|
||||
implementation(Dependencies.Ktor.okhttp)
|
||||
implementation(Dependencies.Ktor.gson)
|
||||
implementation(Dependencies.Ktor.serialization)
|
||||
|
||||
// Testing
|
||||
testImplementation(Dependencies.Kotlin.Test.junit)
|
||||
|
@ -1,28 +1,19 @@
|
||||
package org.jellyfin.apiclient.api.client
|
||||
|
||||
import com.google.gson.FieldNamingPolicy
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.features.json.serializer.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import org.jellyfin.apiclient.api.client.adapter.LocalDateTimeTypeAdapter
|
||||
import org.jellyfin.apiclient.api.client.adapter.UUIDTypeAdapter
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
open class KtorClient(
|
||||
var baseUrl: String
|
||||
) {
|
||||
val client = HttpClient {
|
||||
install(JsonFeature) {
|
||||
serializer = GsonSerializer {
|
||||
setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
|
||||
serializeNulls()
|
||||
registerTypeAdapter(UUID::class.java, UUIDTypeAdapter())
|
||||
registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeTypeAdapter())
|
||||
}
|
||||
serializer = KotlinxSerializer()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,21 +0,0 @@
|
||||
package org.jellyfin.apiclient.api.client.adapter
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Adapter to read zoned date times as local date time and writing it back
|
||||
*/
|
||||
class LocalDateTimeTypeAdapter : TypeAdapter<LocalDateTime>() {
|
||||
override fun write(out: JsonWriter, value: LocalDateTime?) {
|
||||
out.value(value?.atZone(ZoneId.systemDefault()).toString())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): LocalDateTime {
|
||||
return ZonedDateTime.parse(`in`.nextString()).toLocalDateTime()
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package org.jellyfin.apiclient.api.client.adapter
|
||||
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A custom UUID type adapter that supports the badly formatted UUIDs from the Jellyfin API
|
||||
*/
|
||||
class UUIDTypeAdapter : TypeAdapter<UUID>() {
|
||||
override fun write(out: JsonWriter, value: UUID?) {
|
||||
out.value(value?.toString())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): UUID {
|
||||
val uuid = `in`.nextString()
|
||||
|
||||
return if (uuid.length == 32) UUID.fromString(uuid.replace(UUID_REGEX, "$1-$2-$3-$4-$5"))
|
||||
else UUID.fromString(uuid)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UUID_REGEX = "^([a-z\\d]{8})([a-z\\d]{4})(4[a-z\\d]{3})([a-z\\d]{4})([a-z\\d]{12})\$".toRegex()
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package org.jellyfin.apiclient.api.client.adapter
|
||||
|
||||
import com.google.gson.stream.JsonReader
|
||||
import java.util.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class UUIDTypeAdapterTests {
|
||||
private fun String.asJsonReader() = JsonReader("\"$this\"".reader())
|
||||
|
||||
@Test
|
||||
fun `Parses correctly formatted UUIDs`() {
|
||||
val instance = UUIDTypeAdapter()
|
||||
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), instance.read("713dc3fe-952b-438f-a70e-d35e4ef0525a".asJsonReader()))
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), instance.read("713dc3fe-952b-438f-a70e-d35e4ef0525a".asJsonReader()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parses UUIDs formatted without dashes`() {
|
||||
val instance = UUIDTypeAdapter()
|
||||
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), instance.read("713dc3fe952b438fa70ed35e4ef0525a".asJsonReader()))
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), instance.read("713dc3fe952b438fa70ed35e4ef0525a".asJsonReader()))
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ object Dependencies {
|
||||
|
||||
val cli = item("cli", "0.2.1")
|
||||
val coroutinesCore = item("coroutines-core", "1.3.9")
|
||||
val serializationCore = item("serialization-core", "1.0.0-RC")
|
||||
}
|
||||
|
||||
object AndroidX {
|
||||
@ -17,7 +18,7 @@ object Dependencies {
|
||||
}
|
||||
|
||||
object Kotlin {
|
||||
private const val version = "1.3.72"
|
||||
const val version = "1.4.10"
|
||||
private fun item(library: String) = "org.jetbrains.kotlin:kotlin-$library:$version"
|
||||
|
||||
val stdlib = item("stdlib")
|
||||
@ -35,7 +36,7 @@ object Dependencies {
|
||||
private fun item(library: String) = "io.ktor:ktor-$library:$version"
|
||||
|
||||
val okhttp = item("client-okhttp")
|
||||
val gson = item("client-gson")
|
||||
val serialization = item("client-serialization-jvm")
|
||||
}
|
||||
|
||||
object Koin {
|
||||
|
@ -1,9 +1,15 @@
|
||||
plugins {
|
||||
id("kotlin")
|
||||
kotlin("plugin.serialization") version Dependencies.Kotlin.version
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(Dependencies.Kotlin.stdlib)
|
||||
compileOnly(Dependencies.KotlinX.serializationCore)
|
||||
|
||||
// Testing
|
||||
testImplementation(Dependencies.Kotlin.Test.junit)
|
||||
testImplementation(Dependencies.KotlinX.serializationCore)
|
||||
}
|
||||
|
||||
sourceSets.getByName("main").java.srcDir("src/main/kotlin-generated")
|
||||
@ -15,7 +21,7 @@ val sourcesJar by tasks.creating(Jar::class) {
|
||||
}
|
||||
|
||||
publishing.publications.create<MavenPublication>("default") {
|
||||
from(components["java"]) //TODO: Remove when deleting java sources
|
||||
from(components["kotlin"])
|
||||
|
||||
artifact(sourcesJar)
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package org.jellyfin.apiclient.model.serializer
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Serializer to read zoned date times as local date time and writing it back
|
||||
*/
|
||||
class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDateTime =
|
||||
ZonedDateTime.parse(decoder.decodeString()).toLocalDateTime()
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDateTime) =
|
||||
encoder.encodeString(value.atZone(ZoneId.systemDefault()).toString())
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.jellyfin.apiclient.model.serializer
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A UUID serializer that supports the GUIDs without dashes from the Jellyfin API
|
||||
*/
|
||||
class UUIDSerializer : KSerializer<UUID> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID {
|
||||
val uuid = decoder.decodeString()
|
||||
|
||||
return if (uuid.length == 32) UUID.fromString(uuid.replace(UUID_REGEX, "$1-$2-$3-$4-$5"))
|
||||
else UUID.fromString(uuid)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UUID_REGEX = "^([a-z\\d]{8})([a-z\\d]{4})(4[a-z\\d]{3})([a-z\\d]{4})([a-z\\d]{12})\$".toRegex()
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.jellyfin.apiclient.model.serializer
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class UUIDSerializerTests {
|
||||
@Test
|
||||
fun `Parses correctly formatted UUIDs`() {
|
||||
val instance = UUIDSerializer()
|
||||
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), Json.decodeFromString(instance, "\"713dc3fe-952b-438f-a70e-d35e4ef0525a\""))
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), Json.decodeFromString(instance, "\"713dc3fe-952b-438f-a70e-d35e4ef0525a\""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parses UUIDs formatted without dashes`() {
|
||||
val instance = UUIDSerializer()
|
||||
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), Json.decodeFromString(instance, "\"713dc3fe952b438fa70ed35e4ef0525a\""))
|
||||
assertEquals(UUID.fromString("713dc3fe-952b-438f-a70e-d35e4ef0525a"), Json.decodeFromString(instance, "\"713dc3fe952b438fa70ed35e4ef0525a\""))
|
||||
}
|
||||
}
|
@ -16,6 +16,9 @@ dependencies {
|
||||
// Kotlin code generation
|
||||
implementation(Dependencies.kotlinPoet)
|
||||
|
||||
// Needed for the kotlinx.serialization annotations
|
||||
implementation(Dependencies.KotlinX.serializationCore)
|
||||
|
||||
// Dependency Injection
|
||||
implementation(Dependencies.Koin.core)
|
||||
|
||||
|
@ -28,14 +28,11 @@ class Generator(
|
||||
private fun createModels(schemas: Map<String, Schema<Any>>) = schemas.map { (name, schema) ->
|
||||
if (schema.name == null) schema.name = name
|
||||
|
||||
openApiModelBuilder.build(schema)
|
||||
.let { JellyFile(Packages.MODEL, it) }
|
||||
.let(fileSpecBuilder::build)
|
||||
openApiModelBuilder.build(schema).let(fileSpecBuilder::build)
|
||||
}
|
||||
|
||||
private fun createApis(paths: Paths): List<FileSpec> = openApiApiServicesBuilder.build(paths)
|
||||
.map(apiBuilder::build)
|
||||
.map { JellyFile(Packages.API, it) }
|
||||
.map(fileSpecBuilder::build)
|
||||
|
||||
fun generate(
|
||||
|
@ -5,6 +5,7 @@ import org.jellyfin.openapi.builder.api.ApiNameBuilder
|
||||
import org.jellyfin.openapi.builder.api.OperationBuilder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.builder.extra.FileSpecBuilder
|
||||
import org.jellyfin.openapi.builder.extra.TypeSerializerBuilder
|
||||
import org.jellyfin.openapi.builder.model.EmptyModelBuilder
|
||||
import org.jellyfin.openapi.builder.model.EnumModelBuilder
|
||||
import org.jellyfin.openapi.builder.model.ModelBuilder
|
||||
@ -33,11 +34,12 @@ val mainModule = module {
|
||||
single { ModelBuilder(get(), get(), get()) }
|
||||
single { EmptyModelBuilder(get()) }
|
||||
single { EnumModelBuilder(get()) }
|
||||
single { ObjectModelBuilder(get()) }
|
||||
single { ObjectModelBuilder(get(), get()) }
|
||||
|
||||
// Files
|
||||
single { FileSpecBuilder() }
|
||||
|
||||
// Utilities
|
||||
single { DeprecatedAnnotationSpecBuilder() }
|
||||
single { TypeSerializerBuilder() }
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.constants.Classes
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.model.ApiService
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
|
||||
class ApiBuilder(
|
||||
private val operationBuilder: OperationBuilder
|
||||
) : Builder<ApiService, TypeSpec> {
|
||||
override fun build(data: ApiService): TypeSpec = TypeSpec.classBuilder(data.name).apply {
|
||||
) : Builder<ApiService, JellyFile> {
|
||||
override fun build(data: ApiService): JellyFile = TypeSpec.classBuilder(data.name).apply {
|
||||
// Add "api" value to constructor
|
||||
val apiClientType = ClassName(Packages.API_CLIENT, Classes.API_CLIENT)
|
||||
addProperty(PropertySpec.builder("api", apiClientType, KModifier.PRIVATE).initializer("api").build())
|
||||
@ -17,5 +18,5 @@ class ApiBuilder(
|
||||
|
||||
// Add operations
|
||||
data.operations.forEach { namedOperation -> addFunction(operationBuilder.build(namedOperation)) }
|
||||
}.build()
|
||||
}.build().let { JellyFile(Packages.API, emptySet(), it) }
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
package org.jellyfin.openapi.builder.extra
|
||||
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
|
||||
class DeprecatedAnnotationSpecBuilder : Builder<String, AnnotationSpec> {
|
||||
override fun build(data: String): AnnotationSpec {
|
||||
val cls = Deprecated::class.asTypeName()
|
||||
return AnnotationSpec.Companion.builder(cls)
|
||||
return AnnotationSpec.builder(Deprecated::class)
|
||||
.addMember("%S", data)
|
||||
.build()
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ import org.jellyfin.openapi.model.JellyFile
|
||||
|
||||
class FileSpecBuilder : Builder<JellyFile, FileSpec> {
|
||||
override fun build(data: JellyFile): FileSpec {
|
||||
return FileSpec.builder(data.namespace, data.typeSpec.name!!)
|
||||
.indent("\t")
|
||||
.addComment(Strings.FILE_TOP_WARNING)
|
||||
.addType(data.typeSpec)
|
||||
.build()
|
||||
return FileSpec.builder(data.namespace, data.typeSpec.name!!).apply {
|
||||
indent("\t")
|
||||
addComment(Strings.FILE_TOP_WARNING)
|
||||
data.annotations.forEach { addAnnotation(it) }
|
||||
addType(data.typeSpec)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
package org.jellyfin.openapi.builder.extra
|
||||
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.constants.Classes
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Returns the required serializer for a given type or null if no serializer defined
|
||||
*/
|
||||
class TypeSerializerBuilder : Builder<TypeName, TypeName?> {
|
||||
// Types should be non-nullable
|
||||
private val serializers = mapOf(
|
||||
UUID::class.asTypeName() to Classes.Serializers.UUID,
|
||||
LocalDateTime::class.asTypeName() to Classes.Serializers.LOCAL_DATE_TIME,
|
||||
)
|
||||
|
||||
override fun build(data: TypeName): TypeName? {
|
||||
// Look at parameter types to support collections
|
||||
val knownTypes = mutableSetOf<TypeName>()
|
||||
|
||||
if (data is ParameterizedTypeName) {
|
||||
knownTypes += data.rawType
|
||||
knownTypes += data.typeArguments
|
||||
} else {
|
||||
knownTypes += data
|
||||
}
|
||||
|
||||
val knownSerializers = knownTypes.mapNotNull {
|
||||
// Set nullable to false for a not-null comparison
|
||||
serializers[it.copy(nullable = false)]
|
||||
}
|
||||
|
||||
// None found
|
||||
if (knownSerializers.isEmpty()) return null
|
||||
|
||||
// One or multiple found
|
||||
require(knownSerializers.size == 1) { "Can not use multiple serializers" }
|
||||
|
||||
// Return type name for serializer
|
||||
return ClassName(Packages.MODEL_SERIALIZERS, knownSerializers.first())
|
||||
}
|
||||
}
|
@ -1,21 +1,27 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.model.EmptyApiModel
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.util.asPascalCase
|
||||
|
||||
class EmptyModelBuilder(
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder
|
||||
) : Builder<EmptyApiModel, TypeSpec> {
|
||||
override fun build(data: EmptyApiModel): TypeSpec {
|
||||
) : Builder<EmptyApiModel, JellyFile> {
|
||||
override fun build(data: EmptyApiModel): JellyFile {
|
||||
return TypeSpec.classBuilder(data.name.asPascalCase().toPascalCase())
|
||||
.apply {
|
||||
data.description?.let { addKdoc(it) }
|
||||
if (data.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_CLASS))
|
||||
addAnnotation(Serializable::class.asTypeName())
|
||||
}
|
||||
.build()
|
||||
.let { JellyFile(Packages.MODEL, emptySet(), it) }
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.asTypeName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.model.EnumApiModel
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.util.asPascalCase
|
||||
|
||||
class EnumModelBuilder(
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder
|
||||
) : Builder<EnumApiModel, TypeSpec> {
|
||||
override fun build(data: EnumApiModel): TypeSpec {
|
||||
) : Builder<EnumApiModel, JellyFile> {
|
||||
override fun build(data: EnumApiModel): JellyFile {
|
||||
return TypeSpec.enumBuilder(data.name.asPascalCase().toPascalCase())
|
||||
.apply {
|
||||
data.constants.forEach {
|
||||
@ -18,7 +22,9 @@ class EnumModelBuilder(
|
||||
}
|
||||
data.description?.let { addKdoc(it) }
|
||||
if (data.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_CLASS))
|
||||
addAnnotation(Serializable::class.asTypeName())
|
||||
}
|
||||
.build()
|
||||
.let { JellyFile(Packages.MODEL, emptySet(), it) }
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.model.ApiModel
|
||||
import org.jellyfin.openapi.model.EmptyApiModel
|
||||
import org.jellyfin.openapi.model.EnumApiModel
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
import org.jellyfin.openapi.model.*
|
||||
|
||||
class ModelBuilder(
|
||||
private val emptyModelBuilder: EmptyModelBuilder,
|
||||
private val enumModelBuilder: EnumModelBuilder,
|
||||
private val objectModelBuilder: ObjectModelBuilder
|
||||
) : Builder<ApiModel, TypeSpec> {
|
||||
) : Builder<ApiModel, JellyFile> {
|
||||
override fun build(data: ApiModel) = when (data) {
|
||||
is EmptyApiModel -> emptyModelBuilder.build(data)
|
||||
is EnumApiModel -> enumModelBuilder.build(data)
|
||||
|
@ -1,17 +1,25 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.builder.extra.TypeSerializerBuilder
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
import org.jellyfin.openapi.util.asPascalCase
|
||||
|
||||
class ObjectModelBuilder(
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder
|
||||
) : Builder<ObjectApiModel, TypeSpec> {
|
||||
override fun build(data: ObjectApiModel): TypeSpec {
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder,
|
||||
private val typeSerializerBuilder: TypeSerializerBuilder
|
||||
) : Builder<ObjectApiModel, JellyFile> {
|
||||
override fun build(data: ObjectApiModel): JellyFile {
|
||||
val properties = mutableListOf<PropertySpec>()
|
||||
val serializers = mutableSetOf<TypeName>()
|
||||
val constructor = FunSpec.constructorBuilder().apply {
|
||||
data.properties.forEach { property ->
|
||||
// Create constructor parameter
|
||||
@ -28,21 +36,43 @@ class ObjectModelBuilder(
|
||||
property.description?.let { addKdoc(it) }
|
||||
|
||||
if (property.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_MEMBER))
|
||||
addAnnotation(AnnotationSpec.builder(SerialName::class).addMember("%S", property.originalName).build())
|
||||
}
|
||||
.build()
|
||||
)
|
||||
|
||||
// Check if serializer is required
|
||||
val serializer = typeSerializerBuilder.build(property.type)
|
||||
if (serializer != null && serializer !in serializers) serializers += serializer
|
||||
}
|
||||
}.build()
|
||||
|
||||
// Create UseSerializers annotation
|
||||
val useSerializersAnnotation = serializers
|
||||
.ifEmpty { null }
|
||||
?.let {
|
||||
AnnotationSpec.builder(UseSerializers::class).apply {
|
||||
useSiteTarget(AnnotationSpec.UseSiteTarget.FILE)
|
||||
// Add all serializers
|
||||
serializers.forEach { serializer -> addMember("%T::class", serializer) }
|
||||
}.build()
|
||||
}
|
||||
|
||||
val fileAnnotations = useSerializersAnnotation
|
||||
?.let { setOf(useSerializersAnnotation) }
|
||||
?: emptySet()
|
||||
|
||||
// Create class
|
||||
return TypeSpec.classBuilder(data.name.asPascalCase().toPascalCase())
|
||||
.apply {
|
||||
modifiers += KModifier.DATA
|
||||
data.description?.let { addKdoc(it) }
|
||||
if (data.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_CLASS))
|
||||
addAnnotation(AnnotationSpec.builder(Serializable::class).build())
|
||||
}
|
||||
.primaryConstructor(constructor)
|
||||
.addProperties(properties)
|
||||
.build()
|
||||
.let { JellyFile(Packages.MODEL, fileAnnotations, it) }
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
package org.jellyfin.openapi.builder.openapi
|
||||
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import io.swagger.v3.oas.models.media.Schema
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.model.ModelBuilder
|
||||
import org.jellyfin.openapi.hooks.ModelTypePath
|
||||
import org.jellyfin.openapi.model.EmptyApiModel
|
||||
import org.jellyfin.openapi.model.EnumApiModel
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
import org.jellyfin.openapi.model.ObjectApiModelProperty
|
||||
import org.jellyfin.openapi.model.*
|
||||
import org.jellyfin.openapi.util.asPascalCase
|
||||
|
||||
class OpenApiModelBuilder(
|
||||
private val openApiTypeBuilder: OpenApiTypeBuilder,
|
||||
private val modelBuilder: ModelBuilder
|
||||
) : Builder<Schema<Any>, TypeSpec> {
|
||||
override fun build(data: Schema<Any>): TypeSpec {
|
||||
) : Builder<Schema<Any>, JellyFile> {
|
||||
override fun build(data: Schema<Any>): JellyFile {
|
||||
val model = when {
|
||||
// Object
|
||||
data.type == "object" -> when (data.properties.isNullOrEmpty()) {
|
||||
@ -26,6 +22,7 @@ class OpenApiModelBuilder(
|
||||
val name = originalName.asPascalCase().toCamelCase()
|
||||
ObjectApiModelProperty(
|
||||
name = name,
|
||||
originalName = originalName,
|
||||
type = openApiTypeBuilder.build(ModelTypePath(data.name, name), property),
|
||||
description = property.description,
|
||||
deprecated = property.deprecated == true
|
||||
|
@ -87,6 +87,5 @@ class OpenApiTypeBuilder(
|
||||
|
||||
private fun buildBinary() = InputStream::class.asTypeName()
|
||||
|
||||
|
||||
class UnknownTypeError(type: String?, format: String?) : Error("Unknown type $type with format $format")
|
||||
}
|
||||
|
@ -3,4 +3,9 @@ package org.jellyfin.openapi.constants
|
||||
object Classes {
|
||||
const val API_CLIENT = "KtorClient"
|
||||
const val API_RESPONSE = "Response"
|
||||
|
||||
object Serializers {
|
||||
const val UUID = "UUIDSerializer"
|
||||
const val LOCAL_DATE_TIME = "LocalDateTimeSerializer"
|
||||
}
|
||||
}
|
||||
|
@ -15,4 +15,9 @@ object Packages {
|
||||
* Package for the generated models
|
||||
*/
|
||||
const val MODEL = "org.jellyfin.apiclient.model.api"
|
||||
|
||||
/**
|
||||
* Package containing all kotlinx.serialization serializers
|
||||
*/
|
||||
const val MODEL_SERIALIZERS = "org.jellyfin.apiclient.model.serializer"
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package org.jellyfin.openapi.model
|
||||
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
|
||||
data class JellyFile(
|
||||
val namespace: String,
|
||||
val annotations: Set<AnnotationSpec>,
|
||||
val typeSpec: TypeSpec
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import com.squareup.kotlinpoet.TypeName
|
||||
|
||||
data class ObjectApiModelProperty(
|
||||
val name: String,
|
||||
val originalName: String,
|
||||
val type: TypeName,
|
||||
val description: String?,
|
||||
val deprecated: Boolean
|
||||
|
Loading…
Reference in New Issue
Block a user