Skip to content
Open
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 @@ -59,6 +59,8 @@ public class VersionedCodec<T : @Serializable Any>(
private val serializer: KSerializer<T>,
private val migration: Migration<T>,
private val versionPath: Path = Path("$file.version"), // TODO: Save to file metadata instead
private val tempPath: Path = Path("$file.temp"),
private val tempVersionPath: Path = Path("$versionPath.temp"),
) : Codec<T> {

override suspend fun decode(): T? =
Expand All @@ -79,12 +81,22 @@ public class VersionedCodec<T : @Serializable Any>(
}

override suspend fun encode(value: T?) {
if (value != null) {
SystemFileSystem.sink(versionPath).buffered().use { json.encode(Int.serializer(), version, it) }
SystemFileSystem.sink(file).buffered().use { json.encode(serializer, value, it) }
} else {
if (value == null) {
SystemFileSystem.delete(versionPath, mustExist = false)
SystemFileSystem.delete(file, mustExist = false)
return
}

try {
SystemFileSystem.sink(tempPath).buffered().use { json.encode(serializer, value, it) }
SystemFileSystem.sink(tempVersionPath).buffered().use { json.encode(Int.serializer(), version, it) }
} catch (e: Throwable) {
SystemFileSystem.delete(tempPath, mustExist = false)
SystemFileSystem.delete(tempVersionPath, mustExist = false)
throw e
}

SystemFileSystem.atomicMove(source = tempPath, destination = file)
SystemFileSystem.atomicMove(source = tempVersionPath, destination = versionPath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,46 @@ import io.github.xxfast.kstore.file.storeOf
import kotlinx.coroutines.test.runTest
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlin.test.assertFailsWith

@Serializable data class CatV0(val name: String, val lives: Int = 9)
@Serializable data class CatV1(val name: String, val lives: Int = 9, val cuteness: Int = 12)
@Serializable data class CatV2(val name: String, val lives: Int = 9, val age: Int = 9 - lives, val kawaiiness: Long)
@Serializable data class CatV3(val name: String, val lives: Int = 9, val age: Int = 9 - lives, val isCute: Boolean)

@Serializable
data class CatV41(val name: String, val friends: Map<String, @Serializable(with = TodoSerializer::class) Int>) {
object TodoSerializer : KSerializer<Int> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Int", PrimitiveKind.INT)
override fun deserialize(decoder: Decoder): Int = TODO("Not yet implemented")
override fun serialize(encoder: Encoder, value: Int): Unit = TODO("Not yet implemented")
}
}

@Serializable
data class CatV42(val name: String, val friends: Map<String, Int>)


val MYLO_V0 = CatV0(name = "mylo", lives = 7)
val MYLO_V1 = CatV1(name = "mylo", lives = 7, cuteness = 12)
val MYLO_V2 = CatV2(name = "mylo", lives = 7, age = 2, kawaiiness = 12L)
val MYLO_V3 = CatV3(name = "mylo", lives = 7, age = 2, isCute = true)
val MYLO_V41 = CatV41(name = "mylo", friends = mapOf("oreo" to 5, "kat" to 10))
val MYLO_V42 = CatV42(name = "mylo", friends = mapOf("oreo" to 5, "kat" to 10))

class KVersionedStoreTests {
private val file: Path = Path("test_migration.json")
Expand Down Expand Up @@ -73,10 +95,15 @@ class KVersionedStoreTests {
}
}

private val storeV41: KStore<CatV41> = storeOf(file = file, version = 4)
private val storeV42: KStore<CatV42> = storeOf(file = file, version = 4)

@AfterTest
fun cleanup() {
SystemFileSystem.delete(file, mustExist = false)
SystemFileSystem.delete(Path("${file.name}.version"), mustExist = false)
SystemFileSystem.delete(Path("${file.name}.temp"), mustExist = false)
SystemFileSystem.delete(Path("${file.name}.version.temp"), mustExist = false)
}

@Test
Expand Down Expand Up @@ -127,4 +154,16 @@ class KVersionedStoreTests {
val actual: CatV2? = storeV2.get()
assertEquals(expect, actual)
}

@Test
fun testTransactionalEncode() = runTest {
assertFailsWith<NotImplementedError> { storeV41.set(MYLO_V41) }
assertEquals(null, storeV41.get())

storeV42.set(MYLO_V42)
assertFailsWith<NotImplementedError> { storeV41.set(MYLO_V41) }

assertEquals(MYLO_V42, storeV42.get())
assertFailsWith<NotImplementedError> { storeV41.get() }
}
}