Compare commits

...

45 Commits

Author SHA1 Message Date
eaa143f7d7 kv rework, fixes in map keyvalue repo, tests 2022-06-03 22:33:26 +06:00
bcb0e42fa2 add new started clients for crud 2022-06-03 17:52:58 +06:00
8eed435302 start improvements in ktor parts 2022-06-03 12:22:51 +06:00
0e4a63057f start 0.11.0 2022-06-02 23:50:06 +06:00
1af5faa440 Merge pull request #159 from InsanusMokrassar/0.10.8
0.10.8
2022-05-29 20:06:14 +06:00
7412217b0c add Element.isOverflow 2022-05-29 10:22:57 +06:00
3e749d75b7 start 0.10.8 2022-05-29 10:19:39 +06:00
846b5c87c9 Merge pull request #158 from InsanusMokrassar/0.10.7
0.10.7
2022-05-28 23:59:54 +06:00
7b5e84f80b doForAll and getForAll not suspend 2022-05-28 22:47:06 +06:00
27822a8a66 start 0.10.7 2022-05-28 18:48:51 +06:00
36f4e7ec37 Merge pull request #157 from InsanusMokrassar/0.10.6
0.10.6
2022-05-28 09:47:10 +06:00
187e84ad65 fill changelog and downgrade coroutines 2022-05-28 09:45:18 +06:00
195fe221c4 Update libs.versions.toml 2022-05-28 00:25:59 +06:00
6f9c19bbf6 fixes in ResizeObserver 2022-05-27 11:39:09 +06:00
65bdab4f7e ResizeObserver 2022-05-27 02:37:58 +06:00
c4d5fcfc22 start 0.10.6 2022-05-27 02:37:14 +06:00
43232afa62 Merge pull request #156 from InsanusMokrassar/0.10.5
0.10.5
2022-05-19 10:44:00 -04:00
18908a01d7 rewrite the way to use onStateHandlingErrorHandler inside of DefaultStatesMachine 2022-05-19 20:42:22 +06:00
b22fbfb3bc StateHandlingErrorHandler 2022-05-19 20:22:34 +06:00
57aaea88b6 Update gradle.properties 2022-05-17 12:38:15 +06:00
140949c5ea Update CHANGELOG.md 2022-05-17 12:32:59 +06:00
639241e0d3 Update libs.versions.toml 2022-05-17 12:31:50 +06:00
e0eb42bc2d update dexcount 2022-05-16 17:38:49 +06:00
7990b21cc5 add SharedPreferencesKeyValueRepo 2022-05-16 17:34:34 +06:00
2ba5c97709 start 0.10.5 2022-05-16 17:33:49 +06:00
33b7c85fc2 Merge pull request #154 from InsanusMokrassar/0.10.4
0.10.4
2022-05-12 17:24:13 +06:00
7f6c02ffdf update serialization 2022-05-12 17:04:53 +06:00
f368616e6f start 0.10.4 2022-05-12 17:03:59 +06:00
dd632f4203 Merge pull request #153 from InsanusMokrassar/0.10.3
0.10.3
2022-05-11 08:42:24 +06:00
a8f3ae501b Update CHANGELOG.md 2022-05-11 08:34:44 +06:00
ea76963ac2 Update libs.versions.toml 2022-05-11 08:33:55 +06:00
99dfa97958 Merge pull request #152 from InsanusMokrassar/0.10.3
0.10.3
2022-05-11 02:49:35 +06:00
f68270a5b3 fixes in AccumulatorFlow 2022-05-11 02:38:48 +06:00
542ed81034 start 0.10.3 2022-05-11 01:18:48 +06:00
404a11f5e7 Merge pull request #151 from InsanusMokrassar/0.10.2
0.10.2
2022-05-10 23:44:39 +06:00
411221070e Update CHANGELOG.md 2022-05-10 23:44:13 +06:00
25d35d0c76 Update compose 2022-05-10 15:03:04 +06:00
c72904d61c start 0.10.2 2022-05-10 15:00:56 +06:00
b94c9acd26 Merge pull request #150 from InsanusMokrassar/0.10.1
0.10.1
2022-04-29 23:20:27 +06:00
bdb4988569 fixes 2022-04-29 19:46:15 +06:00
1dafd1352a hotfix 2022-04-29 18:47:23 +06:00
31fbd3cad7 hmacSha256 and hex 2022-04-29 18:42:59 +06:00
848bc5ec10 Update gradle.properties 2022-04-28 23:45:57 +06:00
4d0ad826a0 Update libs.versions.toml 2022-04-28 23:32:35 +06:00
6955820fcc Merge pull request #149 from InsanusMokrassar/0.10.0
0.10.0
2022-04-28 20:30:52 +06:00
45 changed files with 1642 additions and 72 deletions

View File

@@ -1,5 +1,60 @@
# Changelog
## 0.11.0
## 0.10.8
* `Common`
* Add `Element.isOverflow*` extension properties
## 0.10.7
* `Pagination`:
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
## 0.10.6
* `Versions`
* `Ktor`: `2.0.1` -> `2.0.2`
* `Common`
* `JS`:
* Add `ResizeObserver` functionality
## 0.10.5
* `Versions`
* `Compose`: `1.2.0-alpha01-dev683` -> `1.2.0-alpha01-dev686`
* `Repos`
* `Android`:
* New function `SharedPreferencesKeyValueRepo`
* `FSM`
* Add `StateHandlingErrorHandler` and opportunity to handle states handling errors
## 0.10.4
* `Versions`:
* `Serialization`: `1.3.2` -> `1.3.3`
## 0.10.3
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev682` -> `1.2.0-alpha01-dev683`
* `Coroutines`:
* Fixes in `AccumulatorFlow`
## 0.10.2
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev675` -> `1.2.0-alpha01-dev682`
## 0.10.1
* `Versions`:
* `Ktor`: `2.0.0` -> `2.0.1`
* `Crypto`:
* Add `hmacSha256`
* Add `hex`
## 0.10.0
* `Versions`:

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -0,0 +1,58 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -6,11 +6,12 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException
private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private sealed interface AccumulatorFlowStep<T>
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
/**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
@@ -26,12 +27,12 @@ class AccumulatorFlow<T>(
private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsForBroadcast = mutableListOf<Channel<T>>()
private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
when (step) {
is DataRetrievedAccumulatorFlowStep -> {
if (activeData.first() === step.data) {
if (activeData.firstOrNull() === step.data) {
dataMutex.withLock {
activeData.removeFirst()
}
@@ -42,7 +43,7 @@ class AccumulatorFlow<T>(
dataMutex.withLock {
val dataToSend = activeData.toList()
safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it as Any) }
dataToSend.forEach { step.channel.send(it) }
}
}
}
@@ -58,24 +59,29 @@ class AccumulatorFlow<T>(
channelsMutex.withLock {
channelsForBroadcast.forEach { channel ->
safelyWithResult {
channel.send(it as Any)
channel.send(it)
}
}
}
}
override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel))
for (data in channel) {
try {
collector.emit(data as T)
steps.send(DataRetrievedAccumulatorFlowStep(data))
} finally {
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely {
for (data in channel) {
val emitResult = runCatchingSafely {
collector.emit(data)
}
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
steps.send(DataRetrievedAccumulatorFlowStep(data))
}
emitResult.getOrThrow()
}
}
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow()
}
}

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.crypto
val HEX_ARRAY = "0123456789abcdef".toCharArray()
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
val v: Int = this[j].toInt() and 0xFF
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
}
return hexChars.concatToString()
}
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.crypto
expect fun SourceString.hmacSha256(key: String): String

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class Hex {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultHex = text.hex()
assertEquals(
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
resultHex
)
}
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class HmacSHA256 {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultSha = text.hmacSha256("Example")
assertEquals("5a481d59329ef862b158eedc95392ebb22492ba3014661a3379d8201db992484", resultSha)
}
}

View File

@@ -7,3 +7,7 @@ external interface CryptoJs {
@JsModule("crypto-js")
@JsNonModule
external val CryptoJS: CryptoJs
actual fun SourceString.hmacSha256(key: String): String {
return CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.crypto
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
actual fun SourceString.hmacSha256(key: String): String {
val mac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
mac.init(secretKey)
return mac.doFinal(toByteArray()).hex()
}

View File

@@ -3,6 +3,8 @@ package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -13,13 +15,24 @@ import kotlinx.coroutines.sync.withLock
* handling until [start] method will be called
*/
interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
): T? {
return runCatchingSafely {
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}.getOrElse {
onStateHandlingErrorHandler(state, it)
}
}
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler())
}
/**
@@ -38,8 +51,9 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
*/
operator fun <T: State> invoke(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers)
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
}
}
@@ -52,12 +66,17 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : StatesMachine<T> {
/**
* Will call [launchStateHandling] for state handling
*/
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
/**
* This
*/

View File

@@ -1,6 +1,8 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
@@ -20,9 +22,11 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : DefaultStatesMachine<T>(
statesManager,
handlers
handlers,
onStateHandlingErrorHandler
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
@@ -34,7 +38,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (compare(previousState, actualState)) {
if (shouldReplaceJob(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.fsm.common.utils
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.10.0
android_code_version=115
version=0.11.0
android_code_version=124

View File

@@ -1,22 +1,22 @@
[versions]
kt = "1.6.21"
kt-serialization = "1.3.2"
kt-serialization = "1.3.3"
kt-coroutines = "1.6.1"
jb-compose = "1.2.0-alpha01-dev675"
jb-compose = "1.2.0-alpha01-dev686"
jb-exposed = "0.38.2"
jb-dokka = "1.6.21"
klock = "2.7.0"
uuid = "0.4.0"
ktor = "2.0.0"
ktor = "2.0.2"
gh-release = "2.3.7"
android-gradle = "7.0.4"
dexcount = "3.0.1"
dexcount = "3.1.0"
android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1"
@@ -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" }

View File

@@ -0,0 +1,55 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import io.ktor.websocket.serialization.sendSerializedBase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T> HttpClient.createStandardWebsocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
sendSerialized(received.data)
}
}
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
inline fun <reified T : Any> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
sendSerialized(it)
}
}
}
}

View File

@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
suspend fun <T> doForAll(
inline fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
) {
doWithPagination(initialPagination) {
block(it).let(paginationMapper)
}
}
suspend fun <T> doForAllWithNextPaging(
inline fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
@@ -23,9 +23,9 @@ suspend fun <T> doForAllWithNextPaging(
)
}
suspend fun <T> doAllWithCurrentPaging(
inline fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
@@ -34,7 +34,7 @@ suspend fun <T> doAllWithCurrentPaging(
)
}
suspend fun <T> doForAllWithCurrentPaging(
inline fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
) = doAllWithCurrentPaging(initialPagination, block)

View File

@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
suspend fun <T> getAll(
inline fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
): List<T> {
val results = mutableListOf<T>()
doForAll(initialPagination, paginationMapper) {
@@ -16,46 +16,45 @@ suspend fun <T> getAll(
return results.toList()
}
suspend fun <T, R> R.getAllBy(
inline fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: suspend R.(Pagination) -> PaginationResult<T>
block: R.(Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ paginationMapper(it) },
{ block(it) }
)
suspend fun <T> getAllWithNextPaging(
inline fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
block
)
suspend fun <T, R> R.getAllByWithNextPaging(
inline fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
block: R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging(
initialPagination,
{ block(it) }
)
suspend fun <T> getAllWithCurrentPaging(
inline fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T>
block: (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
block
)
suspend fun <T, R> R.getAllByWithCurrentPaging(
inline fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
block: R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging(
initialPagination,
{ block(it) }
)
initialPagination
) { block(it) }

View File

@@ -42,4 +42,11 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
class DelegateBasedStandardCRUDRepo<ObjectType, IdType, InputValueType>(
readDelegate: ReadStandardCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
) : StandardCRUDRepo<ObjectType, IdType, InputValueType>,
ReadStandardCRUDRepo<ObjectType, IdType> by readDelegate,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> by writeDelegate

View File

@@ -50,3 +50,10 @@ interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value
}
}
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
class DelegateBasedStandardKeyValueRepo<Key, Value>(
readDelegate: ReadStandardKeyValueRepo<Key, Value>,
writeDelegate: WriteStandardKeyValueRepo<Key, Value>
) : StandardKeyValueRepo<Key, Value>,
ReadStandardKeyValueRepo<Key, Value> by readDelegate,
WriteStandardKeyValueRepo<Key, Value> by writeDelegate

View File

@@ -160,3 +160,9 @@ class KeyValueStore<T : Any> internal constructor (
}
}
}
inline fun <T : Any> SharedPreferencesKeyValueRepo(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)

View File

@@ -74,19 +74,24 @@ abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
map: MutableMap<IdType, ObjectType>,
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
override suspend fun updateObject(
newValue: InputValueType,
id: IdType,
old: ObjectType
): ObjectType = updateCallback(newValue, id, old)
): ObjectType = map.updateCallback(newValue, id, old)
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = createCallback(newValue)
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
}
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback)
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
) = MapCRUDRepo(this, updateCallback, createCallback)

View File

@@ -78,11 +78,11 @@ class WriteMapKeyValueRepo<Key, Value>(
}
override suspend fun unsetWithValues(toUnset: List<Value>) {
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)
}
}
}

View File

@@ -5,10 +5,12 @@ 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
@Deprecated("Use KtorReadStandardCrudRepoClient instead")
class KtorReadStandardCrudRepo<ObjectType, IdType> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
@@ -38,9 +40,7 @@ class KtorReadStandardCrudRepo<ObjectType, IdType> (
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
),
objectsSerializerNullable
)
@@ -49,9 +49,7 @@ class KtorReadStandardCrudRepo<ObjectType, IdType> (
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
),
Boolean.serializer()
)

View File

@@ -0,0 +1,94 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
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
import io.ktor.http.*
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorReadStandardCrudRepoClient<ObjectType, IdType> (
private val baseUrl: String,
private val httpClient: HttpClient,
private val objectType: TypeInfo,
private val contentType: ContentType,
private val idSerializer: suspend (IdType) -> String
) : ReadStandardCRUDRepo<ObjectType, IdType> {
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts)
) {
contentType(contentType)
}.body()
override suspend fun getById(id: IdType): ObjectType? = httpClient.get(
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
idParameterName to idSerializer(id)
)
)
) {
contentType(contentType)
}.takeIf { it.status != HttpStatusCode.NoContent } ?.body<ObjectType>(objectType)
override suspend fun contains(id: IdType): Boolean = httpClient.get(
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
idParameterName to idSerializer(id)
)
)
) {
contentType(contentType)
}.body()
override suspend fun count(): Long = httpClient.get(
buildStandardUrl(
baseUrl,
countRouting
)
) {
contentType(contentType)
}.body()
}
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
contentType,
idSerializer
)
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
serialFormat.encodeHex(idsSerializer, it)
}

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
@Deprecated("Use KtorStandardCrudRepoClient instead")
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
baseUrl: String,
baseSubpart: String,

View File

@@ -0,0 +1,122 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import io.ktor.client.utils.EmptyContent.contentType
import io.ktor.http.ContentType
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorStandardCrudRepoClient<ObjectType, IdType, InputValue> (
readDelegate: ReadStandardCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteStandardCRUDRepo<ObjectType, IdType, InputValue>
) : StandardCRUDRepo<ObjectType, IdType, InputValue> by DelegateBasedStandardCRUDRepo(
readDelegate,
writeDelegate
) {
companion object {
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
httpClient: HttpClient,
objectTypeInfo: TypeInfo,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient(
KtorReadStandardCrudRepoClient(
baseUrl,
httpClient,
objectTypeInfo,
contentType,
idSerializer
),
KtorWriteStandardCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
contentType
)
)
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
objectTypeInfo: TypeInfo,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart),
httpClient,
objectTypeInfo,
contentType,
idSerializer
)
}
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
contentType,
idSerializer
)
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
serialFormat.encodeHex(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart),
httpClient,
contentType,
idSerializer
)
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
@Deprecated("Use KtorWriteStandardCrudRepoClient instead")
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,

View File

@@ -0,0 +1,89 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.*
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.*
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
class KtorWriteStandardCrudRepoClient<ObjectType, IdType, InputValue> (
private val baseUrl: String,
private val httpClient: HttpClient,
override val newObjectsFlow: Flow<ObjectType>,
override val updatedObjectsFlow: Flow<ObjectType>,
override val deletedObjectsIdsFlow: Flow<IdType>,
private val createSetup: suspend HttpRequestBuilder.(List<InputValue>) -> Unit,
private val updateSetup: suspend HttpRequestBuilder.(List<UpdatedValuePair<IdType, InputValue>>) -> Unit,
private val deleteByIdSetup: suspend HttpRequestBuilder.(List<IdType>) -> Unit,
private val createBodyGetter: suspend HttpResponse.() -> List<ObjectType>,
private val updateBodyGetter: suspend HttpResponse.() -> List<ObjectType>
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValue> {
override suspend fun create(values: List<InputValue>): List<ObjectType> = httpClient.post(
buildStandardUrl(baseUrl, createRouting)
) {
createSetup(values)
}.createBodyGetter()
override suspend fun update(
values: List<UpdatedValuePair<IdType, InputValue>>
): List<ObjectType> = httpClient.post(
buildStandardUrl(baseUrl, updateManyRouting)
) {
updateSetup(values)
}.updateBodyGetter()
override suspend fun update(id: IdType, value: InputValue): ObjectType? = update(listOf(id to value)).firstOrNull()
override suspend fun deleteById(ids: List<IdType>) {
httpClient.post(
buildStandardUrl(baseUrl, deleteByIdRouting)
) {
deleteByIdSetup(ids)
}.status
}
companion object {
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType
) = KtorWriteStandardCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
{
contentType(contentType)
setBody(it)
},
{
contentType(contentType)
setBody(it)
},
{
contentType(contentType)
setBody(it)
},
{ body() },
{ body() }
)
}
}

View File

@@ -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<Key, Value>(
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<Key, Value> {
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<Value>(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<Value> = httpClient.get(
buildStandardUrl(baseUrl, valuesRoute, pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()))
) {
contentType(contentType)
}.body(paginationResultObjectsTypeInfo)
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = 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<Key> = 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 <reified Key, reified Value> KtorReadStandardKeyValueRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (Key) -> String,
noinline valueSerializer: suspend (Value) -> String
) = KtorReadStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
typeInfo<Value>(),
typeInfo<PaginationResult<Value>>(),
typeInfo<PaginationResult<Key>>(),
idSerializer,
valueSerializer
)
inline fun <reified Key, reified Value> KtorReadStandardKeyValueRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorReadStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
{
serialFormat.encodeToString(idsSerializer, it).encodeURLQueryComponent()
}
) {
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
}
inline fun <reified Key, reified Value> KtorReadStandardKeyValueRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<Key>,
valuesSerializer: KSerializer<Value>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorReadStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
{
serialFormat.encodeHex(idsSerializer, it)
}
) {
serialFormat.encodeHex(valuesSerializer, it)
}

View File

@@ -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<Key, Value> (
readDelegate: ReadStandardKeyValueRepo<Key, Value>,
writeDelegate: WriteStandardKeyValueRepo<Key, Value>
) : StandardKeyValueRepo<Key, Value> by DelegateBasedStandardKeyValueRepo(
readDelegate,
writeDelegate
) {
companion object {
inline operator fun <reified Key, reified Value> 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 <reified Key, reified Value> 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 <reified Key, reified Value> KtorStandardKeyValueRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
idSerializer: SerializationStrategy<Key>,
valueSerializer: SerializationStrategy<Value>,
serialFormat: StringFormat,
) = KtorStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
{
serialFormat.encodeToString(idSerializer, it).encodeURLQueryComponent()
}
) {
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
}
inline fun <reified Key, reified Value> KtorStandardKeyValueRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
idSerializer: SerializationStrategy<Key>,
valueSerializer: SerializationStrategy<Value>,
serialFormat: BinaryFormat,
) = KtorStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
{
serialFormat.encodeHex(idSerializer, it)
}
) {
serialFormat.encodeHex(valueSerializer, it)
}

View File

@@ -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<Key, Value>(
private val baseUrl: String,
private val httpClient: HttpClient,
private val contentType: ContentType,
override val onNewValue: Flow<Pair<Key, Value>>,
override val onValueRemoved: Flow<Key>,
private val idsListTypeInfo: TypeInfo,
private val objectsListTypeInfo: TypeInfo,
private val idsToObjectsMapTypeInfo: TypeInfo
) : WriteStandardKeyValueRepo<Key, Value> {
@OptIn(InternalAPI::class)
override suspend fun unsetWithValues(toUnset: List<Value>) {
httpClient.post(
buildStandardUrl(baseUrl, unsetWithValuesRoute)
) {
body = toUnset
bodyType = objectsListTypeInfo
contentType(contentType)
}.status
}
@OptIn(InternalAPI::class)
override suspend fun unset(toUnset: List<Key>) {
httpClient.post(
buildStandardUrl(baseUrl, unsetRoute)
) {
body = toUnset
bodyType = idsListTypeInfo
contentType(contentType)
}.status
}
@OptIn(InternalAPI::class)
override suspend fun set(toSet: Map<Key, Value>) {
httpClient.post(
buildStandardUrl(baseUrl, setRoute)
) {
body = toSet
bodyType = idsToObjectsMapTypeInfo
contentType(contentType)
}.status
}
companion object {
inline operator fun <reified Key, reified Value> invoke(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType
) = KtorWriteStandardKeyValueRepoClient<Key, Value>(
baseUrl,
httpClient,
contentType,
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
typeInfo<List<Key>>(),
typeInfo<List<Value>>(),
typeInfo<Map<Key, Value>>()
)
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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"
const val reversedParameterName = "reversed"

View File

@@ -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<Int, ComplexData>()
val repo = MapCRUDRepo<ComplexData, Int, SimpleData>(
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<ComplexData, Int, SimpleData>(
"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()
}
}
}

View File

@@ -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<SimpleData> = (0 until 100).map { SimpleData(("$it")) },
val title: String = uuid4().toString()
)

View File

@@ -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<Int, ComplexData>()
val repo = MapKeyValueRepo<Int, ComplexData>(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<Int, ComplexData>(
"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()
}
}
}

View File

@@ -0,0 +1,7 @@
import com.benasher44.uuid.uuid4
import kotlinx.serialization.Serializable
@Serializable
data class SimpleData(
val title: String = uuid4().toString()
)

View File

@@ -0,0 +1,72 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.decodeHex
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
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.*
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
noinline idDeserializer: suspend (String) -> IdType
) {
get(getByPaginationRouting) {
val pagination = call.request.queryParameters.extractPagination
call.respond(originalRepo.getByPagination(pagination))
}
get(getByIdRouting) {
val id = idDeserializer(
call.getQueryParameterOrSendError(idParameterName) ?: return@get
)
val result = originalRepo.getById(id)
if (result == null) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(result)
}
}
get(containsRouting) {
val id = idDeserializer(
call.getQueryParameterOrSendError(idParameterName) ?: return@get
)
call.respond(
originalRepo.contains(id)
)
}
get(countRouting) {
call.respond(
originalRepo.count()
)
}
}
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = configureReadStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeFromString(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = configureReadStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeHex(idsSerializer, it)
}

View File

@@ -0,0 +1,34 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.StandardCRUDRepo
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.route
import kotlinx.serialization.*
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
noinline idDeserializer: suspend (String) -> IdType
) {
configureReadStandardCrudRepoRoutes(originalRepo, idDeserializer)
configureWriteStandardCrudRepoRoutes(originalRepo)
}
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = configureStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeFromString(idsSerializer, it)
}
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = configureStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeHex(idsSerializer, it)
}

View File

@@ -0,0 +1,39 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
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
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureWriteStandardCrudRepoRoutes(
originalRepo: WriteStandardCRUDRepo<ObjectType, IdType, InputValue>
) {
includeWebsocketHandling(
newObjectsFlowRouting,
originalRepo.newObjectsFlow,
)
includeWebsocketHandling(
updatedObjectsFlowRouting,
originalRepo.updatedObjectsFlow
)
includeWebsocketHandling(
deletedObjectsIdsFlowRouting,
originalRepo.deletedObjectsIdsFlow
)
post(createRouting) {
call.respond(originalRepo.create(call.receive()))
}
post(updateManyRouting) {
call.respond(originalRepo.update(call.receive()))
}
post(deleteByIdRouting) {
call.respond(originalRepo.deleteById(call.receive()))
}
}

View File

@@ -0,0 +1,58 @@
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.StandardCRUDRepo
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
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.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
inline fun <reified Key : Any, reified Value : Any> Route.configureStandardKeyValueRepoRoutes (
originalRepo: StandardKeyValueRepo<Key, Value>,
noinline idDeserializer: suspend (String) -> Key,
noinline valueDeserializer: suspend (String) -> Value
) {
configureReadStandardKeyValueRepoRoutes(originalRepo, idDeserializer, valueDeserializer)
configureWriteStandardKeyValueRepoRoutes(originalRepo)
}
inline fun <reified Key : Any, reified Value : Any> Route.configureStandardKeyValueRepoRoutes(
originalRepo: StandardKeyValueRepo<Key, Value>,
idsSerializer: DeserializationStrategy<Key>,
valueSerializer: DeserializationStrategy<Value>,
serialFormat: StringFormat
) = configureStandardKeyValueRepoRoutes(
originalRepo,
{
serialFormat.decodeFromString(idsSerializer, it.decodeURLQueryComponent())
},
{
serialFormat.decodeFromString(valueSerializer, it.decodeURLQueryComponent())
}
)
inline fun <reified Key : Any, reified Value : Any> Route.configureStandardKeyValueRepoRoutes(
originalRepo: StandardKeyValueRepo<Key, Value>,
idsSerializer: DeserializationStrategy<Key>,
valueSerializer: DeserializationStrategy<Value>,
serialFormat: BinaryFormat
) = configureStandardKeyValueRepoRoutes(
originalRepo,
{
serialFormat.decodeHex(idsSerializer, it)
},
{
serialFormat.decodeHex(valueSerializer, it)
}
)

View File

@@ -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 <reified Key, reified Value> Route.configureReadStandardKeyValueRepoRoutes (
originalRepo: ReadStandardKeyValueRepo<Key, Value>,
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<PaginationResult<Value>>()
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 <reified Key, reified Value> Route.configureReadStandardKeyValueRepoRoutes(
originalRepo: ReadStandardKeyValueRepo<Key, Value>,
idsSerializer: DeserializationStrategy<Key>,
valueSerializer: DeserializationStrategy<Value>,
serialFormat: StringFormat
) = configureReadStandardKeyValueRepoRoutes(
originalRepo,
{
serialFormat.decodeFromString(idsSerializer, it.decodeURLQueryComponent())
},
{
serialFormat.decodeFromString(valueSerializer, it.decodeURLQueryComponent())
}
)
inline fun <reified Key, reified Value> Route.configureReadStandardKeyValueRepoRoutes(
originalRepo: ReadStandardKeyValueRepo<Key, Value>,
idsSerializer: DeserializationStrategy<Key>,
valueSerializer: DeserializationStrategy<Value>,
serialFormat: BinaryFormat
) = configureReadStandardKeyValueRepoRoutes(
originalRepo,
{
serialFormat.decodeHex(idsSerializer, it)
},
{
serialFormat.decodeHex(valueSerializer, it)
}
)

View File

@@ -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 <reified Key : Any, reified Value : Any> Route.configureWriteStandardKeyValueRepoRoutes (
originalRepo: WriteStandardKeyValueRepo<Key, Value>
) {
includeWebsocketHandling(
onNewValueRoute,
originalRepo.onNewValue
)
includeWebsocketHandling(
onValueRemovedRoute,
originalRepo.onValueRemoved
)
val mapType = typeInfo<Map<Key, Value>>()
val listKeysType = typeInfo<List<Key>>()
val listValuesType = typeInfo<List<Value>>()
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)
}
}