223 lines
No EOL
7.7 KiB
Kotlin
223 lines
No EOL
7.7 KiB
Kotlin
package nl.kallestruik.dlib.config
|
|
|
|
import kotlinx.serialization.descriptors.*
|
|
import kotlinx.serialization.json.JsonPrimitive
|
|
import nl.kallestruik.dlib.config.annotations.*
|
|
import nl.kallestruik.dlib.config.data.ConfigSchema
|
|
import nl.kallestruik.dlib.config.data.ConfigValueConstraints
|
|
import nl.kallestruik.dlib.config.data.ConfigValueType
|
|
import nl.kallestruik.dlib.exceptions.ConfigSchemaGenerationException
|
|
import kotlin.math.min
|
|
import kotlin.math.max
|
|
|
|
fun SerialDescriptor.toConfigSchema(annotations: List<Annotation> = listOf()): ConfigSchema {
|
|
val allAnnotations = annotations + this.annotations
|
|
return when (this.kind) {
|
|
SerialKind.ENUM -> createEnumConfigSchema(this, allAnnotations)
|
|
PrimitiveKind.BOOLEAN -> createBooleanConfigSchema(allAnnotations)
|
|
PrimitiveKind.BYTE -> createIntegerConfigSchema(allAnnotations, Byte.MAX_VALUE.toLong(), Byte.MIN_VALUE.toLong())
|
|
PrimitiveKind.CHAR -> createStringConfigSchema(allAnnotations, maxLength=1)
|
|
PrimitiveKind.SHORT -> createIntegerConfigSchema(allAnnotations, Short.MAX_VALUE.toLong(), Short.MIN_VALUE.toLong())
|
|
PrimitiveKind.INT -> createIntegerConfigSchema(allAnnotations, Int.MAX_VALUE.toLong(), Int.MIN_VALUE.toLong())
|
|
PrimitiveKind.LONG -> createIntegerConfigSchema(allAnnotations, Long.MAX_VALUE, Long.MIN_VALUE)
|
|
PrimitiveKind.FLOAT -> createFloatConfigSchema(allAnnotations, Float.MAX_VALUE.toDouble(), -Float.MAX_VALUE.toDouble())
|
|
PrimitiveKind.DOUBLE -> createFloatConfigSchema(allAnnotations, Double.MAX_VALUE, -Double.MAX_VALUE)
|
|
PrimitiveKind.STRING -> createStringConfigSchema(allAnnotations)
|
|
StructureKind.LIST -> createListConfigSchema(this, allAnnotations)
|
|
StructureKind.MAP -> createMapConfigSchema(this, allAnnotations)
|
|
StructureKind.OBJECT, StructureKind.CLASS -> createObjectConfigSchema(this, allAnnotations)
|
|
else -> throw ConfigSchemaGenerationException("Creating a schema for type ${this.kind} is not currently supported.")
|
|
}
|
|
}
|
|
|
|
private fun createObjectConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val children = descriptor.elementDescriptors
|
|
.mapIndexed { i, child ->
|
|
descriptor.getElementName(i) to child.toConfigSchema(descriptor.getElementAnnotations(i))
|
|
}.toMap()
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.OBJECT,
|
|
children = children,
|
|
description = description,
|
|
)
|
|
}
|
|
|
|
private fun createListConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.LIST,
|
|
description = description,
|
|
items = descriptor.elementDescriptors.first().toConfigSchema(),
|
|
)
|
|
}
|
|
|
|
private fun createMapConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val allowedKeys = annotations
|
|
.filterIsInstance<AllowedKeys>()
|
|
.flatMap { annotation -> annotation.keys
|
|
.map { listOf(it) }
|
|
}
|
|
|
|
val allowedKeysExclusive = annotations
|
|
.filterIsInstance<AllowedKeysExclusive>()
|
|
.map { it.exclusiveKeys.toList() }
|
|
|
|
if (descriptor.elementDescriptors.first().kind != PrimitiveKind.STRING) {
|
|
throw ConfigSchemaGenerationException("Map keys have to be strings.")
|
|
}
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.MAP,
|
|
description = description,
|
|
items = descriptor.elementDescriptors.last().toConfigSchema(),
|
|
constraints = ConfigValueConstraints(
|
|
allowedKeys = allowedKeys + allowedKeysExclusive,
|
|
),
|
|
)
|
|
}
|
|
|
|
private fun createStringConfigSchema(annotations: List<Annotation>, maxLength: Int? = null): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val allowedValues = annotations
|
|
.filterIsInstance<StringEnum>()
|
|
.flatMap { it.values.toList() }
|
|
|
|
val default = annotations
|
|
.filterIsInstance<DefaultString>()
|
|
.firstOrNull()
|
|
?.let { JsonPrimitive(it.default) }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.STRING,
|
|
description = description,
|
|
default = default,
|
|
constraints = ConfigValueConstraints(
|
|
enum = allowedValues.ifEmpty { null },
|
|
maxLength = maxLength,
|
|
),
|
|
)
|
|
}
|
|
|
|
private fun createEnumConfigSchema(descriptor: SerialDescriptor, annotations: List<Annotation>): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val allowedValues = descriptor.elementNames.toList()
|
|
|
|
val default = annotations
|
|
.filterIsInstance<DefaultString>()
|
|
.firstOrNull()
|
|
?.let { JsonPrimitive(it.default) }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.STRING,
|
|
description = description,
|
|
default = default,
|
|
constraints = ConfigValueConstraints(
|
|
enum = allowedValues.ifEmpty { null },
|
|
),
|
|
)
|
|
}
|
|
|
|
private fun createBooleanConfigSchema(annotations: List<Annotation>): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val default = annotations
|
|
.filterIsInstance<DefaultBoolean>()
|
|
.firstOrNull()
|
|
?.let { JsonPrimitive(it.default) }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.BOOLEAN,
|
|
description = description,
|
|
default = default,
|
|
)
|
|
}
|
|
|
|
private fun createIntegerConfigSchema(annotations: List<Annotation>, maxValue: Long, minValue: Long): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val max = annotations
|
|
.filterIsInstance<MaxInt>()
|
|
.map { it.max }
|
|
.reduceOrNull { acc, next ->
|
|
return@reduceOrNull max(acc, next)
|
|
} ?: maxValue
|
|
|
|
val min = annotations
|
|
.filterIsInstance<MinInt>()
|
|
.map { it.min }
|
|
.reduceOrNull { acc, next ->
|
|
min(acc, next)
|
|
} ?: minValue
|
|
|
|
val default = annotations
|
|
.filterIsInstance<DefaultInt>()
|
|
.firstOrNull()
|
|
?.let { JsonPrimitive(it.default) }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.INTEGER,
|
|
description = description,
|
|
default = default,
|
|
constraints = ConfigValueConstraints(
|
|
maxValueInt = max,
|
|
minValueInt = min,
|
|
),
|
|
)
|
|
}
|
|
|
|
private fun createFloatConfigSchema(annotations: List<Annotation>, maxValue: Double, minValue: Double): ConfigSchema {
|
|
val description = annotations
|
|
.filterIsInstance<Description>()
|
|
.flatMap { it.lines.toList() }
|
|
|
|
val max = annotations
|
|
.filterIsInstance<MaxFloat>()
|
|
.map { it.max }
|
|
.reduceOrNull { acc, next ->
|
|
return@reduceOrNull max(acc, next)
|
|
} ?: maxValue
|
|
|
|
val min = annotations
|
|
.filterIsInstance<MinFloat>()
|
|
.map { it.min }
|
|
.reduceOrNull { acc, next ->
|
|
min(acc, next)
|
|
} ?: minValue
|
|
|
|
val default = annotations
|
|
.filterIsInstance<DefaultFloat>()
|
|
.firstOrNull()
|
|
?.let { JsonPrimitive(it.default) }
|
|
|
|
return ConfigSchema(
|
|
type = ConfigValueType.FLOAT,
|
|
description = description,
|
|
default = default,
|
|
constraints = ConfigValueConstraints(
|
|
maxValueFloat = max,
|
|
minValueFloat = min,
|
|
),
|
|
)
|
|
} |