Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,39 +192,30 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
concreteSerializer: KSerializer<Sub>,
allowOverwrite: Boolean = false
) {
// Check for overwrite
val name = concreteSerializer.descriptor.serialName
val baseClassSerializers = polyBase2Serializers.getOrPut(baseClass, ::hashMapOf)
val previousSerializer = baseClassSerializers[concreteClass]
val names = polyBase2NamedSerializers.getOrPut(baseClass, ::hashMapOf)
if (allowOverwrite) {
// Remove previous serializers from name mapping
if (previousSerializer != null) {
names.remove(previousSerializer.descriptor.serialName)
}
// Update mappings
baseClassSerializers[concreteClass] = concreteSerializer
names[name] = concreteSerializer
return
}
// Overwrite prohibited
if (previousSerializer != null) {
if (previousSerializer != concreteSerializer) {
throw SerializerAlreadyRegisteredException(baseClass, concreteClass)
} else {
// Cleanup name mapping
names.remove(previousSerializer.descriptor.serialName)
}

// Check KClass conflict
val previousSerializer = baseClassSerializers[concreteClass]
if (previousSerializer != null && previousSerializer != concreteSerializer) {
if (allowOverwrite) names.remove(previousSerializer.descriptor.serialName)
else throw SerializerAlreadyRegisteredException(baseClass, concreteClass)
}

// Check SerialName conflict
val previousByName = names[name]
if (previousByName != null) {
val conflictingClass = polyBase2Serializers[baseClass]!!.asSequence().find { it.value === previousByName }
throw IllegalArgumentException(
"Multiple polymorphic serializers for base class '$baseClass' " +
"have the same serial name '$name': '$concreteClass' and '$conflictingClass'"
if (previousByName != null && previousByName != concreteSerializer) {
val previousClass = baseClassSerializers.asSequence().find { it.value === previousByName }?.key
?: error("Name $name is registered in the module but no Kotlin class is associated with it.")

if (allowOverwrite) baseClassSerializers.remove(previousClass)
else throw IllegalArgumentException(
"Multiple polymorphic serializers in a scope of '$baseClass' " +
"have the same serial name '$name': $concreteSerializer for '$concreteClass' and $previousByName for '$previousClass'"
)
}
// Overwrite if no conflicts

baseClassSerializers[concreteClass] = concreteSerializer
names[name] = concreteSerializer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.test.Platform
import kotlinx.serialization.test.assertFailsWithMessage
import kotlinx.serialization.test.currentPlatform
import kotlinx.serialization.test.isJs
import kotlinx.serialization.test.isJvm
import kotlin.reflect.*
import kotlin.test.*

Expand Down Expand Up @@ -176,6 +181,10 @@ class ModuleBuildersTest {
@SerialName("C")
class C

@Serializable
@SerialName("C")
class C2

@Serializer(forClass = C::class)
object CSerializer : KSerializer<C> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("AnotherName", StructureKind.OBJECT)
Expand Down Expand Up @@ -206,6 +215,27 @@ class ModuleBuildersTest {
assertNull(result.getPolymorphic(Any::class, serializedClassName = "AnotherName"))
}

@Test
fun testOverwriteWithDifferentClass() {
val c1 = SerializersModule {
polymorphic<Any>(Any::class) {
subclass(C::class)
}
}
val c2 = SerializersModule {
polymorphic<Any>(Any::class) {
subclass(C2::class)
}
}
val classNameMsg = if (currentPlatform == Platform.JS || currentPlatform == Platform.WASM) "class Any" else "class kotlin.Any"
assertFailsWithMessage<IllegalArgumentException>("Multiple polymorphic serializers in a scope of '$classNameMsg' have the same serial name 'C'") { c1 + c2 }
val module = c1 overwriteWith c2
// C should not be registered at all, C2 should be registered both under "C" and C2::class
assertEquals(C2.serializer(), module.getPolymorphic(Any::class, serializedClassName = "C"))
assertNull(module.getPolymorphic(Any::class, C()))
assertEquals(C2.serializer(), module.getPolymorphic(Any::class, C2()))
}

@Test
fun testOverwriteWithSameSerialName() {
val m1 = SerializersModule {
Expand Down
4 changes: 4 additions & 0 deletions core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ inline fun jvmOnly(test: () -> Unit) {
if (isJvm()) test()
}

inline fun <reified T : Throwable> assertFailsWithMessage(message: String, block: () -> Unit) {
val exception = assertFailsWith(T::class, null, block)
assertTrue(exception.message!!.contains(message), "Expected message '${exception.message}' to contain substring '$message'")
}