Add kotlinx.serialization

This commit is contained in:
Niels van Velzen 2020-09-13 11:40:50 +02:00
parent 5e87381b8d
commit 132e2cafaa
27 changed files with 226 additions and 125 deletions

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()))
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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())
}

View File

@ -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()
}
}

View File

@ -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\""))
}
}

View File

@ -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)

View File

@ -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(

View File

@ -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() }
}

View File

@ -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) }
}

View File

@ -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()
}

View File

@ -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()
}
}

View File

@ -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())
}
}

View File

@ -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) }
}
}

View File

@ -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) }
}
}

View File

@ -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)

View File

@ -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) }
}
}

View File

@ -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

View File

@ -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")
}

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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
)

View File

@ -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