diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 145cf936447..12aa141beb1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,17 +37,24 @@ kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization- kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" } kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" } +kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" } ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" } +ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" } ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" } ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" } ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" } ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" } +ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt index 90ad3a0b92f..b224d8caebf 100644 --- a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/StandartKeyValueRepo.kt @@ -50,3 +50,10 @@ interface StandardKeyValueRepo : ReadStandardKeyValueRepo = StandardKeyValueRepo + +class DelegateBasedStandardKeyValueRepo( + readDelegate: ReadStandardKeyValueRepo, + writeDelegate: WriteStandardKeyValueRepo +) : StandardKeyValueRepo, + ReadStandardKeyValueRepo by readDelegate, + WriteStandardKeyValueRepo by writeDelegate diff --git a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt index 3d929737a12..7e87197ac8d 100644 --- a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt +++ b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValueRepo.kt @@ -78,11 +78,11 @@ class WriteMapKeyValueRepo( } override suspend fun unsetWithValues(toUnset: List) { - map.forEach { - if (it.value in toUnset) { - map.remove(it.key) - _onValueRemoved.emit(it.key) - } + map.mapNotNull { (k, v) -> + k.takeIf { v in toUnset } + }.forEach { + map.remove(it) + _onValueRemoved.emit(it) } } } diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepo.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepo.kt index 70bfce26391..80955437308 100644 --- a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepo.kt +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepo.kt @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ktor.common.crud.* +import dev.inmo.micro_utils.repos.ktor.common.idParameterName import io.ktor.client.HttpClient import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.serializer @@ -39,9 +40,7 @@ class KtorReadStandardCrudRepo ( buildStandardUrl( baseUrl, getByIdRouting, - mapOf( - "id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id) - ) + idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id) ), objectsSerializerNullable ) @@ -50,9 +49,7 @@ class KtorReadStandardCrudRepo ( buildStandardUrl( baseUrl, containsRouting, - mapOf( - "id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id) - ) + idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id) ), Boolean.serializer() ) diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepoClient.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepoClient.kt index ce280f2c8bf..82de6de4b5c 100644 --- a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepoClient.kt +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorReadStandardCrudRepoClient.kt @@ -4,6 +4,7 @@ import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ktor.common.crud.* +import dev.inmo.micro_utils.repos.ktor.common.idParameterName import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get @@ -30,7 +31,7 @@ class KtorReadStandardCrudRepoClient ( baseUrl, getByIdRouting, mapOf( - "id" to idSerializer(id) + idParameterName to idSerializer(id) ) ) ) { @@ -42,7 +43,7 @@ class KtorReadStandardCrudRepoClient ( baseUrl, containsRouting, mapOf( - "id" to idSerializer(id) + idParameterName to idSerializer(id) ) ) ) { diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorStandardCrudRepoClient.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorStandardCrudRepoClient.kt index d32a072f26c..87d79182c6d 100644 --- a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorStandardCrudRepoClient.kt +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/crud/KtorStandardCrudRepoClient.kt @@ -17,7 +17,7 @@ class KtorStandardCrudRepoClient ( writeDelegate ) { companion object { - inline operator fun invoke( + inline operator fun invoke( baseUrl: String, httpClient: HttpClient, objectTypeInfo: TypeInfo, @@ -38,7 +38,7 @@ class KtorStandardCrudRepoClient ( ) ) - inline operator fun invoke( + inline operator fun invoke( baseUrl: String, subpart: String, httpClient: HttpClient, diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorReadStandardKeyValueRepoClient.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorReadStandardKeyValueRepoClient.kt new file mode 100644 index 00000000000..e22a426dca2 --- /dev/null +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorReadStandardKeyValueRepoClient.kt @@ -0,0 +1,144 @@ +package dev.inmo.micro_utils.repos.ktor.client.key_value + +import dev.inmo.micro_utils.ktor.common.* +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo +import dev.inmo.micro_utils.repos.ktor.common.* +import dev.inmo.micro_utils.repos.ktor.common.crud.* +import dev.inmo.micro_utils.repos.ktor.common.key_value.* +import dev.inmo.micro_utils.repos.ktor.common.one_to_many.containsByKeyRoute +import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.http.* +import io.ktor.util.reflect.TypeInfo +import io.ktor.util.reflect.typeInfo +import kotlinx.serialization.* + +class KtorReadStandardKeyValueRepoClient( + private val baseUrl: String, + private val httpClient: HttpClient, + private val contentType: ContentType, + private val objectType: TypeInfo, + private val paginationResultObjectsTypeInfo: TypeInfo, + private val paginationResultIdsTypeInfo: TypeInfo, + private val idSerializer: suspend (Key) -> String, + private val valueSerializer: suspend (Value) -> String +) : ReadStandardKeyValueRepo { + override suspend fun get(k: Key): Value? = httpClient.get( + buildStandardUrl( + baseUrl, + getRoute, + mapOf( + idParameterName to idSerializer(k) + ) + ) + ) { + contentType(contentType) + }.takeIf { it.status != HttpStatusCode.NoContent } ?.body(objectType) + + override suspend fun contains(key: Key): Boolean = httpClient.get( + buildStandardUrl( + baseUrl, + containsRoute, + idParameterName to idSerializer(key) + ) + ) { + contentType(contentType) + }.body() + + override suspend fun values( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = httpClient.get( + buildStandardUrl(baseUrl, valuesRoute, pagination.asUrlQueryParts + (reversedParameterName to reversed.toString())) + ) { + contentType(contentType) + }.body(paginationResultObjectsTypeInfo) + + override suspend fun keys( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = httpClient.get( + buildStandardUrl(baseUrl, keysRoute, pagination.asUrlQueryParts + (reversedParameterName to reversed.toString())) + ) { + contentType(contentType) + }.body(paginationResultIdsTypeInfo) + + override suspend fun keys( + v: Value, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = httpClient.get( + buildStandardUrl( + baseUrl, + keysRoute, + pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()) + (valueParameterName to valueSerializer(v)) + ) + ) { + contentType(contentType) + }.body(paginationResultIdsTypeInfo) + + override suspend fun count(): Long = httpClient.get( + buildStandardUrl( + baseUrl, + countRouting + ) + ) { + contentType(contentType) + }.body() +} + +inline fun KtorReadStandardKeyValueRepoClient( + baseUrl: String, + httpClient: HttpClient, + contentType: ContentType, + noinline idSerializer: suspend (Key) -> String, + noinline valueSerializer: suspend (Value) -> String +) = KtorReadStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + typeInfo(), + typeInfo>(), + typeInfo>(), + idSerializer, + valueSerializer +) + +inline fun KtorReadStandardKeyValueRepoClient( + baseUrl: String, + httpClient: HttpClient, + idsSerializer: KSerializer, + valueSerializer: KSerializer, + serialFormat: StringFormat, + contentType: ContentType, +) = KtorReadStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + { + serialFormat.encodeToString(idsSerializer, it).encodeURLQueryComponent() + } +) { + serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent() +} + +inline fun KtorReadStandardKeyValueRepoClient( + baseUrl: String, + httpClient: HttpClient, + idsSerializer: KSerializer, + valuesSerializer: KSerializer, + serialFormat: BinaryFormat, + contentType: ContentType, +) = KtorReadStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + { + serialFormat.encodeHex(idsSerializer, it) + } +) { + serialFormat.encodeHex(valuesSerializer, it) +} diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorStandardKeyValueRepoClient.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorStandardKeyValueRepoClient.kt new file mode 100644 index 00000000000..92f60bf7053 --- /dev/null +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorStandardKeyValueRepoClient.kt @@ -0,0 +1,85 @@ +package dev.inmo.micro_utils.repos.ktor.client.key_value + +import dev.inmo.micro_utils.ktor.common.* +import dev.inmo.micro_utils.repos.* +import io.ktor.client.HttpClient +import io.ktor.http.ContentType +import io.ktor.http.encodeURLQueryComponent +import kotlinx.serialization.* + +class KtorStandardKeyValueRepoClient ( + readDelegate: ReadStandardKeyValueRepo, + writeDelegate: WriteStandardKeyValueRepo +) : StandardKeyValueRepo by DelegateBasedStandardKeyValueRepo( + readDelegate, + writeDelegate +) { + companion object { + inline operator fun invoke( + baseUrl: String, + httpClient: HttpClient, + contentType: ContentType, + noinline idSerializer: suspend (Key) -> String, + noinline valueSerializer: suspend (Value) -> String + ) = KtorStandardKeyValueRepoClient( + KtorReadStandardKeyValueRepoClient( + baseUrl, httpClient, contentType, idSerializer, valueSerializer + ), + KtorWriteStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType + ) + ) + inline operator fun invoke( + baseUrl: String, + subpart: String, + httpClient: HttpClient, + contentType: ContentType, + noinline idSerializer: suspend (Key) -> String, + noinline valueSerializer: suspend (Value) -> String + ) = KtorStandardKeyValueRepoClient( + buildStandardUrl(baseUrl, subpart), + httpClient, + contentType, + idSerializer, + valueSerializer + ) + } +} + +inline fun KtorStandardKeyValueRepoClient( + baseUrl: String, + httpClient: HttpClient, + contentType: ContentType, + idSerializer: SerializationStrategy, + valueSerializer: SerializationStrategy, + serialFormat: StringFormat, +) = KtorStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + { + serialFormat.encodeToString(idSerializer, it).encodeURLQueryComponent() + } +) { + serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent() +} + +inline fun KtorStandardKeyValueRepoClient( + baseUrl: String, + httpClient: HttpClient, + contentType: ContentType, + idSerializer: SerializationStrategy, + valueSerializer: SerializationStrategy, + serialFormat: BinaryFormat, +) = KtorStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + { + serialFormat.encodeHex(idSerializer, it) + } +) { + serialFormat.encodeHex(valueSerializer, it) +} diff --git a/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorWriteStandardKeyValueRepoClient.kt b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorWriteStandardKeyValueRepoClient.kt new file mode 100644 index 00000000000..65cceca45e9 --- /dev/null +++ b/repos/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key_value/KtorWriteStandardKeyValueRepoClient.kt @@ -0,0 +1,83 @@ +package dev.inmo.micro_utils.repos.ktor.client.key_value + +import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow +import dev.inmo.micro_utils.ktor.common.* +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo +import dev.inmo.micro_utils.repos.ktor.common.* +import dev.inmo.micro_utils.repos.ktor.common.crud.* +import dev.inmo.micro_utils.repos.ktor.common.key_value.* +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.http.* +import io.ktor.util.InternalAPI +import io.ktor.util.reflect.TypeInfo +import io.ktor.util.reflect.typeInfo +import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.* + +class KtorWriteStandardKeyValueRepoClient( + private val baseUrl: String, + private val httpClient: HttpClient, + private val contentType: ContentType, + override val onNewValue: Flow>, + override val onValueRemoved: Flow, + private val idsListTypeInfo: TypeInfo, + private val objectsListTypeInfo: TypeInfo, + private val idsToObjectsMapTypeInfo: TypeInfo +) : WriteStandardKeyValueRepo { + @OptIn(InternalAPI::class) + override suspend fun unsetWithValues(toUnset: List) { + httpClient.post( + buildStandardUrl(baseUrl, unsetWithValuesRoute) + ) { + body = toUnset + bodyType = objectsListTypeInfo + contentType(contentType) + }.status + } + + @OptIn(InternalAPI::class) + override suspend fun unset(toUnset: List) { + httpClient.post( + buildStandardUrl(baseUrl, unsetRoute) + ) { + body = toUnset + bodyType = idsListTypeInfo + contentType(contentType) + }.status + } + + @OptIn(InternalAPI::class) + override suspend fun set(toSet: Map) { + httpClient.post( + buildStandardUrl(baseUrl, setRoute) + ) { + body = toSet + bodyType = idsToObjectsMapTypeInfo + contentType(contentType) + }.status + } + + companion object { + inline operator fun invoke( + baseUrl: String, + httpClient: HttpClient, + contentType: ContentType + ) = KtorWriteStandardKeyValueRepoClient( + baseUrl, + httpClient, + contentType, + httpClient.createStandardWebsocketFlow( + buildStandardUrl(baseUrl, onNewValueRoute), + ), + httpClient.createStandardWebsocketFlow( + buildStandardUrl(baseUrl, onValueRemovedRoute), + ), + typeInfo>(), + typeInfo>(), + typeInfo>() + ) + } +} diff --git a/repos/ktor/common/build.gradle b/repos/ktor/common/build.gradle index 1881ecbcc12..79002ed147b 100644 --- a/repos/ktor/common/build.gradle +++ b/repos/ktor/common/build.gradle @@ -13,5 +13,22 @@ kotlin { api internalProject("micro_utils.repos.common") } } + jvmTest { + dependencies { + implementation internalProject("micro_utils.repos.common") + implementation internalProject("micro_utils.repos.ktor.client") + implementation internalProject("micro_utils.repos.ktor.server") + implementation internalProject("micro_utils.repos.inmemory") + implementation libs.kt.coroutines.test + + implementation libs.ktor.server.cio + implementation libs.ktor.client.cio + implementation libs.ktor.server.content.negotiation + implementation libs.ktor.serialization.kotlinx.json + implementation libs.ktor.client.content.negotiation + implementation libs.ktor.client.logging + implementation libs.ktor.client.websockets + } + } } } diff --git a/repos/ktor/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/common/ParametersNames.kt b/repos/ktor/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/common/ParametersNames.kt index 85bece83603..68022ef6200 100644 --- a/repos/ktor/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/common/ParametersNames.kt +++ b/repos/ktor/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/common/ParametersNames.kt @@ -1,5 +1,6 @@ package dev.inmo.micro_utils.repos.ktor.common +const val idParameterName = "id" const val keyParameterName = "key" const val valueParameterName = "value" -const val reversedParameterName = "reversed" \ No newline at end of file +const val reversedParameterName = "reversed" diff --git a/repos/ktor/common/src/jvmTest/kotlin/CRUDTests.kt b/repos/ktor/common/src/jvmTest/kotlin/CRUDTests.kt new file mode 100644 index 00000000000..9c17c341e0f --- /dev/null +++ b/repos/ktor/common/src/jvmTest/kotlin/CRUDTests.kt @@ -0,0 +1,89 @@ +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.ktor.client.crud.KtorStandardCrudRepoClient +import dev.inmo.micro_utils.repos.ktor.server.crud.configureStandardCrudRepoRoutes +import io.ktor.client.HttpClient +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.routing.routing +import io.ktor.server.websocket.WebSockets +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals + +class CRUDTests { + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testCRUDFunctions() { + runTest() { + val map = mutableMapOf() + val repo = MapCRUDRepo( + map, + { newValue, id, oldValue -> + oldValue.copy(title = newValue.title) + } + ) { + size to ComplexData(size, title = it.title) + } + val server = io.ktor.server.engine.embeddedServer( + CIO, + 23456, + "127.0.0.1" + ) { + install(ContentNegotiation) { + json() + } + install(WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + } + routing { + configureStandardCrudRepoRoutes( + repo + ) { + it.toInt() + } + } + }.start(false) + val client = HttpClient { + install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { + json() + } + install(Logging) + install(io.ktor.client.plugins.websocket.WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + } + } + val crudClient = KtorStandardCrudRepoClient( + "http://127.0.0.1:23456", + client, + ContentType.Application.Json + ) { + it.toString() + } + + val created = crudClient.create(SimpleData("Example")).single() + assertEquals(map.size, 1) + assertEquals(map.size.toLong(), crudClient.count()) + assertEquals(1, crudClient.count()) + assertEquals(map.getValue(map.keys.first()), created) + + val updated = crudClient.update(created.id, SimpleData("Example2")) + assertEquals(map.size, 1) + assertEquals(map.size.toLong(), crudClient.count()) + assertEquals(1, crudClient.count()) + assertEquals(map.getValue(map.keys.first()), updated) + + crudClient.deleteById(created.id) + assertEquals(map.size, 0) + assertEquals(map.size.toLong(), crudClient.count()) + assertEquals(0, crudClient.count()) + server.stop() + } + } +} diff --git a/repos/ktor/common/src/jvmTest/kotlin/ComplexData.kt b/repos/ktor/common/src/jvmTest/kotlin/ComplexData.kt new file mode 100644 index 00000000000..ad202ee46b3 --- /dev/null +++ b/repos/ktor/common/src/jvmTest/kotlin/ComplexData.kt @@ -0,0 +1,10 @@ +import com.benasher44.uuid.uuid4 +import kotlinx.serialization.Serializable + +@Serializable +data class ComplexData( + val id: Int, + val simple: SimpleData = SimpleData(), + val simples: List = (0 until 100).map { SimpleData(("$it")) }, + val title: String = uuid4().toString() +) diff --git a/repos/ktor/common/src/jvmTest/kotlin/KVTests.kt b/repos/ktor/common/src/jvmTest/kotlin/KVTests.kt new file mode 100644 index 00000000000..e9071246e86 --- /dev/null +++ b/repos/ktor/common/src/jvmTest/kotlin/KVTests.kt @@ -0,0 +1,139 @@ +import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination +import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.ktor.client.key_value.KtorStandardKeyValueRepoClient +import dev.inmo.micro_utils.repos.ktor.server.key_value.configureStandardKeyValueRepoRoutes +import io.ktor.client.HttpClient +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.install +import io.ktor.server.cio.CIO +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.routing.routing +import io.ktor.server.websocket.WebSockets +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import kotlin.test.* + +class KVTests { + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun testCRUDFunctions() { + runTest() { + val map = mutableMapOf() + val repo = MapKeyValueRepo(map) + val server = io.ktor.server.engine.embeddedServer( + CIO, + 23456, + "127.0.0.1" + ) { + install(ContentNegotiation) { + json() + } + install(WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + } + routing { + configureStandardKeyValueRepoRoutes( + repo, + Int.serializer(), + ComplexData.serializer(), + Json {} + ) + } + }.start(false) + val client = HttpClient { + install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { + json() + } + install(Logging) + install(io.ktor.client.plugins.websocket.WebSockets) { + contentConverter = KotlinxWebsocketSerializationConverter(Json) + } + } + val crudClient = KtorStandardKeyValueRepoClient( + "http://127.0.0.1:23456", + client, + ContentType.Application.Json, + Int.serializer(), + ComplexData.serializer(), + Json + ) + + val dataInOneKey = ComplexData(1, title = "Example1") + val dataInMultipleKeys = ComplexData(2, title = "Example2") + val repeatCount = 3 + + val dataList = listOf( + 1 to dataInOneKey + ) + (0 until repeatCount).map { + (it + 2) to dataInMultipleKeys + } + + dataList.forEachIndexed { i, (id, data) -> + crudClient.set(id, data) + assertEquals(map.size, i + 1) + assertEquals(map.size.toLong(), crudClient.count()) + assertEquals(i + 1L, crudClient.count()) + dataList.take(i + 1).forEach { (id, data) -> + assertEquals(data, map[id]) + assertEquals(data, crudClient.get(id)) + assertEquals(map[id], crudClient.get(id)) + } + } + + dataList.forEach { (id, data) -> + assertTrue(crudClient.contains(id)) + assertEquals(data, crudClient.get(id)) + } + + assertEquals( + dataList.mapNotNull { if (it.second == dataInMultipleKeys) it.first else null }, + getAllWithNextPaging(firstPageWithOneElementPagination) { + crudClient.keys(dataInMultipleKeys, it) + } + ) + + assertEquals( + dataList.mapNotNull { if (it.second == dataInOneKey) it.first else null }, + getAllWithNextPaging(firstPageWithOneElementPagination) { + crudClient.keys(dataInOneKey, it) + } + ) + + assertEquals( + dataList.map { it.first }, + getAllWithNextPaging(firstPageWithOneElementPagination) { + crudClient.keys(it) + } + ) + + assertEquals( + dataList.map { it.second }, + getAllWithNextPaging(firstPageWithOneElementPagination) { + crudClient.values(it) + } + ) + + assertEquals(dataList.size.toLong(), crudClient.count()) + + crudClient.unsetWithValues(dataInMultipleKeys) + assertEquals( + dataList.filter { it.second == dataInOneKey }.size.toLong(), + crudClient.count() + ) + + crudClient.unset(dataList.first { it.second == dataInOneKey }.first) + assertEquals( + 0, + crudClient.count() + ) + + server.stop() + } + } +} diff --git a/repos/ktor/common/src/jvmTest/kotlin/SimpleData.kt b/repos/ktor/common/src/jvmTest/kotlin/SimpleData.kt new file mode 100644 index 00000000000..a078cfe0cf1 --- /dev/null +++ b/repos/ktor/common/src/jvmTest/kotlin/SimpleData.kt @@ -0,0 +1,7 @@ +import com.benasher44.uuid.uuid4 +import kotlinx.serialization.Serializable + +@Serializable +data class SimpleData( + val title: String = uuid4().toString() +) diff --git a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorReadStandardCrudRepo.kt b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorReadStandardCrudRepo.kt index 7ac033b7006..e8ee34dd409 100644 --- a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorReadStandardCrudRepo.kt +++ b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorReadStandardCrudRepo.kt @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.ktor.server.* import dev.inmo.micro_utils.pagination.extractPagination import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ktor.common.crud.* +import dev.inmo.micro_utils.repos.ktor.common.idParameterName import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.server.application.call @@ -25,7 +26,7 @@ inline fun Route.configureReadStandardCrudR get(getByIdRouting) { val id = idDeserializer( - call.getQueryParameterOrSendError("id") ?: return@get + call.getQueryParameterOrSendError(idParameterName) ?: return@get ) val result = originalRepo.getById(id) @@ -39,7 +40,7 @@ inline fun Route.configureReadStandardCrudR get(containsRouting) { val id = idDeserializer( - call.getQueryParameterOrSendError("id") ?: return@get + call.getQueryParameterOrSendError(idParameterName) ?: return@get ) call.respond( diff --git a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorWriteStandardCrudRepo.kt b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorWriteStandardCrudRepo.kt index ae205e27f5d..3dbe3c78188 100644 --- a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorWriteStandardCrudRepo.kt +++ b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud/NewKtorWriteStandardCrudRepo.kt @@ -26,11 +26,7 @@ inline fun Route.configureStandardKeyValueRepoRoutes ( + originalRepo: StandardKeyValueRepo, + noinline idDeserializer: suspend (String) -> Key, + noinline valueDeserializer: suspend (String) -> Value +) { + configureReadStandardKeyValueRepoRoutes(originalRepo, idDeserializer, valueDeserializer) + configureWriteStandardKeyValueRepoRoutes(originalRepo) +} + +inline fun Route.configureStandardKeyValueRepoRoutes( + originalRepo: StandardKeyValueRepo, + idsSerializer: DeserializationStrategy, + valueSerializer: DeserializationStrategy, + serialFormat: StringFormat +) = configureStandardKeyValueRepoRoutes( + originalRepo, + { + serialFormat.decodeFromString(idsSerializer, it.decodeURLQueryComponent()) + }, + { + serialFormat.decodeFromString(valueSerializer, it.decodeURLQueryComponent()) + } +) + +inline fun Route.configureStandardKeyValueRepoRoutes( + originalRepo: StandardKeyValueRepo, + idsSerializer: DeserializationStrategy, + valueSerializer: DeserializationStrategy, + serialFormat: BinaryFormat +) = configureStandardKeyValueRepoRoutes( + originalRepo, + { + serialFormat.decodeHex(idsSerializer, it) + }, + { + serialFormat.decodeHex(valueSerializer, it) + } +) + diff --git a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartReadKeyValueRepo.kt b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartReadKeyValueRepo.kt new file mode 100644 index 00000000000..8ade9432458 --- /dev/null +++ b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartReadKeyValueRepo.kt @@ -0,0 +1,104 @@ +package dev.inmo.micro_utils.repos.ktor.server.key_value + +import dev.inmo.micro_utils.ktor.common.* +import dev.inmo.micro_utils.ktor.server.* +import dev.inmo.micro_utils.pagination.PaginationResult +import dev.inmo.micro_utils.pagination.extractPagination +import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo +import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo +import dev.inmo.micro_utils.repos.ktor.common.idParameterName +import dev.inmo.micro_utils.repos.ktor.common.key_value.* +import dev.inmo.micro_utils.repos.ktor.common.valueParameterName +import io.ktor.http.* +import io.ktor.server.application.call +import io.ktor.server.response.respond +import io.ktor.server.response.responseType +import io.ktor.server.routing.Route +import io.ktor.server.routing.get +import io.ktor.util.InternalAPI +import io.ktor.util.reflect.typeInfo +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@OptIn(InternalAPI::class) +inline fun Route.configureReadStandardKeyValueRepoRoutes ( + originalRepo: ReadStandardKeyValueRepo, + noinline idDeserializer: suspend (String) -> Key, + noinline valueDeserializer: suspend (String) -> Value +) { + get(getRoute) { + val key = idDeserializer( + call.getQueryParameterOrSendError(idParameterName) ?: return@get + ) + + originalRepo.get(key) ?.let { + call.respond(it) + } ?: call.respond(HttpStatusCode.NoContent) + } + + val paginationWithValuesTypeInfo = typeInfo>() + get(valuesRoute) { + val pagination = call.request.queryParameters.extractPagination + val reversed = call.getQueryParameter(reversedParameterName) ?.toBoolean() ?: false + + call.response.responseType = paginationWithValuesTypeInfo + call.response.pipeline.execute(call, originalRepo.values(pagination, reversed) as Any) + } + + get(keysRoute) { + val pagination = call.request.queryParameters.extractPagination + val reversed = call.getQueryParameterOrSendError(reversedParameterName) ?.toBoolean() ?: false + val value = call.getQueryParameter(valueParameterName) ?.let { + valueDeserializer(it) + } + + call.respond( + value ?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed) + ) + } + + get(containsRoute) { + val key = idDeserializer( + call.getQueryParameterOrSendError(idParameterName) ?: return@get + ) + + call.respond( + originalRepo.contains(key) + ) + } + + get(countRoute) { + call.respond(originalRepo.count()) + } +} + +inline fun Route.configureReadStandardKeyValueRepoRoutes( + originalRepo: ReadStandardKeyValueRepo, + idsSerializer: DeserializationStrategy, + valueSerializer: DeserializationStrategy, + serialFormat: StringFormat +) = configureReadStandardKeyValueRepoRoutes( + originalRepo, + { + serialFormat.decodeFromString(idsSerializer, it.decodeURLQueryComponent()) + }, + { + serialFormat.decodeFromString(valueSerializer, it.decodeURLQueryComponent()) + } +) + +inline fun Route.configureReadStandardKeyValueRepoRoutes( + originalRepo: ReadStandardKeyValueRepo, + idsSerializer: DeserializationStrategy, + valueSerializer: DeserializationStrategy, + serialFormat: BinaryFormat +) = configureReadStandardKeyValueRepoRoutes( + originalRepo, + { + serialFormat.decodeHex(idsSerializer, it) + }, + { + serialFormat.decodeHex(valueSerializer, it) + } +) + diff --git a/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartWriteKeyValueRepo.kt b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartWriteKeyValueRepo.kt new file mode 100644 index 00000000000..bac70871d05 --- /dev/null +++ b/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key_value/NewKtorStandartWriteKeyValueRepo.kt @@ -0,0 +1,45 @@ +package dev.inmo.micro_utils.repos.ktor.server.key_value + +import dev.inmo.micro_utils.ktor.server.* +import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo +import dev.inmo.micro_utils.repos.ktor.common.key_value.* +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.call +import io.ktor.server.request.receive +import io.ktor.server.response.respond +import io.ktor.server.routing.Route +import io.ktor.server.routing.post +import io.ktor.util.reflect.typeInfo + +inline fun Route.configureWriteStandardKeyValueRepoRoutes ( + originalRepo: WriteStandardKeyValueRepo +) { + includeWebsocketHandling( + onNewValueRoute, + originalRepo.onNewValue + ) + + includeWebsocketHandling( + onValueRemovedRoute, + originalRepo.onValueRemoved + ) + + val mapType = typeInfo>() + val listKeysType = typeInfo>() + val listValuesType = typeInfo>() + + post(setRoute) { + originalRepo.set(call.receive(mapType)) + call.respond(HttpStatusCode.OK) + } + + post(unsetRoute) { + originalRepo.unset(call.receive(listKeysType)) + call.respond(HttpStatusCode.OK) + } + + post(unsetWithValuesRoute) { + originalRepo.unsetWithValues(call.receive(listValuesType)) + call.respond(HttpStatusCode.OK) + } +}