DLib/src/main/kotlin/nl/kallestruik/dlib/config/ConfigSchemeBuilder.kt
Kalle Struik 0b3d31677f
Some checks failed
continuous-integration/drone/push Build is failing
First public release
2022-07-04 12:09:21 +02:00

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