From d45eb2d824307a14ebba961e4b7a72376d06a55c Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 20:56:18 +0200 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20feat(constraints):=20add=20Stri?= =?UTF-8?q?ngOrDefault=20type=20support=20in=20various=20constraints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - introduce StringOrDefault type handling in configSizeOrNull, asPathOrNull, and other constraints - implement factory methods for StringOrDefault in Contains, EndsWith, MaxLength, MinLength, and others - update DisallowValues and other constraints to utilize StringOrDefault for validation --- gradle.properties | 4 +- .../constraints/CollectionConstraintUtils.kt | 5 +- .../config/constraints/ConstraintsUtils.kt | 11 ++++ .../api/core/config/constraints/Contains.kt | 10 +++ .../core/config/constraints/DisallowValues.kt | 2 +- .../api/core/config/constraints/EndsWith.kt | 10 +++ .../core/config/constraints/ExistingFile.kt | 4 +- .../core/config/constraints/MaxDuration.kt | 11 ++++ .../api/core/config/constraints/MaxLength.kt | 10 +++ .../api/core/config/constraints/MaxNumber.kt | 22 +++++++ .../core/config/constraints/MinDuration.kt | 11 ++++ .../api/core/config/constraints/MinLength.kt | 11 ++++ .../api/core/config/constraints/MinNumber.kt | 22 +++++++ .../core/config/constraints/NegativeNumber.kt | 22 +++++++ .../core/config/constraints/PositiveNumber.kt | 24 ++++++- .../surf/api/core/config/constraints/Range.kt | 27 ++++++++ .../api/core/config/constraints/StartsWith.kt | 11 ++++ .../serializer/SpongeConfigSerializers.kt | 19 ++++++ .../api/core/config/type/StringOrDefault.kt | 62 +++++++++++++++++++ 19 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt create mode 100644 surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt diff --git a/gradle.properties b/gradle.properties index deeda5d4..3cdc441c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=26.1.2 group=dev.slne.surf.api -version=3.16.0 +version=3.17.0 relocationPrefix=dev.slne.surf.api.libs -snapshot=false +snapshot=true diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt index b1f571f8..8231b97e 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt @@ -1,10 +1,13 @@ package dev.slne.surf.api.core.config.constraints -internal fun Any?.configSizeOrNull(): Int? = when (this) { +import dev.slne.surf.api.core.config.type.StringOrDefault + +internal tailrec fun Any?.configSizeOrNull(): Int? = when (this) { null -> null is Collection<*> -> size is Map<*, *> -> size is Array<*> -> size is CharSequence -> length + is StringOrDefault -> value?.configSizeOrNull() else -> null } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt new file mode 100644 index 00000000..2e4a3ec0 --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.api.core.config.constraints + +import dev.slne.surf.api.core.config.type.StringOrDefault + +internal fun Any?.toStringOrDefaultAware(): String { + if (this is StringOrDefault) { + return value.toString() + } + + return toString() +} \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Contains.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Contains.kt index 46b77065..dfc758e3 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Contains.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Contains.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -23,5 +24,14 @@ annotation class Contains(val value: String) { } } } + + internal object FactoryStringOrDefault : Constraint.Factory { + override fun make(data: Contains, type: Type): Constraint = { valueOrDefault -> + val value = valueOrDefault?.value + if (value != null && !value.contains(data.value)) { + throw SerializationException("String must contain '${data.value}'") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt index 740e6ad8..740fb410 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt @@ -18,7 +18,7 @@ annotation class DisallowValues(vararg val values: String) { companion object { internal object Factory : Constraint.Factory { override fun make(data: DisallowValues, type: Type): Constraint = { value -> - if (value != null && data.values.any { it.equals(value.toString(), ignoreCase = true) }) { + if (value != null && data.values.any { it.equals(value.toStringOrDefaultAware(), ignoreCase = true) }) { throw SerializationException("Value '$value' is not allowed") } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/EndsWith.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/EndsWith.kt index c013118f..e6efae9e 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/EndsWith.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/EndsWith.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -23,5 +24,14 @@ annotation class EndsWith(val suffix: String) { } } } + + internal object FactoryStringOrDefault : Constraint.Factory { + override fun make(data: EndsWith, type: Type): Constraint = { stringOrDefault -> + val value = stringOrDefault?.value + if (value != null && !value.endsWith(data.suffix)) { + throw SerializationException("String must end with '${data.suffix}'") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ExistingFile.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ExistingFile.kt index 04759854..4e91ea7c 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ExistingFile.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ExistingFile.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.io.File @@ -41,12 +42,13 @@ annotation class ExistingFile { } } -internal fun Any?.asPathOrNull(): Path? { +internal tailrec fun Any?.asPathOrNull(): Path? { return when (this) { null -> null is Path -> this is File -> toPath() is String -> runCatching { Path.of(this) }.getOrNull() + is StringOrDefault -> value?.asPathOrNull() else -> null } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxDuration.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxDuration.kt index 1bd48122..2f46c274 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxDuration.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxDuration.kt @@ -1,6 +1,7 @@ package dev.slne.surf.api.core.config.constraints import dev.slne.surf.api.core.config.type.ConfigDuration +import dev.slne.surf.api.core.config.type.DurationOrDisabled import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -25,5 +26,15 @@ annotation class MaxDuration(val seconds: Long) { } } } + + internal object FactoryDurationOrDisabled : Constraint.Factory { + override fun make(data: MaxDuration, type: Type): Constraint = { durationOrDisabled -> + val value = durationOrDisabled?.value + + if (value != null && value.inWholeSeconds > data.seconds) { + throw SerializationException("Duration is too long: ${value}, expected <= ${data.seconds}s") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxLength.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxLength.kt index 38f979de..471cce5f 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxLength.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxLength.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -24,5 +25,14 @@ annotation class MaxLength(val max: Int) { } } } + + internal object FactoryStringOrDefault : Constraint.Factory { + override fun make(data: MaxLength, type: Type): Constraint = { stringOrDefault -> + val value = stringOrDefault?.value + if (value != null && value.length > data.max) { + throw SerializationException("String is too long: ${value.length}, expected <= ${data.max}") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxNumber.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxNumber.kt index cbe07763..85b64d0c 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxNumber.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MaxNumber.kt @@ -1,5 +1,7 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.number.DoubleOr +import dev.slne.surf.api.core.config.type.number.IntOr import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -35,5 +37,25 @@ annotation class MaxNumber(val max: Double) { } } } + + internal object FactoryIntOr : Constraint.Factory { + override fun make(data: MaxNumber, type: Type): Constraint = { intOr -> + val number = intOr?.value + + if (number != null && number > data.max) { + throw SerializationException(type, "Number is too big: $number, expected <= ${data.max}") + } + } + } + + internal object FactoryDoubleOr : Constraint.Factory { + override fun make(data: MaxNumber, type: Type): Constraint = { doubleOr -> + val number = doubleOr?.value + + if (number != null && number > data.max) { + throw SerializationException(type, "Number is too big: $number, expected <= ${data.max}") + } + } + } } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinDuration.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinDuration.kt index 85795969..2684b882 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinDuration.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinDuration.kt @@ -1,6 +1,7 @@ package dev.slne.surf.api.core.config.constraints import dev.slne.surf.api.core.config.type.ConfigDuration +import dev.slne.surf.api.core.config.type.DurationOrDisabled import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -25,5 +26,15 @@ annotation class MinDuration(val seconds: Long) { } } } + + internal object FactoryDurationOrDisabled : Constraint.Factory { + override fun make(data: MinDuration, type: Type): Constraint = { durationOrDisabled -> + val value = durationOrDisabled?.value + + if (value != null && value.inWholeSeconds < data.seconds) { + throw SerializationException("Duration is too short: ${value}, expected >= ${data.seconds}s") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinLength.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinLength.kt index 81f5985b..cd12b69e 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinLength.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinLength.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -23,5 +24,15 @@ annotation class MinLength(val min: Int) { } } } + + internal object FactoryStringOrDefault : Constraint.Factory { + override fun make(data: MinLength, type: Type): Constraint = { stringOrDefault -> + val value = stringOrDefault?.value + + if (value != null && value.length < data.min) { + throw SerializationException("String is too short: ${value.length}, expected >= ${data.min}") + } + } + } } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinNumber.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinNumber.kt index 1d6edca4..a2c87d99 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinNumber.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/MinNumber.kt @@ -1,5 +1,7 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.number.DoubleOr +import dev.slne.surf.api.core.config.type.number.IntOr import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -35,6 +37,26 @@ annotation class MinNumber(val min: Double) { } } } + + internal object FactoryIntOr : Constraint.Factory { + override fun make(data: MinNumber, type: Type): Constraint = { intOr -> + val number = intOr?.value + + if (number != null && number < data.min) { + throw SerializationException(type, "Number is too small: $number, expected >= ${data.min}") + } + } + } + + internal object FactoryDoubleOr : Constraint.Factory { + override fun make(data: MinNumber, type: Type): Constraint = { doubleOr -> + val number = doubleOr?.value + + if (number != null && number < data.min) { + throw SerializationException(type, "Number is too small: $number, expected >= ${data.min}") + } + } + } } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/NegativeNumber.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/NegativeNumber.kt index f28f51da..8c48e515 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/NegativeNumber.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/NegativeNumber.kt @@ -1,5 +1,7 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.number.DoubleOr +import dev.slne.surf.api.core.config.type.number.IntOr import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -21,5 +23,25 @@ annotation class NegativeNumber { } } } + + internal object FactoryIntOr : Constraint.Factory { + override fun make(data: NegativeNumber, type: Type): Constraint = { intOr -> + val value = intOr?.value + + if (value != null && value >= 0) { + throw SerializationException("Number must be negative: $value, expected < 0") + } + } + } + + internal object FactoryDoubleOr : Constraint.Factory { + override fun make(data: NegativeNumber, type: Type): Constraint = { doubleOr -> + val value = doubleOr?.value + + if (value != null && value >= 0.0) { + throw SerializationException("Number must be negative: $value, expected < 0") + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/PositiveNumber.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/PositiveNumber.kt index cad0081e..9a7000ca 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/PositiveNumber.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/PositiveNumber.kt @@ -1,5 +1,7 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.number.DoubleOr +import dev.slne.surf.api.core.config.type.number.IntOr import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -29,7 +31,27 @@ annotation class PositiveNumber { */ internal object Factory : Constraint.Factory { override fun make(data: PositiveNumber, type: Type): Constraint = { number -> - if (number != null && number.toDouble() <= 0) { + if (number != null && number.toDouble() <= 0.0) { + throw SerializationException("Number is not positive: $number, expected > 0") + } + } + } + + internal object FactoryIntOr : Constraint.Factory { + override fun make(data: PositiveNumber, type: Type): Constraint = { intOr -> + val number = intOr?.value + + if (number != null && number <= 0) { + throw SerializationException("Number is not positive: $number, expected > 0") + } + } + } + + internal object FactoryDoubleOr : Constraint.Factory { + override fun make(data: PositiveNumber, type: Type): Constraint = { doubleOr -> + val number = doubleOr?.value + + if (number != null && number <= 0.0) { throw SerializationException("Number is not positive: $number, expected > 0") } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Range.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Range.kt index 26ca037c..868b8011 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Range.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/Range.kt @@ -1,5 +1,7 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.number.DoubleOr +import dev.slne.surf.api.core.config.type.number.IntOr import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -27,5 +29,30 @@ annotation class Range(val min: Double, val max: Double) { } } } + + internal object FactoryIntOr : Constraint.Factory { + override fun make(data: Range, type: Type): Constraint = { intOr -> + val value = intOr?.value + + if (value != null) { + val double = value.toDouble() + if (double < data.min || double > data.max) { + throw SerializationException("Number is out of range: $value, expected ${data.min}..${data.max}") + } + } + } + } + + internal object FactoryDoubleOr : Constraint.Factory { + override fun make(data: Range, type: Type): Constraint = { doubleOr -> + val value = doubleOr?.value + + if (value != null) { + if (value < data.min || value > data.max) { + throw SerializationException("Number is out of range: $value, expected ${data.min}..${data.max}") + } + } + } + } } } \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/StartsWith.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/StartsWith.kt index 255a28ab..642ac983 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/StartsWith.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/StartsWith.kt @@ -1,5 +1,6 @@ package dev.slne.surf.api.core.config.constraints +import dev.slne.surf.api.core.config.type.StringOrDefault import org.spongepowered.configurate.objectmapping.meta.Constraint import org.spongepowered.configurate.serialize.SerializationException import java.lang.reflect.Type @@ -23,5 +24,15 @@ annotation class StartsWith(val prefix: String) { } } } + + internal object FactoryStringOrDefault : Constraint.Factory { + override fun make(data: StartsWith, type: Type): Constraint = { stringOrDefault -> + val value = stringOrDefault?.value + + if (value != null && !value.startsWith(data.prefix)) { + throw SerializationException("String must start with '${data.prefix}'") + } + } + } } } diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/serializer/SpongeConfigSerializers.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/serializer/SpongeConfigSerializers.kt index e36053d5..8dc2719a 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/serializer/SpongeConfigSerializers.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/serializer/SpongeConfigSerializers.kt @@ -6,6 +6,7 @@ import dev.slne.surf.api.core.config.serializer.collection.map.MapSerializer import dev.slne.surf.api.core.config.type.BooleanOrDefault import dev.slne.surf.api.core.config.type.ConfigDuration import dev.slne.surf.api.core.config.type.DurationOrDisabled +import dev.slne.surf.api.core.config.type.StringOrDefault import dev.slne.surf.api.core.config.type.number.DoubleOr import dev.slne.surf.api.core.config.type.number.IntOr import dev.slne.surf.api.core.minimessage.SurfMiniMessageHolder @@ -87,6 +88,7 @@ abstract class SpongeConfigSerializers { builder.register(KeySerializer) builder.register(ConfigDuration.Serializer) builder.register(BooleanOrDefault.Serializer) + builder.register(StringOrDefault.Serializer) builder.register(DurationOrDisabled.Serializer) builder.register(IntOr.Default.Serializer) builder.register(IntOr.Disabled.Serializer) @@ -152,23 +154,40 @@ abstract class SpongeConfigSerializers { ObjectMapper.factoryBuilder() .addDiscoverer(dataClassFieldDiscoverer()) .addConstraint(PositiveNumber.Companion.Factory) + .addConstraint(PositiveNumber.Companion.FactoryIntOr) + .addConstraint(PositiveNumber.Companion.FactoryDoubleOr) .addConstraint(NegativeNumber.Companion.Factory) + .addConstraint(NegativeNumber.Companion.FactoryIntOr) + .addConstraint(NegativeNumber.Companion.FactoryDoubleOr) .addConstraint(MinNumber.Companion.Factory) + .addConstraint(MinNumber.Companion.FactoryIntOr) + .addConstraint(MinNumber.Companion.FactoryDoubleOr) .addConstraint(MaxNumber.Companion.Factory) + .addConstraint(MaxNumber.Companion.FactoryIntOr) + .addConstraint(MaxNumber.Companion.FactoryDoubleOr) .addConstraint(NotBlank.Companion.Factory) .addConstraint(Trimmed.Companion.Factory) .addConstraint(MaxLength.Companion.Factory) + .addConstraint(MaxLength.Companion.FactoryStringOrDefault) .addConstraint(MinLength.Companion.Factory) + .addConstraint(MinLength.Companion.FactoryStringOrDefault) .addConstraint(StartsWith.Companion.Factory) + .addConstraint(StartsWith.Companion.FactoryStringOrDefault) .addConstraint(EndsWith.Companion.Factory) + .addConstraint(EndsWith.Companion.FactoryStringOrDefault) .addConstraint(Contains.Companion.Factory) + .addConstraint(Contains.Companion.FactoryStringOrDefault) .addConstraint(Range.Companion.Factory) + .addConstraint(Range.Companion.FactoryIntOr) + .addConstraint(Range.Companion.FactoryDoubleOr) .addConstraint(MinSize.Companion.Factory) .addConstraint(MaxSize.Companion.Factory) .addConstraint(NotEmpty.Companion.Factory) .addConstraint(NoDuplicates.Companion.Factory) .addConstraint(MinDuration.Companion.Factory) + .addConstraint(MinDuration.Companion.FactoryDurationOrDisabled) .addConstraint(MaxDuration.Companion.Factory) + .addConstraint(MaxDuration.Companion.FactoryDurationOrDisabled) .addConstraint(DisallowValues.Companion.Factory) .addConstraint(Namespace.Companion.Factory) .addConstraint(ExistingFile.Companion.Factory) diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt new file mode 100644 index 00000000..72e362dd --- /dev/null +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt @@ -0,0 +1,62 @@ +package dev.slne.surf.api.core.config.type + +import org.spongepowered.configurate.serialize.ScalarSerializer +import java.lang.reflect.AnnotatedType +import java.util.function.Predicate + +/** + * Represents a configuration value that can either hold a string or use a default fallback value. + * + * The class provides utility to handle values that might explicitly contain a string or defer + * to a caller-provided default value if none is specified. + * + * Features: + * - Allows explicit value encapsulation via the `of` method. + * - Supports the `USE_DEFAULT` singleton to represent fallback default behavior. + * - Provides an `or` infix function to resolve the value with a provided default. + * + * Serialization: + * - The `Serializer` handles the string representation of the value for use in configurations. + * - Serialized values include explicit strings or an internal `__default__` marker for default representation. + * + * Companion Object: + * - Contains helper methods and a constant (`USE_DEFAULT`) for working with default behavior. + */ +@ConsistentCopyVisibility +data class StringOrDefault private constructor(val value: String) { + + infix fun or(default: String): String { + return if (value == DEFAULT_MARKER) default else value + } + + companion object { + private const val DEFAULT_MARKER = "__default__" + + val USE_DEFAULT = StringOrDefault(DEFAULT_MARKER) + + fun of(value: String) = StringOrDefault(value) + } + + internal object Serializer : ScalarSerializer.Annotated(StringOrDefault::class.java) { + override fun deserialize( + type: AnnotatedType?, + obj: Any? + ): StringOrDefault? { + val value = obj.toString() + + return if (value == DEFAULT_MARKER) { + USE_DEFAULT + } else { + StringOrDefault(value) + } + } + + override fun serialize( + type: AnnotatedType?, + item: StringOrDefault, + typeSupported: Predicate?>? + ): Any { + return item.value + } + } +} \ No newline at end of file From 2d68fc28fe625793a7d0af35c0cd410fb1339be6 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 20:56:47 +0200 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=94=A7=20chore(gradle):=20disable=20s?= =?UTF-8?q?napshot=20builds=20in=20gradle.properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3cdc441c..6850f874 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,4 @@ mcVersion=26.1.2 group=dev.slne.surf.api version=3.17.0 relocationPrefix=dev.slne.surf.api.libs -snapshot=true +snapshot=false From 4c83e4b3becb633f2f7813ae0304b0294e2c9b71 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 20:57:28 +0200 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=A8=20feat(config):=20add=20StringOrD?= =?UTF-8?q?efault=20type=20for=20configuration=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../surf-api-core/api/surf-api-core.api | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/surf-api-core/surf-api-core/api/surf-api-core.api b/surf-api-core/surf-api-core/api/surf-api-core.api index ded82280..6b54ca04 100644 --- a/surf-api-core/surf-api-core/api/surf-api-core.api +++ b/surf-api-core/surf-api-core/api/surf-api-core.api @@ -578,6 +578,22 @@ public final class dev/slne/surf/api/core/config/type/DurationOrDisabled { public final class dev/slne/surf/api/core/config/type/DurationOrDisabled$Companion { } +public final class dev/slne/surf/api/core/config/type/StringOrDefault { + public static final field Companion Ldev/slne/surf/api/core/config/type/StringOrDefault$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()Ljava/lang/String; + public fun hashCode ()I + public final fun or (Ljava/lang/String;)Ljava/lang/String; + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/api/core/config/type/StringOrDefault$Companion { + public final fun getUSE_DEFAULT ()Ldev/slne/surf/api/core/config/type/StringOrDefault; + public final fun of (Ljava/lang/String;)Ldev/slne/surf/api/core/config/type/StringOrDefault; +} + public abstract interface annotation class dev/slne/surf/api/core/config/type/number/BelowZeroToEmpty : java/lang/annotation/Annotation { } From 0fa7bafd81f32fdd3d43dd6a652683ec6ce33a59 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 21:06:56 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(config):=20up?= =?UTF-8?q?date=20StringOrDefault=20to=20handle=20nullable=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change value type from String to String? in StringOrDefault data class - modify or() method to return default when value is null - update USE_DEFAULT constant to use null instead of a marker string - adjust serialization to handle nullable StringOrDefault instances --- .../surf/api/core/config/type/StringOrDefault.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt index 72e362dd..56de190c 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt @@ -23,16 +23,14 @@ import java.util.function.Predicate * - Contains helper methods and a constant (`USE_DEFAULT`) for working with default behavior. */ @ConsistentCopyVisibility -data class StringOrDefault private constructor(val value: String) { +data class StringOrDefault private constructor(val value: String?) { - infix fun or(default: String): String { - return if (value == DEFAULT_MARKER) default else value - } + infix fun or(default: String): String = value ?: default companion object { private const val DEFAULT_MARKER = "__default__" - val USE_DEFAULT = StringOrDefault(DEFAULT_MARKER) + val USE_DEFAULT = StringOrDefault(null) fun of(value: String) = StringOrDefault(value) } @@ -42,9 +40,9 @@ data class StringOrDefault private constructor(val value: String) { type: AnnotatedType?, obj: Any? ): StringOrDefault? { - val value = obj.toString() + val value = obj?.toString() - return if (value == DEFAULT_MARKER) { + return if (value == null || value == DEFAULT_MARKER) { USE_DEFAULT } else { StringOrDefault(value) @@ -53,10 +51,10 @@ data class StringOrDefault private constructor(val value: String) { override fun serialize( type: AnnotatedType?, - item: StringOrDefault, + item: StringOrDefault?, typeSupported: Predicate?>? ): Any { - return item.value + return item?.value ?: DEFAULT_MARKER } } } \ No newline at end of file From 50a6f461a7c93446cbde803215ebc976d8b05983 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 21:15:43 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=A8=20feat(StringOrDefault):=20add=20?= =?UTF-8?q?toString=20method=20to=20handle=20nullable=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement toString method to return DEFAULT_MARKER for null values --- .../dev/slne/surf/api/core/config/type/StringOrDefault.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt index 56de190c..72cae64f 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt @@ -27,6 +27,10 @@ data class StringOrDefault private constructor(val value: String?) { infix fun or(default: String): String = value ?: default + override fun toString(): String { + return value ?: DEFAULT_MARKER + } + companion object { private const val DEFAULT_MARKER = "__default__" From ffa65b4300f7e588df956fb846fc5c73a3f9326c Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 21:16:51 +0200 Subject: [PATCH 6/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(constraints):?= =?UTF-8?q?=20update=20value=20comparison=20to=20use=20toString=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replace toStringOrDefaultAware with toString for value comparison - ensure consistent handling of nullable values in DisallowValues constraint --- .../config/constraints/CollectionConstraintUtils.kt | 13 ------------- .../api/core/config/constraints/DisallowValues.kt | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt deleted file mode 100644 index 8231b97e..00000000 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/CollectionConstraintUtils.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.slne.surf.api.core.config.constraints - -import dev.slne.surf.api.core.config.type.StringOrDefault - -internal tailrec fun Any?.configSizeOrNull(): Int? = when (this) { - null -> null - is Collection<*> -> size - is Map<*, *> -> size - is Array<*> -> size - is CharSequence -> length - is StringOrDefault -> value?.configSizeOrNull() - else -> null -} \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt index 740fb410..740e6ad8 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt @@ -18,7 +18,7 @@ annotation class DisallowValues(vararg val values: String) { companion object { internal object Factory : Constraint.Factory { override fun make(data: DisallowValues, type: Type): Constraint = { value -> - if (value != null && data.values.any { it.equals(value.toStringOrDefaultAware(), ignoreCase = true) }) { + if (value != null && data.values.any { it.equals(value.toString(), ignoreCase = true) }) { throw SerializationException("Value '$value' is not allowed") } } From e94a8c81fefeb1b64b55b0d8742130fee1279122 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 21:18:16 +0200 Subject: [PATCH 7/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(constraints):?= =?UTF-8?q?=20update=20DisallowValues=20to=20use=20toString=20method=20for?= =?UTF-8?q?=20value=20comparison?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/core/config/constraints/ConstraintsUtils.kt | 11 ----------- .../api/core/config/constraints/DisallowValues.kt | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt deleted file mode 100644 index 2e4a3ec0..00000000 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/ConstraintsUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.slne.surf.api.core.config.constraints - -import dev.slne.surf.api.core.config.type.StringOrDefault - -internal fun Any?.toStringOrDefaultAware(): String { - if (this is StringOrDefault) { - return value.toString() - } - - return toString() -} \ No newline at end of file diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt index 740fb410..740e6ad8 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/constraints/DisallowValues.kt @@ -18,7 +18,7 @@ annotation class DisallowValues(vararg val values: String) { companion object { internal object Factory : Constraint.Factory { override fun make(data: DisallowValues, type: Type): Constraint = { value -> - if (value != null && data.values.any { it.equals(value.toStringOrDefaultAware(), ignoreCase = true) }) { + if (value != null && data.values.any { it.equals(value.toString(), ignoreCase = true) }) { throw SerializationException("Value '$value' is not allowed") } } From 6034f94d4b4d4b484ded351847452637da47e55a Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Wed, 27 May 2026 21:19:31 +0200 Subject: [PATCH 8/8] =?UTF-8?q?=E2=9C=A8=20feat(StringOrDefault):=20add=20?= =?UTF-8?q?JvmField=20annotation=20to=20USE=5FDEFAULT=20constant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt index 72cae64f..28d1cf4e 100644 --- a/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt +++ b/surf-api-core/surf-api-core/src/main/kotlin/dev/slne/surf/api/core/config/type/StringOrDefault.kt @@ -34,6 +34,7 @@ data class StringOrDefault private constructor(val value: String?) { companion object { private const val DEFAULT_MARKER = "__default__" + @JvmField val USE_DEFAULT = StringOrDefault(null) fun of(value: String) = StringOrDefault(value)