From e1591720ce53b844e800ac81b755ebac42fa110b Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Mon, 23 Sep 2024 12:13:43 +0200 Subject: [PATCH 1/6] feat(core): add client sync rules paramters --- .../kotlin/com/powersync/PowerSyncDatabase.kt | 10 ++- .../com/powersync/db/PowerSyncDatabaseImpl.kt | 10 ++- .../kotlin/com/powersync/sync/SyncStream.kt | 7 ++- .../kotlin/com/powersync/utils/Json.kt | 18 ++++++ .../kotlin/com/powersync/utils/JsonTest.kt | 62 +++++++++++++++++++ 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index 1b428b93..3158ab3c 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -33,8 +33,12 @@ public interface PowerSyncDatabase : Queries { * TODO: Internal Team - Status changes are reported on [statusStream]. */ - public suspend fun connect(connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L, - retryDelayMs: Long = 5000L) + public suspend fun connect( + connector: PowerSyncBackendConnector, + crudThrottleMs: Long = 1000L, + retryDelayMs: Long = 5000L, + syncRuleParameters: Map? = null + ) /** @@ -94,4 +98,4 @@ public interface PowerSyncDatabase : Queries { * To preserve data in local-only tables, set clearLocal to false. */ public suspend fun disconnectAndClear(clearLocal: Boolean = true) -} \ No newline at end of file +} diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index 0cddc036..7790fa4b 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -85,7 +85,12 @@ internal class PowerSyncDatabaseImpl( } @OptIn(FlowPreview::class) - override suspend fun connect(connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long) { + override suspend fun connect( + connector: PowerSyncBackendConnector, + crudThrottleMs: Long, + retryDelayMs: Long, + syncRuleParameters: Map?) + { // close connection if one is open disconnect() @@ -95,7 +100,8 @@ internal class PowerSyncDatabaseImpl( connector = connector, uploadCrud = suspend { connector.uploadData(this) }, retryDelayMs = retryDelayMs, - logger = logger + logger = logger, + syncRuleParameters = syncRuleParameters ) syncJob = scope.launch { diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index 92eb7422..ae6df835 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -9,6 +9,7 @@ import com.powersync.bucket.WriteCheckpointResponse import co.touchlab.stately.concurrency.AtomicBoolean import com.powersync.connectors.PowerSyncBackendConnector import com.powersync.utils.JsonUtil +import com.powersync.utils.convertMapToJson import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.HttpTimeout @@ -41,7 +42,8 @@ internal class SyncStream( private val connector: PowerSyncBackendConnector, private val uploadCrud: suspend () -> Unit, private val retryDelayMs: Long = 5000L, - private val logger: Logger + private val logger: Logger, + private val syncRuleParameters: Map? ) { private var isUploadingCrud = AtomicBoolean(false) @@ -245,7 +247,8 @@ internal class SyncStream( val req = StreamingSyncRequest( buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) }, - clientId = clientId!! + clientId = clientId!!, + parameters = convertMapToJson(syncRuleParameters) ) streamingSyncRequest(req).collect { value -> diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt index 71fb9e64..5e2cd626 100644 --- a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt +++ b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt @@ -1,6 +1,9 @@ package com.powersync.utils import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive /** * A global instance of a JSON serializer. @@ -12,3 +15,18 @@ internal object JsonUtil { } } +internal fun convertMapToJson(map: Map?): JsonObject { + if (map == null) return JsonObject(emptyMap()) + + val result = map.mapValues { (_, value) -> + when (value) { + is Int -> JsonPrimitive(value) + is Long -> JsonPrimitive(value) + is Double -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + is String -> JsonPrimitive(value) + else -> JsonNull + } + } + return JsonObject(result) +} diff --git a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt new file mode 100644 index 00000000..811fbf56 --- /dev/null +++ b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt @@ -0,0 +1,62 @@ +package com.powersync.utils + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlin.test.* + +class JsonTest { + @Test + fun testEmptyMap() { + val emptyMap = emptyMap() + val result = convertMapToJson(emptyMap) + assertEquals(0, result.size) + } + + @Test + fun testNull() { + val result = convertMapToJson(null) + assertEquals(JsonObject(mapOf()), result) + } + + @Test + fun testIntegerMap() { + val testMap = mapOf("int" to 1) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("int" to JsonPrimitive(1))), result) + } + + @Test + fun testStringMap() { + val testMap = mapOf("string" to "string") + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("string" to JsonPrimitive("string"))), result) + } + + @Test + fun testLongMap() { + val testMap = mapOf("double" to 123L) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("double" to JsonPrimitive(123L))), result) + } + + @Test + fun testBooleanMap() { + val testMap = mapOf("boolean" to false) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("boolean" to JsonPrimitive(false))), result) + } + + @Test + fun testDoubleMap() { + val testMap = mapOf("double" to 0.02) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("double" to JsonPrimitive(0.02))), result) + } + + @Test + fun testMapWithUnsupportedType() { + val testMap = mapOf("unsupported" to object {}) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("unsupported" to JsonPrimitive(null))), result) + } +} \ No newline at end of file From 1bf362c889b16dbf2f67d22f0edf46505f716783 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Mon, 23 Sep 2024 12:22:39 +0200 Subject: [PATCH 2/6] chore: name change --- .../src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt | 2 +- .../kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt | 5 ++--- core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index 3158ab3c..f7cca564 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -37,7 +37,7 @@ public interface PowerSyncDatabase : Queries { connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L, retryDelayMs: Long = 5000L, - syncRuleParameters: Map? = null + syncRulesParameters: Map? = null ) diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index 7790fa4b..64863176 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -21,7 +21,6 @@ import com.powersync.db.schema.Schema import com.powersync.sync.SyncStatus import com.powersync.sync.SyncStream import com.powersync.utils.JsonUtil -import com.powersync.utils.Strings.quoteIdentifier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job @@ -89,7 +88,7 @@ internal class PowerSyncDatabaseImpl( connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long, - syncRuleParameters: Map?) + syncRulesParameters: Map?) { // close connection if one is open disconnect() @@ -101,7 +100,7 @@ internal class PowerSyncDatabaseImpl( uploadCrud = suspend { connector.uploadData(this) }, retryDelayMs = retryDelayMs, logger = logger, - syncRuleParameters = syncRuleParameters + syncRulesParameters = syncRulesParameters ) syncJob = scope.launch { diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index ae6df835..05e206a1 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -43,7 +43,7 @@ internal class SyncStream( private val uploadCrud: suspend () -> Unit, private val retryDelayMs: Long = 5000L, private val logger: Logger, - private val syncRuleParameters: Map? + private val syncRulesParameters: Map? ) { private var isUploadingCrud = AtomicBoolean(false) @@ -248,7 +248,7 @@ internal class SyncStream( val req = StreamingSyncRequest( buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) }, clientId = clientId!!, - parameters = convertMapToJson(syncRuleParameters) + parameters = convertMapToJson(syncRulesParameters) ) streamingSyncRequest(req).collect { value -> From f1d65e3cee5de465a372c3e097260aad648f18c6 Mon Sep 17 00:00:00 2001 From: Dominic Bauer Date: Tue, 1 Oct 2024 11:15:42 +0200 Subject: [PATCH 3/6] chore: rename variable to align with other sdks --- core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt | 3 ++- .../kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt | 4 ++-- core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index f7cca564..da943f2c 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -29,6 +29,7 @@ public interface PowerSyncDatabase : Queries { * * Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms. * Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms. + * Use @param [params] to specify sync parameters from the client * * TODO: Internal Team - Status changes are reported on [statusStream]. */ @@ -37,7 +38,7 @@ public interface PowerSyncDatabase : Queries { connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L, retryDelayMs: Long = 5000L, - syncRulesParameters: Map? = null + params: Map? = null ) diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index 64863176..fb0e8918 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -88,7 +88,7 @@ internal class PowerSyncDatabaseImpl( connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long, - syncRulesParameters: Map?) + params: Map?) { // close connection if one is open disconnect() @@ -100,7 +100,7 @@ internal class PowerSyncDatabaseImpl( uploadCrud = suspend { connector.uploadData(this) }, retryDelayMs = retryDelayMs, logger = logger, - syncRulesParameters = syncRulesParameters + params = params ) syncJob = scope.launch { diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index 05e206a1..e2b7b28e 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -43,7 +43,7 @@ internal class SyncStream( private val uploadCrud: suspend () -> Unit, private val retryDelayMs: Long = 5000L, private val logger: Logger, - private val syncRulesParameters: Map? + private val params: Map? ) { private var isUploadingCrud = AtomicBoolean(false) @@ -248,7 +248,7 @@ internal class SyncStream( val req = StreamingSyncRequest( buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) }, clientId = clientId!!, - parameters = convertMapToJson(syncRulesParameters) + parameters = convertMapToJson(params) ) streamingSyncRequest(req).collect { value -> From a2f953bdee1c90b14bc0de56db618f5755b2b9fd Mon Sep 17 00:00:00 2001 From: Dominic Date: Tue, 1 Oct 2024 12:26:30 +0200 Subject: [PATCH 4/6] chore: add arrays, lists and maps to function --- .../kotlin/com/powersync/utils/Json.kt | 34 +++++++--- .../kotlin/com/powersync/utils/JsonTest.kt | 66 +++++++++++++++++++ 2 files changed, 91 insertions(+), 9 deletions(-) diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt index 5e2cd626..ef0f1ee9 100644 --- a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt +++ b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt @@ -1,6 +1,8 @@ package com.powersync.utils import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -15,18 +17,32 @@ internal object JsonUtil { } } -internal fun convertMapToJson(map: Map?): JsonObject { +internal fun convertMapToJson(map: Map?): JsonObject { if (map == null) return JsonObject(emptyMap()) val result = map.mapValues { (_, value) -> - when (value) { - is Int -> JsonPrimitive(value) - is Long -> JsonPrimitive(value) - is Double -> JsonPrimitive(value) - is Boolean -> JsonPrimitive(value) - is String -> JsonPrimitive(value) - else -> JsonNull - } + convertToJsonElement(value) } return JsonObject(result) } + + +internal fun convertToJsonElement(value: Any?): JsonElement { + return when (value) { + null -> JsonNull + is Int -> JsonPrimitive(value) + is Long -> JsonPrimitive(value) + is Double -> JsonPrimitive(value) + is Float -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + is String -> JsonPrimitive(value) + is Map<*, *> -> convertMapToJson(value as Map) + is List<*> -> convertListToJsonArray(value) + is Array<*> -> convertListToJsonArray(value.toList()) + else -> JsonNull + } +} + +internal fun convertListToJsonArray(list: List): JsonArray { + return JsonArray(list.map { convertToJsonElement(it) }) +} \ No newline at end of file diff --git a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt index 811fbf56..9f92b82e 100644 --- a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt +++ b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt @@ -1,5 +1,7 @@ package com.powersync.utils +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlin.test.* @@ -53,6 +55,70 @@ class JsonTest { assertEquals(JsonObject(mapOf("double" to JsonPrimitive(0.02))), result) } + @Test + fun testArrayMap() { + val testMap = mapOf("array" to arrayOf(1, 2, 3)) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("array" to JsonArray(listOf(JsonPrimitive(1), JsonPrimitive(2), JsonPrimitive(3))))), result) + } + + @Test + fun testListMap() { + val testMap = mapOf("list" to listOf("a", "b", "c")) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf("list" to JsonArray(listOf(JsonPrimitive("a"), JsonPrimitive("b"), JsonPrimitive("c"))))), result) + } + + @Test + fun testNestedMap() { + val testMap = mapOf( + "nested" to mapOf( + "int" to 1, + "string" to "value" + ) + ) + val result = convertMapToJson(testMap) + assertEquals(JsonObject(mapOf( + "nested" to JsonObject(mapOf( + "int" to JsonPrimitive(1), + "string" to JsonPrimitive("value") + )) + )), result) + } + + @Test + fun testComplexNestedStructure() { + val testMap = mapOf( + "string" to "value", + "int" to 42, + "list" to listOf(1, "two", 3.0), + "nestedMap" to mapOf( + "array" to arrayOf(true, false), + "nestedList" to listOf( + mapOf("key" to "value"), + listOf(1, 2, 3) + ) + ) + ) + val result = convertMapToJson(testMap) + + val expected = JsonObject(mapOf( + "string" to JsonPrimitive("value"), + "int" to JsonPrimitive(42), + "list" to JsonArray(listOf(JsonPrimitive(1), JsonPrimitive("two"), JsonPrimitive(3.0))), + "nestedMap" to JsonObject(mapOf( + "array" to JsonArray(listOf(JsonPrimitive(true), JsonPrimitive(false))), + "nestedList" to JsonArray(listOf( + JsonObject(mapOf("key" to JsonPrimitive("value"))), + JsonArray(listOf(JsonPrimitive(1), JsonPrimitive(2), JsonPrimitive(3))) + )) + )) + )) + + assertEquals(expected, result) + } + + @OptIn(ExperimentalSerializationApi::class) @Test fun testMapWithUnsupportedType() { val testMap = mapOf("unsupported" to object {}) From d111bec6ecf74c4225864d6c5bf6f16dc787fe68 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 2 Oct 2024 12:26:08 +0200 Subject: [PATCH 5/6] feat: add JsonParam sealed class to provide type safety --- .../kotlin/com/powersync/PowerSyncDatabase.kt | 23 +- .../com/powersync/db/PowerSyncDatabaseImpl.kt | 3 +- .../powersync/sync/StreamingSyncRequest.kt | 2 +- .../kotlin/com/powersync/sync/SyncStream.kt | 7 +- .../kotlin/com/powersync/utils/Json.kt | 53 ++-- .../kotlin/com/powersync/utils/JsonTest.kt | 280 ++++++++++++------ 6 files changed, 251 insertions(+), 117 deletions(-) diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index da943f2c..dfc3e1d2 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -5,6 +5,8 @@ import com.powersync.db.Queries import com.powersync.db.crud.CrudBatch import com.powersync.db.crud.CrudTransaction import com.powersync.sync.SyncStatus +import com.powersync.utils.JsonParam +import com.powersync.utils.toJsonParam /** * A PowerSync managed database. @@ -27,10 +29,27 @@ public interface PowerSyncDatabase : Queries { * * The connection is automatically re-opened if it fails for any reason. * + * Use @param [connector] to specify the [PowerSyncBackendConnector]. * Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms. * Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms. - * Use @param [params] to specify sync parameters from the client + * Use @param [params] to specify sync parameters from the client. Can be constructed using [toJsonParam]. * + * Example usage: + * ``` + * val params = mapOf( + * "name" to "John", + * "age" to 30, + * "isStudent" to false, + * "grades" to listOf(85, 90, 78) + * ).mapValues { it.value.toJsonParam() } + * + * connect( + * connector = connector, + * crudThrottleMs = 2000L, + * retryDelayMs = 10000L, + * params = params + * ) + * ``` * TODO: Internal Team - Status changes are reported on [statusStream]. */ @@ -38,7 +57,7 @@ public interface PowerSyncDatabase : Queries { connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L, retryDelayMs: Long = 5000L, - params: Map? = null + params: Map? = null ) diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index fb0e8918..302103f3 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -20,6 +20,7 @@ import com.powersync.db.internal.PowerSyncTransaction import com.powersync.db.schema.Schema import com.powersync.sync.SyncStatus import com.powersync.sync.SyncStream +import com.powersync.utils.JsonParam import com.powersync.utils.JsonUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview @@ -88,7 +89,7 @@ internal class PowerSyncDatabaseImpl( connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long, - params: Map?) + params: Map?) { // close connection if one is open disconnect() diff --git a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt b/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt index 6fda9fe4..9bef02c3 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt @@ -10,7 +10,7 @@ internal data class StreamingSyncRequest( val buckets: List, @SerialName("include_checksum") val includeChecksum: Boolean = true, @SerialName("client_id") val clientId: String, - val parameters: JsonObject = JsonObject(mapOf()) + val parameters: JsonObject? = JsonObject(mapOf()) ) { @SerialName("raw_data") private val rawData: Boolean = true } \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index e2b7b28e..1757c538 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -8,8 +8,9 @@ import com.powersync.bucket.Checkpoint import com.powersync.bucket.WriteCheckpointResponse import co.touchlab.stately.concurrency.AtomicBoolean import com.powersync.connectors.PowerSyncBackendConnector +import com.powersync.utils.JsonParam import com.powersync.utils.JsonUtil -import com.powersync.utils.convertMapToJson +import com.powersync.utils.toJsonObject import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.HttpTimeout @@ -43,7 +44,7 @@ internal class SyncStream( private val uploadCrud: suspend () -> Unit, private val retryDelayMs: Long = 5000L, private val logger: Logger, - private val params: Map? + private val params: Map? ) { private var isUploadingCrud = AtomicBoolean(false) @@ -248,7 +249,7 @@ internal class SyncStream( val req = StreamingSyncRequest( buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) }, clientId = clientId!!, - parameters = convertMapToJson(params) + parameters = params?.toJsonObject() ) streamingSyncRequest(req).collect { value -> diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt index ef0f1ee9..e412452c 100644 --- a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt +++ b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt @@ -17,32 +17,41 @@ internal object JsonUtil { } } -internal fun convertMapToJson(map: Map?): JsonObject { - if (map == null) return JsonObject(emptyMap()) +public sealed class JsonParam { + public data class Number(val value: kotlin.Number) : JsonParam() + public data class String(val value: kotlin.String) : JsonParam() + public data class Boolean(val value: kotlin.Boolean) : JsonParam() + public data class Map(val value: kotlin.collections.Map) : JsonParam() + public data class Collection(val value: kotlin.collections.Collection) : JsonParam() + public data class JsonElement(val value: kotlinx.serialization.json.JsonElement) : JsonParam() + public data object Null : JsonParam() - val result = map.mapValues { (_, value) -> - convertToJsonElement(value) + internal fun toJsonElement(): kotlinx.serialization.json.JsonElement = when (this) { + is Number -> JsonPrimitive(value) + is String -> JsonPrimitive(value) + is Boolean -> JsonPrimitive(value) + is Map -> JsonObject(value.mapValues { it.value.toJsonElement() }) + is Collection -> JsonArray(value.map { it.toJsonElement() }) + is JsonElement -> value + Null -> JsonNull } - return JsonObject(result) } - -internal fun convertToJsonElement(value: Any?): JsonElement { - return when (value) { - null -> JsonNull - is Int -> JsonPrimitive(value) - is Long -> JsonPrimitive(value) - is Double -> JsonPrimitive(value) - is Float -> JsonPrimitive(value) - is Boolean -> JsonPrimitive(value) - is String -> JsonPrimitive(value) - is Map<*, *> -> convertMapToJson(value as Map) - is List<*> -> convertListToJsonArray(value) - is Array<*> -> convertListToJsonArray(value.toList()) - else -> JsonNull - } +public fun Any?.toJsonParam(): JsonParam = when (this) { + is Number -> JsonParam.Number(this) + is String -> JsonParam.String(this) + is Boolean -> JsonParam.Boolean(this) + is Map<*, *> -> JsonParam.Map(this.mapKeys { it.key.toString() } + .mapValues { it.value.toJsonParam() }) + is List<*> -> JsonParam.Collection(this.map { it.toJsonParam() }) + is Array<*> -> JsonParam.Collection(this.map { it.toJsonParam() }) + is JsonElement -> JsonParam.JsonElement(this) + null -> JsonParam.Null + else -> throw IllegalArgumentException("Unsupported type for JsonParam: $this") } -internal fun convertListToJsonArray(list: List): JsonArray { - return JsonArray(list.map { convertToJsonElement(it) }) +public fun Map.toJsonObject(): JsonObject { + return JsonObject(this.mapValues { (_, value) -> + value?.toJsonElement() ?: JsonNull + }) } \ No newline at end of file diff --git a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt index 9f92b82e..705fac89 100644 --- a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt +++ b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt @@ -1,128 +1,232 @@ package com.powersync.utils -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive + +import kotlinx.serialization.json.* import kotlin.test.* class JsonTest { @Test - fun testEmptyMap() { - val emptyMap = emptyMap() - val result = convertMapToJson(emptyMap) - assertEquals(0, result.size) + fun testNumberToJsonElement() { + val number = JsonParam.Number(42) + val jsonElement = number.toJsonElement() + assertTrue(jsonElement is JsonPrimitive) + assertEquals(42, jsonElement.int) } @Test - fun testNull() { - val result = convertMapToJson(null) - assertEquals(JsonObject(mapOf()), result) + fun testStringToJsonElement() { + val string = JsonParam.String("test") + val jsonElement = string.toJsonElement() + assertTrue(jsonElement is JsonPrimitive) + assertEquals("test", jsonElement.content) } @Test - fun testIntegerMap() { - val testMap = mapOf("int" to 1) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("int" to JsonPrimitive(1))), result) + fun testBooleanToJsonElement() { + val boolean = JsonParam.Boolean(true) + val jsonElement = boolean.toJsonElement() + assertTrue(jsonElement is JsonPrimitive) + assertTrue(jsonElement.boolean) } @Test - fun testStringMap() { - val testMap = mapOf("string" to "string") - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("string" to JsonPrimitive("string"))), result) + fun testMapToJsonElement() { + val map = JsonParam.Map(mapOf( + "key1" to JsonParam.String("value1"), + "key2" to JsonParam.Number(42) + )) + val jsonElement = map.toJsonElement() + assertTrue(jsonElement is JsonObject) + assertEquals("value1", jsonElement["key1"]?.jsonPrimitive?.content) + assertEquals(42, jsonElement["key2"]?.jsonPrimitive?.int) } @Test - fun testLongMap() { - val testMap = mapOf("double" to 123L) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("double" to JsonPrimitive(123L))), result) + fun testListToJsonElement() { + val list = JsonParam.Collection(listOf( + JsonParam.String("item1"), + JsonParam.Number(42) + )) + val jsonElement = list.toJsonElement() + assertTrue(jsonElement is JsonArray) + assertEquals("item1", jsonElement[0].jsonPrimitive.content) + assertEquals(42, jsonElement[1].jsonPrimitive.int) } @Test - fun testBooleanMap() { - val testMap = mapOf("boolean" to false) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("boolean" to JsonPrimitive(false))), result) + fun testJsonElementParamToJsonElement() { + val originalJson = buildJsonObject { + put("key", "value") + } + val jsonElementParam = JsonParam.JsonElement(originalJson) + val jsonElement = jsonElementParam.toJsonElement() + assertEquals(originalJson, jsonElement) } @Test - fun testDoubleMap() { - val testMap = mapOf("double" to 0.02) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("double" to JsonPrimitive(0.02))), result) + fun testNullToJsonElement() { + val nullParam = JsonParam.Null + val jsonElement = nullParam.toJsonElement() + assertTrue(jsonElement is JsonNull) } @Test - fun testArrayMap() { - val testMap = mapOf("array" to arrayOf(1, 2, 3)) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("array" to JsonArray(listOf(JsonPrimitive(1), JsonPrimitive(2), JsonPrimitive(3))))), result) + fun testToJsonParamWithNull() { + val json = null.toJsonParam() + assertTrue(json is JsonParam.Null) } @Test - fun testListMap() { - val testMap = mapOf("list" to listOf("a", "b", "c")) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("list" to JsonArray(listOf(JsonPrimitive("a"), JsonPrimitive("b"), JsonPrimitive("c"))))), result) + fun testToJsonWithNumber() { + val json = 42.toJsonParam() + assertTrue(json is JsonParam.Number) + assertEquals(42, json.value) } @Test - fun testNestedMap() { - val testMap = mapOf( - "nested" to mapOf( - "int" to 1, - "string" to "value" - ) - ) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf( - "nested" to JsonObject(mapOf( - "int" to JsonPrimitive(1), - "string" to JsonPrimitive("value") - )) - )), result) - } - - @Test - fun testComplexNestedStructure() { - val testMap = mapOf( - "string" to "value", - "int" to 42, - "list" to listOf(1, "two", 3.0), - "nestedMap" to mapOf( - "array" to arrayOf(true, false), - "nestedList" to listOf( - mapOf("key" to "value"), - listOf(1, 2, 3) - ) - ) + fun testToJsonWithString() { + val json = "test".toJsonParam() + assertTrue(json is JsonParam.String) + assertEquals("test", json.value) + } + + @Test + fun testToJsonWithBoolean() { + val json = true.toJsonParam() + assertTrue(json is JsonParam.Boolean) + assertEquals(true, json.value) + } + + @Test + fun testToJsonWithMap() { + val map = mapOf("key" to "value") + val json = map.toJsonParam() + assertTrue(json is JsonParam.Map) + assertEquals(1, json.value.size) + assertTrue(json.value["key"] is JsonParam.String) + assertEquals("value", (json.value["key"] as JsonParam.String).value) + } + + @Test + fun testToJsonParamWithCollections() { + // Test with List + val list = listOf("item1", 42) + val jsonFromList = list.toJsonParam() + assertEquals(jsonFromList, JsonParam.Collection(list.map { it.toJsonParam() })) + + // Test with Array + val array = arrayOf("item2", 84) + val jsonFromArray = array.toJsonParam() + assertEquals(jsonFromArray, JsonParam.Collection(array.map { it.toJsonParam() })) + } + + @Test + fun testToJsonWithJsonElement() { + val jsonElement = JsonPrimitive("test") + val json = jsonElement.toJsonParam() + assertTrue(json is JsonParam.JsonElement) + assertEquals(jsonElement, (json).value) + } + + @Test + fun testToJsonParamWithUnsupportedType() { + assertFailsWith { + Any().toJsonParam() + } + } + + @Test + fun testMapToJsonObject() { + val params = mapOf( + "string" to JsonParam.String("value"), + "number" to JsonParam.Number(42), + "boolean" to JsonParam.Boolean(true), + "null" to JsonParam.Null ) - val result = convertMapToJson(testMap) - - val expected = JsonObject(mapOf( - "string" to JsonPrimitive("value"), - "int" to JsonPrimitive(42), - "list" to JsonArray(listOf(JsonPrimitive(1), JsonPrimitive("two"), JsonPrimitive(3.0))), - "nestedMap" to JsonObject(mapOf( - "array" to JsonArray(listOf(JsonPrimitive(true), JsonPrimitive(false))), - "nestedList" to JsonArray(listOf( - JsonObject(mapOf("key" to JsonPrimitive("value"))), - JsonArray(listOf(JsonPrimitive(1), JsonPrimitive(2), JsonPrimitive(3))) + val jsonObject = params.toJsonObject() + assertEquals("value", jsonObject["string"]?.jsonPrimitive?.content) + assertEquals(42, jsonObject["number"]?.jsonPrimitive?.int) + assertEquals(true, jsonObject["boolean"]?.jsonPrimitive?.boolean) + assertTrue(jsonObject["null"] is JsonNull) + } + + @Test + fun testComplexNestedMapToJsonObject() { + val complexNestedMap = mapOf( + "string" to JsonParam.String("value"), + "number" to JsonParam.Number(42), + "boolean" to JsonParam.Boolean(true), + "null" to JsonParam.Null, + "nestedMap" to JsonParam.Map(mapOf( + "list" to JsonParam.Collection(listOf( + JsonParam.Number(1), + JsonParam.String("two"), + JsonParam.Boolean(false) + )), + "deeplyNested" to JsonParam.Map(mapOf( + "jsonElement" to JsonParam.JsonElement(buildJsonObject { + put("key", "value") + put("array", buildJsonArray { + add(1) + add("string") + add(true) + }) + }), + "mixedList" to JsonParam.Collection(arrayListOf( + JsonParam.Number(3.14), + JsonParam.Map(mapOf( + "key" to JsonParam.String("nestedValue") + )), + JsonParam.Null + ) + ) )) )) - )) + ) - assertEquals(expected, result) - } + val jsonObject = complexNestedMap.toJsonObject() - @OptIn(ExperimentalSerializationApi::class) - @Test - fun testMapWithUnsupportedType() { - val testMap = mapOf("unsupported" to object {}) - val result = convertMapToJson(testMap) - assertEquals(JsonObject(mapOf("unsupported" to JsonPrimitive(null))), result) + // Verify top-level elements + assertEquals("value", jsonObject["string"]?.jsonPrimitive?.content) + assertEquals(42, jsonObject["number"]?.jsonPrimitive?.int) + assertEquals(true, jsonObject["boolean"]?.jsonPrimitive?.boolean) + assertTrue(jsonObject["null"] is JsonNull) + + // Verify nested map + val nestedMap = jsonObject["nestedMap"]?.jsonObject + assertNotNull(nestedMap) + + // Verify nested list + val nestedList = nestedMap["list"]?.jsonArray + assertNotNull(nestedList) + assertEquals(3, nestedList.size) + assertEquals(1, nestedList[0].jsonPrimitive.int) + assertEquals("two", nestedList[1].jsonPrimitive.content) + assertEquals(false, nestedList[2].jsonPrimitive.boolean) + + // Verify deeply nested map + val deeplyNested = nestedMap["deeplyNested"]?.jsonObject + assertNotNull(deeplyNested) + + // Verify JsonElement + val jsonElement = deeplyNested["jsonElement"]?.jsonObject + assertNotNull(jsonElement) + assertEquals("value", jsonElement["key"]?.jsonPrimitive?.content) + val jsonElementArray = jsonElement["array"]?.jsonArray + assertNotNull(jsonElementArray) + assertEquals(3, jsonElementArray.size) + assertEquals(1, jsonElementArray[0].jsonPrimitive.int) + assertEquals("string", jsonElementArray[1].jsonPrimitive.content) + assertEquals(true, jsonElementArray[2].jsonPrimitive.boolean) + + // Verify mixed list + val mixedList = deeplyNested["mixedList"]?.jsonArray + assertNotNull(mixedList) + assertEquals(3, mixedList.size) + assertEquals(3.14, mixedList[0].jsonPrimitive.double) + val nestedMapInList = mixedList[1].jsonObject + assertNotNull(nestedMapInList) + assertEquals("nestedValue", nestedMapInList["key"]?.jsonPrimitive?.content) + assertTrue(mixedList[2] is JsonNull) } } \ No newline at end of file From 24c5f9846fb16a5bca2c575fc5f2e2a03e7ceea5 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 2 Oct 2024 17:59:57 +0200 Subject: [PATCH 6/6] chore: pr reverts --- .../kotlin/com/powersync/PowerSyncDatabase.kt | 18 ++--- .../com/powersync/db/PowerSyncDatabaseImpl.kt | 5 +- .../powersync/sync/StreamingSyncRequest.kt | 2 +- .../kotlin/com/powersync/sync/SyncStream.kt | 4 +- .../kotlin/com/powersync/utils/Json.kt | 13 ---- .../kotlin/com/powersync/utils/JsonTest.kt | 65 ------------------- 6 files changed, 15 insertions(+), 92 deletions(-) diff --git a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index dfc3e1d2..55a99c54 100644 --- a/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/core/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -6,7 +6,6 @@ import com.powersync.db.crud.CrudBatch import com.powersync.db.crud.CrudTransaction import com.powersync.sync.SyncStatus import com.powersync.utils.JsonParam -import com.powersync.utils.toJsonParam /** * A PowerSync managed database. @@ -32,16 +31,17 @@ public interface PowerSyncDatabase : Queries { * Use @param [connector] to specify the [PowerSyncBackendConnector]. * Use @param [crudThrottleMs] to specify the time between CRUD operations. Defaults to 1000ms. * Use @param [retryDelayMs] to specify the delay between retries after failure. Defaults to 5000ms. - * Use @param [params] to specify sync parameters from the client. Can be constructed using [toJsonParam]. + * Use @param [params] to specify sync parameters from the client. * * Example usage: * ``` - * val params = mapOf( - * "name" to "John", - * "age" to 30, - * "isStudent" to false, - * "grades" to listOf(85, 90, 78) - * ).mapValues { it.value.toJsonParam() } + * val params = JsonParam.Map( + * mapOf( + * "name" to JsonParam.String("John Doe"), + * "age" to JsonParam.Number(30), + * "isStudent" to JsonParam.Boolean(false) + * ) + * ) * * connect( * connector = connector, @@ -57,7 +57,7 @@ public interface PowerSyncDatabase : Queries { connector: PowerSyncBackendConnector, crudThrottleMs: Long = 1000L, retryDelayMs: Long = 5000L, - params: Map? = null + params: Map = emptyMap() ) diff --git a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index 302103f3..2bdd9e86 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -22,6 +22,7 @@ import com.powersync.sync.SyncStatus import com.powersync.sync.SyncStream import com.powersync.utils.JsonParam import com.powersync.utils.JsonUtil +import com.powersync.utils.toJsonObject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.Job @@ -89,7 +90,7 @@ internal class PowerSyncDatabaseImpl( connector: PowerSyncBackendConnector, crudThrottleMs: Long, retryDelayMs: Long, - params: Map?) + params: Map) { // close connection if one is open disconnect() @@ -101,7 +102,7 @@ internal class PowerSyncDatabaseImpl( uploadCrud = suspend { connector.uploadData(this) }, retryDelayMs = retryDelayMs, logger = logger, - params = params + params = params.toJsonObject() ) syncJob = scope.launch { diff --git a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt b/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt index 9bef02c3..6fda9fe4 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/StreamingSyncRequest.kt @@ -10,7 +10,7 @@ internal data class StreamingSyncRequest( val buckets: List, @SerialName("include_checksum") val includeChecksum: Boolean = true, @SerialName("client_id") val clientId: String, - val parameters: JsonObject? = JsonObject(mapOf()) + val parameters: JsonObject = JsonObject(mapOf()) ) { @SerialName("raw_data") private val rawData: Boolean = true } \ No newline at end of file diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index 1757c538..6b0c18c9 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -44,7 +44,7 @@ internal class SyncStream( private val uploadCrud: suspend () -> Unit, private val retryDelayMs: Long = 5000L, private val logger: Logger, - private val params: Map? + private val params: JsonObject ) { private var isUploadingCrud = AtomicBoolean(false) @@ -249,7 +249,7 @@ internal class SyncStream( val req = StreamingSyncRequest( buckets = initialBuckets.map { (bucket, after) -> BucketRequest(bucket, after) }, clientId = clientId!!, - parameters = params?.toJsonObject() + parameters = params ) streamingSyncRequest(req).collect { value -> diff --git a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt index e412452c..d8ddac8f 100644 --- a/core/src/commonMain/kotlin/com/powersync/utils/Json.kt +++ b/core/src/commonMain/kotlin/com/powersync/utils/Json.kt @@ -37,19 +37,6 @@ public sealed class JsonParam { } } -public fun Any?.toJsonParam(): JsonParam = when (this) { - is Number -> JsonParam.Number(this) - is String -> JsonParam.String(this) - is Boolean -> JsonParam.Boolean(this) - is Map<*, *> -> JsonParam.Map(this.mapKeys { it.key.toString() } - .mapValues { it.value.toJsonParam() }) - is List<*> -> JsonParam.Collection(this.map { it.toJsonParam() }) - is Array<*> -> JsonParam.Collection(this.map { it.toJsonParam() }) - is JsonElement -> JsonParam.JsonElement(this) - null -> JsonParam.Null - else -> throw IllegalArgumentException("Unsupported type for JsonParam: $this") -} - public fun Map.toJsonObject(): JsonObject { return JsonObject(this.mapValues { (_, value) -> value?.toJsonElement() ?: JsonNull diff --git a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt index 705fac89..af3fd451 100644 --- a/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt +++ b/core/src/commonTest/kotlin/com/powersync/utils/JsonTest.kt @@ -70,71 +70,6 @@ class JsonTest { assertTrue(jsonElement is JsonNull) } - @Test - fun testToJsonParamWithNull() { - val json = null.toJsonParam() - assertTrue(json is JsonParam.Null) - } - - @Test - fun testToJsonWithNumber() { - val json = 42.toJsonParam() - assertTrue(json is JsonParam.Number) - assertEquals(42, json.value) - } - - @Test - fun testToJsonWithString() { - val json = "test".toJsonParam() - assertTrue(json is JsonParam.String) - assertEquals("test", json.value) - } - - @Test - fun testToJsonWithBoolean() { - val json = true.toJsonParam() - assertTrue(json is JsonParam.Boolean) - assertEquals(true, json.value) - } - - @Test - fun testToJsonWithMap() { - val map = mapOf("key" to "value") - val json = map.toJsonParam() - assertTrue(json is JsonParam.Map) - assertEquals(1, json.value.size) - assertTrue(json.value["key"] is JsonParam.String) - assertEquals("value", (json.value["key"] as JsonParam.String).value) - } - - @Test - fun testToJsonParamWithCollections() { - // Test with List - val list = listOf("item1", 42) - val jsonFromList = list.toJsonParam() - assertEquals(jsonFromList, JsonParam.Collection(list.map { it.toJsonParam() })) - - // Test with Array - val array = arrayOf("item2", 84) - val jsonFromArray = array.toJsonParam() - assertEquals(jsonFromArray, JsonParam.Collection(array.map { it.toJsonParam() })) - } - - @Test - fun testToJsonWithJsonElement() { - val jsonElement = JsonPrimitive("test") - val json = jsonElement.toJsonParam() - assertTrue(json is JsonParam.JsonElement) - assertEquals(jsonElement, (json).value) - } - - @Test - fun testToJsonParamWithUnsupportedType() { - assertFailsWith { - Any().toJsonParam() - } - } - @Test fun testMapToJsonObject() { val params = mapOf(