From b8a346b286a1d51d9abf432ea75d0c8c9f07bcc7 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Tue, 24 Oct 2023 22:32:38 +0400 Subject: [PATCH] Add InstantPocketbase type with custom Serializable --- .../github/agrevster/pocketbaseKotlin/Util.kt | 7 +-- .../models/utils/BaseModel.kt | 30 +++---------- .../models/utils/InsantPocketbase.kt | 43 +++++++++++++++++++ .../pocketbaseKotlin/services/LogService.kt | 10 +---- .../kotlin/models/utils/InstantPocketbase.kt | 35 +++++++++++++++ 5 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/InsantPocketbase.kt create mode 100644 src/commonTest/kotlin/models/utils/InstantPocketbase.kt diff --git a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/Util.kt b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/Util.kt index f3ef81c..246854e 100644 --- a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/Util.kt +++ b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/Util.kt @@ -1,13 +1,14 @@ package io.github.agrevster.pocketbaseKotlin import io.github.agrevster.pocketbaseKotlin.models.utils.BaseModel +import io.github.agrevster.pocketbaseKotlin.models.utils.parsePocketbase +import io.github.agrevster.pocketbaseKotlin.models.utils.toStringPocketbase import io.ktor.client.call.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin import kotlinx.datetime.Instant -import kotlinx.datetime.toInstant import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -91,9 +92,9 @@ public fun Boolean?.toJsonPrimitiveOrNull(): JsonPrimitive = JsonPrimitive(this) public fun Number.toJsonPrimitive(): JsonPrimitive = JsonPrimitive(this) public fun Instant?.toJsonPrimitive(): JsonPrimitive? = - if (this.toString() != "null") JsonPrimitive(this.toString().replace("T", " ")) else null + if (this != null) JsonPrimitive(this.toStringPocketbase()) else null -public fun JsonPrimitive.toInstant(): Instant = this.toString().replace(" ", "T").removeSurrounding("\"").toInstant() +public fun JsonPrimitive.toInstant(): Instant = Instant.parsePocketbase(this.toString().removeSurrounding("\"")) public fun JsonPrimitive.toNumber(): Double = this.toString().toDouble() diff --git a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/BaseModel.kt b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/BaseModel.kt index c98ef52..67c9161 100644 --- a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/BaseModel.kt +++ b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/BaseModel.kt @@ -1,46 +1,26 @@ package io.github.agrevster.pocketbaseKotlin.models.utils import io.github.agrevster.pocketbaseKotlin.PocketKtInternal -import kotlinx.datetime.Instant -import kotlinx.datetime.toInstant -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient @Serializable /** * The base class used for all [Pocketbase](https://pocketbase.io) models used * * @property [id] The unique ID of the model - * @property [initialCreated] the raw created field from the model formatted in the GO timestamp format. - * Use [created] to access the type of [Instant] - * @property [initialUpdated] the raw updated field from the model formatted in the GO timestamp format. - * Use [updated] to access the type of [Instant] + * @property [created] the created field from the model. + * @property [updated] the updated field from the model. * */ public open class BaseModel(public open val id: String? = null) { + public val created: InstantPocketbase? = null - @SerialName("created") - @PocketKtInternal - public val initialCreated: String? = null - - @SerialName("updated") - @PocketKtInternal - public val initialUpdated: String? = null - - @OptIn(PocketKtInternal::class) - @Transient - public val created: Instant? = initialCreated?.replace(" ", "T")?.toInstant() - - @OptIn(PocketKtInternal::class) - @Transient - public val updated: Instant? = initialUpdated?.replace(" ", "T")?.toInstant() - + public val updated: InstantPocketbase? = null @OptIn(PocketKtInternal::class) override fun toString(): String { - return "BaseModel(id=$id, created=$initialCreated, updated=$initialUpdated)" + return "BaseModel(id=$id, created=$created, updated=$updated)" } } \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/InsantPocketbase.kt b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/InsantPocketbase.kt new file mode 100644 index 0000000..d6b99e8 --- /dev/null +++ b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/models/utils/InsantPocketbase.kt @@ -0,0 +1,43 @@ +package io.github.agrevster.pocketbaseKotlin.models.utils + +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +public typealias InstantPocketbase = @Serializable(InstantPocketbaseSerializer::class) Instant + +internal fun Int.padded(length: Int): String { + return this.toString().padStart(length, '0') +} + +public fun Instant.toStringPocketbase(): String { + val dt = this.toLocalDateTime(TimeZone.UTC) + + return "${dt.year}-${dt.monthNumber.padded(2)}-${dt.dayOfMonth.padded(2)} ${dt.hour.padded(2)}:${dt.minute.padded(2)}:${dt.second.padded(2)}.${dt.nanosecond / 1_000_000}Z" +} + +public fun Instant.Companion.parsePocketbase(string: String): InstantPocketbase { + return parse(string.replace(' ', 'T')) +} + +public object InstantPocketbaseSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Instant { + val dateStr = decoder.decodeString() + return Instant.parsePocketbase(dateStr) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeString(value.toStringPocketbase()) + } + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/services/LogService.kt b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/services/LogService.kt index 358b485..21fb82c 100644 --- a/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/services/LogService.kt +++ b/src/commonMain/kotlin/io/github/agrevster/pocketbaseKotlin/services/LogService.kt @@ -5,23 +5,17 @@ import io.github.agrevster.pocketbaseKotlin.dsl.query.Filter import io.github.agrevster.pocketbaseKotlin.dsl.query.ShowFields import io.github.agrevster.pocketbaseKotlin.dsl.query.SortFields import io.github.agrevster.pocketbaseKotlin.models.LogRequest +import io.github.agrevster.pocketbaseKotlin.models.utils.InstantPocketbase import io.github.agrevster.pocketbaseKotlin.models.utils.ListResult import io.github.agrevster.pocketbaseKotlin.services.utils.BaseService import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.http.* -import kotlinx.datetime.Instant -import kotlinx.datetime.toInstant -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient public class LogService(client: io.github.agrevster.pocketbaseKotlin.PocketbaseClient) : BaseService(client) { @Serializable - public data class HourlyStats(val total: Int, @SerialName("date") val initialDate: String) { - @Transient - val date: Instant = initialDate.replace(" ", "T").toInstant() - } + public data class HourlyStats(val total: Int, val date: InstantPocketbase) /** * Returns a paginated request logs list. diff --git a/src/commonTest/kotlin/models/utils/InstantPocketbase.kt b/src/commonTest/kotlin/models/utils/InstantPocketbase.kt new file mode 100644 index 0000000..52b9cbe --- /dev/null +++ b/src/commonTest/kotlin/models/utils/InstantPocketbase.kt @@ -0,0 +1,35 @@ +package models.utils + +import TestingUtils +import io.github.agrevster.pocketbaseKotlin.models.utils.InstantPocketbase +import io.github.agrevster.pocketbaseKotlin.models.utils.InstantPocketbaseSerializer +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import kotlin.test.Test +import kotlin.test.assertEquals + +class InstantPocketbaseTest : TestingUtils() { + @Test + fun testSerializeDeserialize(): Unit { + @Serializable + data class TestSerializable(val instant: InstantPocketbase) + + val original = TestSerializable(Instant.parse("2023-10-24T01:02:03.123Z")) + + val encoded = Json.encodeToString(original) + assertEquals( + expected = "{\"instant\":\"2023-10-24 01:02:03.123Z\"}", + actual = encoded, + ) + val decoded: TestSerializable = Json.decodeFromString(encoded) + assertEquals( + expected = original, + actual = decoded, + ) + } +} \ No newline at end of file