mirror of
https://github.com/jellyfin/jellyfin-sdk-kotlin.git
synced 2025-02-25 18:50:44 +00:00
Add interfaces and polymorphic serialization to OpenAPI generator
This commit is contained in:
parent
88a3e592e5
commit
3c610643ce
@ -14,6 +14,7 @@ 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.InterfaceModelBuilder
|
||||
import org.jellyfin.openapi.builder.model.ModelBuilder
|
||||
import org.jellyfin.openapi.builder.model.ModelsBuilder
|
||||
import org.jellyfin.openapi.builder.model.ObjectModelBuilder
|
||||
@ -65,11 +66,12 @@ val mainModule = module {
|
||||
single { ApiClientExtensionsBuilder(get()) }
|
||||
|
||||
// Models
|
||||
single { ModelBuilder(get(), get(), get()) }
|
||||
single { ModelBuilder(get(), get(), get(), get()) }
|
||||
single { ModelsBuilder(get(), get()) }
|
||||
single { EmptyModelBuilder(get(), get()) }
|
||||
single { EnumModelBuilder(get(), get()) }
|
||||
single { ObjectModelBuilder(get(), get(), get()) }
|
||||
single { InterfaceModelBuilder(get(), get()) }
|
||||
single { RequestModelBuilder(get()) }
|
||||
|
||||
// Files
|
||||
|
@ -82,7 +82,7 @@ class ApiBuilder(
|
||||
|
||||
// Request model variant
|
||||
if (createRequestModelVariant) {
|
||||
context += fileSpecBuilder.build(requestModelBuilder.build(namedOperation))
|
||||
context += fileSpecBuilder.build(requestModelBuilder.build(context, namedOperation))
|
||||
addFunction(operationParameterModelBuilder.build(namedOperation))
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import net.pearx.kasechange.CaseFormat
|
||||
import net.pearx.kasechange.toPascalCase
|
||||
@ -20,6 +21,15 @@ class EmptyModelBuilder(
|
||||
override fun build(data: EmptyApiModel): JellyFile {
|
||||
return TypeSpec.classBuilder(data.name.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL))
|
||||
.apply {
|
||||
data.interfaces.forEach { interfaceName ->
|
||||
addSuperinterface(
|
||||
ClassName(
|
||||
Packages.MODEL,
|
||||
interfaceName.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
descriptionBuilder.build(DescriptionType.MODEL, data.description)?.let {
|
||||
addKdoc("%L", it)
|
||||
}
|
||||
|
@ -28,6 +28,16 @@ class EnumModelBuilder(
|
||||
override fun build(data: EnumApiModel): JellyFile {
|
||||
return TypeSpec.enumBuilder(data.name.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL))
|
||||
.apply {
|
||||
// Super
|
||||
data.interfaces.forEach { interfaceName ->
|
||||
addSuperinterface(
|
||||
ClassName(
|
||||
Packages.MODEL,
|
||||
interfaceName.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Constructor
|
||||
primaryConstructor(FunSpec.constructorBuilder().apply {
|
||||
addParameter("serialName", Types.STRING)
|
||||
|
@ -0,0 +1,76 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import net.pearx.kasechange.CaseFormat
|
||||
import net.pearx.kasechange.toPascalCase
|
||||
import org.jellyfin.openapi.builder.ContextBuilder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.builder.extra.DescriptionBuilder
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.constants.Types
|
||||
import org.jellyfin.openapi.model.DescriptionType
|
||||
import org.jellyfin.openapi.model.GeneratorContext
|
||||
import org.jellyfin.openapi.model.InterfaceApiModel
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
|
||||
class InterfaceModelBuilder(
|
||||
private val descriptionBuilder: DescriptionBuilder,
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder,
|
||||
) : ContextBuilder<InterfaceApiModel, JellyFile> {
|
||||
@Suppress("ComplexMethod")
|
||||
override fun build(context: GeneratorContext, data: InterfaceApiModel): JellyFile {
|
||||
val interfacePropertyNames = data.interfaces
|
||||
.flatMap { (context.getOrGenerateModel(it) as? InterfaceApiModel)?.properties.orEmpty() }
|
||||
.map { it.name }
|
||||
|
||||
// Create class properties
|
||||
val properties = data.properties.map { property ->
|
||||
PropertySpec
|
||||
.builder(property.name, property.type)
|
||||
.apply {
|
||||
descriptionBuilder.build(DescriptionType.MODEL_PROPERTY, property.description)?.let {
|
||||
addKdoc("%L", it)
|
||||
}
|
||||
|
||||
// Add override modifier if in interface
|
||||
if (property.name in interfacePropertyNames) modifiers += KModifier.OVERRIDE
|
||||
|
||||
if (property.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_MEMBER))
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
// Create class
|
||||
return TypeSpec.interfaceBuilder(data.name.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL))
|
||||
.apply {
|
||||
data.interfaces.forEach { interfaceName ->
|
||||
addSuperinterface(
|
||||
ClassName(
|
||||
Packages.MODEL,
|
||||
interfaceName.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
modifiers += KModifier.SEALED
|
||||
descriptionBuilder.build(DescriptionType.MODEL, data.description)?.let {
|
||||
addKdoc("%L", it)
|
||||
}
|
||||
if (data.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_CLASS))
|
||||
|
||||
// Only allow serialization when a discriminator is defined
|
||||
data.polymorphicDiscriminator?.let {
|
||||
addAnnotation(AnnotationSpec.builder(Types.SERIALIZABLE).build())
|
||||
addAnnotation(AnnotationSpec.builder(Types.JSON_DISCRIMINATOR).addMember("%S", it).build())
|
||||
}
|
||||
}
|
||||
.addProperties(properties)
|
||||
.build()
|
||||
.let { JellyFile(Packages.MODEL, emptySet(), it) }
|
||||
}
|
||||
}
|
@ -1,22 +1,26 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import org.jellyfin.openapi.OpenApiGeneratorError
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.ContextBuilder
|
||||
import org.jellyfin.openapi.model.ApiModel
|
||||
import org.jellyfin.openapi.model.EmptyApiModel
|
||||
import org.jellyfin.openapi.model.EnumApiModel
|
||||
import org.jellyfin.openapi.model.GeneratorContext
|
||||
import org.jellyfin.openapi.model.InterfaceApiModel
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
|
||||
class ModelBuilder(
|
||||
private val emptyModelBuilder: EmptyModelBuilder,
|
||||
private val enumModelBuilder: EnumModelBuilder,
|
||||
private val objectModelBuilder: ObjectModelBuilder
|
||||
) : Builder<ApiModel, JellyFile> {
|
||||
override fun build(data: ApiModel) = when (data) {
|
||||
private val objectModelBuilder: ObjectModelBuilder,
|
||||
private val interfaceModelBuilder: InterfaceModelBuilder,
|
||||
) : ContextBuilder<ApiModel, JellyFile> {
|
||||
override fun build(context: GeneratorContext, data: ApiModel) = when (data) {
|
||||
is EmptyApiModel -> emptyModelBuilder.build(data)
|
||||
is EnumApiModel -> enumModelBuilder.build(data)
|
||||
is ObjectApiModel -> objectModelBuilder.build(data)
|
||||
is ObjectApiModel -> objectModelBuilder.build(context, data)
|
||||
is InterfaceApiModel -> interfaceModelBuilder.build(context, data)
|
||||
else -> throw OpenApiGeneratorError("Unknown model class ${data::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class ModelsBuilder(
|
||||
) : ContextBuilder<Collection<ApiModel>, Unit> {
|
||||
override fun build(context: GeneratorContext, data: Collection<ApiModel>) {
|
||||
for (model in data) {
|
||||
val file = modelBuilder.build(model)
|
||||
val file = modelBuilder.build(context, model)
|
||||
context += fileSpecBuilder.build(file)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
@ -9,7 +10,7 @@ import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import net.pearx.kasechange.CaseFormat
|
||||
import net.pearx.kasechange.toPascalCase
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.ContextBuilder
|
||||
import org.jellyfin.openapi.builder.extra.DeprecatedAnnotationSpecBuilder
|
||||
import org.jellyfin.openapi.builder.extra.DescriptionBuilder
|
||||
import org.jellyfin.openapi.builder.extra.TypeSerializerBuilder
|
||||
@ -17,7 +18,10 @@ import org.jellyfin.openapi.builder.extra.defaultValue
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.constants.Types
|
||||
import org.jellyfin.openapi.model.DefaultValue
|
||||
import org.jellyfin.openapi.model.DescriptionType
|
||||
import org.jellyfin.openapi.model.GeneratorContext
|
||||
import org.jellyfin.openapi.model.InterfaceApiModel
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
|
||||
@ -25,11 +29,32 @@ class ObjectModelBuilder(
|
||||
private val descriptionBuilder: DescriptionBuilder,
|
||||
private val deprecatedAnnotationSpecBuilder: DeprecatedAnnotationSpecBuilder,
|
||||
private val typeSerializerBuilder: TypeSerializerBuilder
|
||||
) : Builder<ObjectApiModel, JellyFile> {
|
||||
@Suppress("ComplexMethod")
|
||||
override fun build(data: ObjectApiModel): JellyFile {
|
||||
) : ContextBuilder<ObjectApiModel, JellyFile> {
|
||||
@Suppress("ComplexMethod", "LoopWithTooManyJumpStatements", "LongMethod", "NestedBlockDepth")
|
||||
override fun build(context: GeneratorContext, data: ObjectApiModel): JellyFile {
|
||||
val properties = mutableListOf<PropertySpec>()
|
||||
val serializers = mutableSetOf<TypeName>()
|
||||
|
||||
var polymorphicProperty: String? = null
|
||||
var polymorphicPropertyValue: String? = null
|
||||
val superPropertyNames = mutableSetOf<String>()
|
||||
|
||||
for (interfaceName in data.interfaces) {
|
||||
// Add shared properties
|
||||
val interfaceModel = context.getOrGenerateModel(interfaceName) as? InterfaceApiModel ?: continue
|
||||
interfaceModel.properties.forEach { superPropertyNames.add(it.name) }
|
||||
|
||||
if (interfaceModel.polymorphicDiscriminator == null) continue
|
||||
|
||||
// Determine discriminator property name
|
||||
val discriminator = interfaceModel.polymorphicDiscriminator
|
||||
require(polymorphicProperty == null || polymorphicProperty == discriminator) {
|
||||
"Multiple polymorphic interfaces with different discriminator are not supported." +
|
||||
" (a=$polymorphicProperty, b=$discriminator)."
|
||||
}
|
||||
polymorphicProperty = discriminator
|
||||
}
|
||||
|
||||
val constructor = FunSpec.constructorBuilder().apply {
|
||||
data.properties.forEach { property ->
|
||||
// Create constructor parameter
|
||||
@ -46,8 +71,13 @@ class ObjectModelBuilder(
|
||||
addKdoc("%L", it)
|
||||
}
|
||||
|
||||
// Add override modifier if in interface
|
||||
if (property.name in superPropertyNames) modifiers += KModifier.OVERRIDE
|
||||
|
||||
if (property.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_MEMBER))
|
||||
addAnnotation(AnnotationSpec.builder(Types.SERIAL_NAME).addMember("%S", property.originalName).build())
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Types.SERIAL_NAME).addMember("%S", property.originalName).build()
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
@ -55,6 +85,19 @@ class ObjectModelBuilder(
|
||||
// Check if serializer is required
|
||||
val serializer = typeSerializerBuilder.build(property.type)
|
||||
if (serializer != null && serializer !in serializers) serializers += serializer
|
||||
|
||||
// Save polymorphic value
|
||||
if (property.originalName == polymorphicProperty) {
|
||||
require(polymorphicPropertyValue == null) { "Polymorphic property value already set" }
|
||||
requireNotNull(property.defaultValue) { "Default value must be set for polymorphic property" }
|
||||
|
||||
polymorphicPropertyValue = when (property.defaultValue) {
|
||||
is DefaultValue.String -> property.defaultValue.value
|
||||
is DefaultValue.EnumMember -> property.defaultValue.serialName
|
||||
|
||||
else -> error("Polymorphic property value is of invalid type ${property.defaultValue}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
@ -76,12 +119,30 @@ class ObjectModelBuilder(
|
||||
// Create class
|
||||
return TypeSpec.classBuilder(data.name.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL))
|
||||
.apply {
|
||||
data.interfaces.forEach { interfaceName ->
|
||||
addSuperinterface(
|
||||
ClassName(
|
||||
Packages.MODEL,
|
||||
interfaceName.toPascalCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
modifiers += KModifier.DATA
|
||||
descriptionBuilder.build(DescriptionType.MODEL, data.description)?.let {
|
||||
addKdoc("%L", it)
|
||||
}
|
||||
if (data.deprecated) addAnnotation(deprecatedAnnotationSpecBuilder.build(Strings.DEPRECATED_CLASS))
|
||||
addAnnotation(AnnotationSpec.builder(Types.SERIALIZABLE).build())
|
||||
|
||||
if (polymorphicProperty != null) {
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(Types.SERIAL_NAME).addMember(
|
||||
"%S",
|
||||
requireNotNull(polymorphicPropertyValue) { "Polymorphic property value is missing" }
|
||||
).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
.primaryConstructor(constructor)
|
||||
.addProperties(properties)
|
||||
|
@ -1,23 +1,26 @@
|
||||
package org.jellyfin.openapi.builder.model
|
||||
|
||||
import net.pearx.kasechange.toPascalCase
|
||||
import org.jellyfin.openapi.builder.Builder
|
||||
import org.jellyfin.openapi.builder.ContextBuilder
|
||||
import org.jellyfin.openapi.constants.Packages
|
||||
import org.jellyfin.openapi.constants.Strings
|
||||
import org.jellyfin.openapi.model.ApiServiceOperation
|
||||
import org.jellyfin.openapi.model.DefaultValue
|
||||
import org.jellyfin.openapi.model.GeneratorContext
|
||||
import org.jellyfin.openapi.model.JellyFile
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
import org.jellyfin.openapi.model.ObjectApiModelProperty
|
||||
|
||||
class RequestModelBuilder(
|
||||
private val objectModelBuilder: ObjectModelBuilder,
|
||||
) : Builder<ApiServiceOperation, JellyFile> {
|
||||
override fun build(data: ApiServiceOperation): JellyFile = objectModelBuilder.build(
|
||||
) : ContextBuilder<ApiServiceOperation, JellyFile> {
|
||||
override fun build(context: GeneratorContext, data: ApiServiceOperation): JellyFile = objectModelBuilder.build(
|
||||
context,
|
||||
ObjectApiModel(
|
||||
data.name.toPascalCase() + Strings.MODEL_REQUEST_SUFFIX,
|
||||
data.description,
|
||||
false,
|
||||
emptySet(),
|
||||
(data.pathParameters + data.queryParameters).map { parameter ->
|
||||
ObjectApiModelProperty(
|
||||
name = parameter.name,
|
||||
|
@ -34,7 +34,8 @@ class OpenApiDefaultValueBuilder : ContextBuilder<Schema<Any>, DefaultValue?> {
|
||||
|
||||
return DefaultValue.EnumMember(
|
||||
enumType = ClassName(Packages.MODEL, model.name),
|
||||
memberName = schemaDefault.toScreamingSnakeCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
memberName = schemaDefault.toScreamingSnakeCase(from = CaseFormat.CAPITALIZED_CAMEL),
|
||||
serialName = schemaDefault,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ import org.jellyfin.openapi.model.ApiModel
|
||||
import org.jellyfin.openapi.model.EmptyApiModel
|
||||
import org.jellyfin.openapi.model.EnumApiModel
|
||||
import org.jellyfin.openapi.model.GeneratorContext
|
||||
import org.jellyfin.openapi.model.InterfaceApiModel
|
||||
import org.jellyfin.openapi.model.InterfaceApiModelProperty
|
||||
import org.jellyfin.openapi.model.ObjectApiModel
|
||||
import org.jellyfin.openapi.model.ObjectApiModelProperty
|
||||
|
||||
@ -16,42 +18,102 @@ class OpenApiModelBuilder(
|
||||
private val openApiTypeBuilder: OpenApiTypeBuilder,
|
||||
private val openApiDefaultValueBuilder: OpenApiDefaultValueBuilder,
|
||||
) : ContextBuilder<Schema<Any>, ApiModel> {
|
||||
override fun build(context: GeneratorContext, data: Schema<Any>): ApiModel = when {
|
||||
// Object
|
||||
data.type == "object" -> when (data.properties.isNullOrEmpty()) {
|
||||
// No properties use the empty model
|
||||
true -> EmptyApiModel(
|
||||
data.name,
|
||||
data.description,
|
||||
data.deprecated == true
|
||||
)
|
||||
// Otherwise use the object model
|
||||
false -> ObjectApiModel(
|
||||
data.name,
|
||||
data.description,
|
||||
data.deprecated == true,
|
||||
data.properties.map { (originalName, property) ->
|
||||
val name = originalName.toCamelCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
ObjectApiModelProperty(
|
||||
name = name,
|
||||
originalName = originalName,
|
||||
defaultValue = openApiDefaultValueBuilder.build(context, property),
|
||||
type = openApiTypeBuilder.build(ModelTypePath(data.name, name), property),
|
||||
description = property.description,
|
||||
deprecated = property.deprecated == true
|
||||
override fun build(context: GeneratorContext, data: Schema<Any>): ApiModel {
|
||||
return when {
|
||||
// Object
|
||||
data.type == "object" -> when {
|
||||
// When properties set, use the object model
|
||||
!data.properties.isNullOrEmpty() -> buildObjectModel(context, data)
|
||||
// When oneOf set, use the interface model
|
||||
!data.oneOf.isNullOrEmpty() -> buildInterfaceModel(context, data)
|
||||
// Otherwise use the empty model
|
||||
else -> buildEmptyModel(data)
|
||||
}
|
||||
// Enum
|
||||
data.enum.isNotEmpty() -> buildEnumModel(data)
|
||||
|
||||
// Unknown type
|
||||
else -> throw NotImplementedError("Unknown top-level type: ${data.type} for ${data.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildInterfaceModel(context: GeneratorContext, data: Schema<Any>): InterfaceApiModel {
|
||||
val referencedModels = data.oneOf.mapNotNull { it.`$ref`?.let(context::getOrGenerateModel) }
|
||||
var sharedProperties: Set<InterfaceApiModelProperty>? = null
|
||||
|
||||
for ((index, model) in referencedModels.withIndex()) {
|
||||
// Add interface to referenced model
|
||||
context.addModelInterface(model, data.name)
|
||||
|
||||
// Only search for shared properties if the lookup didn't fail before this iteration
|
||||
if (sharedProperties == null && index != 0) continue
|
||||
|
||||
// Find properties of current model
|
||||
val modelProperties = when (model) {
|
||||
is ObjectApiModel -> model.properties.map {
|
||||
InterfaceApiModelProperty(
|
||||
name = it.name,
|
||||
originalName = it.originalName,
|
||||
type = it.type,
|
||||
description = it.description,
|
||||
deprecated = it.deprecated,
|
||||
)
|
||||
}.toSet()
|
||||
)
|
||||
}
|
||||
// Enum
|
||||
data.enum.isNotEmpty() -> EnumApiModel(
|
||||
data.name,
|
||||
data.description,
|
||||
data.deprecated == true,
|
||||
data.enum.orEmpty().map { it.toString() }.toSet()
|
||||
)
|
||||
is InterfaceApiModel -> model.properties
|
||||
else -> null
|
||||
}
|
||||
|
||||
// Unknown type
|
||||
else -> throw NotImplementedError("Unknown top-level type: ${data.type} for ${data.name}")
|
||||
// Search for shared properties between previous and current iteration
|
||||
sharedProperties = when {
|
||||
// Unsupported model type
|
||||
modelProperties == null -> null
|
||||
// First iteration
|
||||
sharedProperties == null -> modelProperties
|
||||
// Compare with existing properties
|
||||
else -> sharedProperties.filter { a -> modelProperties.any { b -> a.typeMatches(b) } }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
return InterfaceApiModel(
|
||||
name = data.name,
|
||||
description = data.description,
|
||||
deprecated = data.deprecated == true,
|
||||
interfaces = emptySet(),
|
||||
polymorphicDiscriminator = data.discriminator?.propertyName,
|
||||
properties = sharedProperties.orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildEmptyModel(data: Schema<Any>) = EmptyApiModel(
|
||||
name = data.name,
|
||||
description = data.description,
|
||||
deprecated = data.deprecated == true,
|
||||
interfaces = emptySet(),
|
||||
)
|
||||
|
||||
private fun buildObjectModel(context: GeneratorContext, data: Schema<Any>) = ObjectApiModel(
|
||||
name = data.name,
|
||||
description = data.description,
|
||||
deprecated = data.deprecated == true,
|
||||
interfaces = emptySet(),
|
||||
properties = data.properties.map { (originalName, property) ->
|
||||
val name = originalName.toCamelCase(from = CaseFormat.CAPITALIZED_CAMEL)
|
||||
ObjectApiModelProperty(
|
||||
name = name,
|
||||
originalName = originalName,
|
||||
defaultValue = openApiDefaultValueBuilder.build(context, property),
|
||||
type = openApiTypeBuilder.build(ModelTypePath(data.name, name), property),
|
||||
description = property.description,
|
||||
deprecated = property.deprecated == true,
|
||||
)
|
||||
}.toSet()
|
||||
)
|
||||
|
||||
private fun buildEnumModel(data: Schema<Any>) = EnumApiModel(
|
||||
name = data.name,
|
||||
description = data.description,
|
||||
deprecated = data.deprecated == true,
|
||||
interfaces = emptySet(),
|
||||
constants = data.enum.orEmpty().map { it.toString() }.toSet(),
|
||||
)
|
||||
}
|
||||
|
@ -33,4 +33,5 @@ object Types {
|
||||
val SERIALIZABLE = ClassName("kotlinx.serialization", "Serializable")
|
||||
val SERIAL_NAME = ClassName("kotlinx.serialization", "SerialName")
|
||||
val USE_SERIALIZERs = ClassName("kotlinx.serialization", "UseSerializers")
|
||||
val JSON_DISCRIMINATOR = ClassName("kotlinx.serialization.json", "JsonClassDiscriminator")
|
||||
}
|
||||
|
@ -4,4 +4,5 @@ interface ApiModel {
|
||||
val name: String
|
||||
val description: String?
|
||||
val deprecated: Boolean
|
||||
val interfaces: Set<String>
|
||||
}
|
||||
|
@ -12,7 +12,11 @@ sealed interface DefaultValue {
|
||||
@JvmInline
|
||||
value class Boolean(val value: kotlin.Boolean) : DefaultValue
|
||||
|
||||
data class EnumMember(val enumType: TypeName, val memberName: kotlin.String) : DefaultValue
|
||||
data class EnumMember(
|
||||
val enumType: TypeName,
|
||||
val memberName: kotlin.String,
|
||||
val serialName: kotlin.String,
|
||||
) : DefaultValue
|
||||
|
||||
/**
|
||||
* Custom value builder used in hooks.
|
||||
|
@ -3,5 +3,6 @@ package org.jellyfin.openapi.model
|
||||
data class EmptyApiModel(
|
||||
override val name: String,
|
||||
override val description: String?,
|
||||
override val deprecated: Boolean
|
||||
override val deprecated: Boolean,
|
||||
override val interfaces: Set<String>,
|
||||
) : ApiModel
|
||||
|
@ -4,5 +4,6 @@ data class EnumApiModel(
|
||||
override val name: String,
|
||||
override val description: String?,
|
||||
override val deprecated: Boolean,
|
||||
val constants: Set<String>
|
||||
override val interfaces: Set<String>,
|
||||
val constants: Set<String>,
|
||||
) : ApiModel
|
||||
|
@ -7,6 +7,7 @@ import io.swagger.v3.oas.models.media.Schema
|
||||
import io.swagger.v3.parser.core.models.SwaggerParseResult
|
||||
import net.pearx.kasechange.CaseFormat
|
||||
import net.pearx.kasechange.toPascalCase
|
||||
import org.jellyfin.openapi.OpenApiGeneratorError
|
||||
import org.jellyfin.openapi.builder.openapi.OpenApiModelBuilder
|
||||
|
||||
class GeneratorContext(
|
||||
@ -36,6 +37,18 @@ class GeneratorContext(
|
||||
}
|
||||
}
|
||||
|
||||
fun addModelInterface(model: ApiModel, interfaceName: String) {
|
||||
val interfaces = model.interfaces + interfaceName
|
||||
_models[model.name] = when (model) {
|
||||
is EmptyApiModel -> model.copy(interfaces = interfaces)
|
||||
is EnumApiModel -> model.copy(interfaces = interfaces)
|
||||
is InterfaceApiModel -> model.copy(interfaces = interfaces)
|
||||
is ObjectApiModel -> model.copy(interfaces = interfaces)
|
||||
|
||||
else -> throw OpenApiGeneratorError("Unknown model class ${model::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plusAssign(service: ApiService) {
|
||||
_apiServices.add(service)
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
package org.jellyfin.openapi.model
|
||||
|
||||
data class InterfaceApiModel(
|
||||
override val name: String,
|
||||
override val description: String?,
|
||||
override val deprecated: Boolean,
|
||||
override val interfaces: Set<String>,
|
||||
val polymorphicDiscriminator: String?,
|
||||
val properties: Set<InterfaceApiModelProperty>,
|
||||
) : ApiModel
|
@ -0,0 +1,14 @@
|
||||
package org.jellyfin.openapi.model
|
||||
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
|
||||
data class InterfaceApiModelProperty(
|
||||
val name: String,
|
||||
val originalName: String,
|
||||
val type: TypeName,
|
||||
val description: String?,
|
||||
val deprecated: Boolean,
|
||||
) {
|
||||
fun typeMatches(other: InterfaceApiModelProperty): Boolean =
|
||||
other === this || (name == other.name && originalName == other.originalName && type == other.type)
|
||||
}
|
@ -4,5 +4,6 @@ data class ObjectApiModel(
|
||||
override val name: String,
|
||||
override val description: String?,
|
||||
override val deprecated: Boolean,
|
||||
val properties: Set<ObjectApiModelProperty>
|
||||
override val interfaces: Set<String>,
|
||||
val properties: Set<ObjectApiModelProperty>,
|
||||
) : ApiModel
|
||||
|
@ -8,5 +8,5 @@ data class ObjectApiModelProperty(
|
||||
val defaultValue: DefaultValue?,
|
||||
val type: TypeName,
|
||||
val description: String?,
|
||||
val deprecated: Boolean
|
||||
val deprecated: Boolean,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user