Merge pull request #149 from InsanusMokrassar/0.10.0

0.10.0
This commit is contained in:
InsanusMokrassar 2022-04-28 20:30:52 +06:00 committed by GitHub
commit 6955820fcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 187 additions and 149 deletions

View File

@ -1,5 +1,14 @@
# Changelog # Changelog
## 0.10.0
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Compose`: `1.1.1` -> `1.2.0-alpha01-dev675`
* `Exposed`: `0.37.3` -> `0.38.2`
* `Ktor`: `1.6.8` -> `2.0.0`
* `Dokka`: `1.6.10` -> `1.6.21`
## 0.9.24 ## 0.9.24
* `Ktor`: * `Ktor`:

View File

@ -21,6 +21,7 @@ allprojects {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@ -1,3 +1,5 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
@RequiresOptIn( @RequiresOptIn(

View File

@ -43,6 +43,7 @@ private inline fun <T> performChanges(
if (oldOneEqualToNewObject || newOneEqualToOldObject) { if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll( changedList.addAll(
potentialChanges.take(i).mapNotNull { potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
} }
) )
@ -121,7 +122,10 @@ fun <T> Iterable<T>.calculateDiff(
when { when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> { oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> }) changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
})
potentiallyChangedObjects.clear() potentiallyChangedObjects.clear()
} }
else -> { else -> {

View File

@ -27,20 +27,13 @@ sealed interface Either<T1, T2> {
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2")) @Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2? val t2: T2?
get() = optionalT2.dataOrNull() get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
} }
class EitherSerializer<T1, T2>( class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>, t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>, t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> { ) : KSerializer<Either<T1, T2>> {
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class) @OptIn(InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL

View File

@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) { for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) { if (i + count > oldList.lastIndex) {
continue continue
} }
@ -55,7 +55,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in oldList.indices) { for (step in oldList.indices) {
for ((i, v) in withIndex) { for ((i, _) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = ( val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step) if (step == 0) i until oldList.size else (i until oldList.size step step)
@ -104,7 +104,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) { for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) { if (i + count > oldList.lastIndex) {
continue continue
} }
@ -129,15 +129,20 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in oldList.indices) { for (step in oldList.indices) {
for ((i, v) in withIndex) { for ((i, _) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step) val newList = if (step == 0) {
).map { index -> i until oldList.size
} else {
i until oldList.size step step
}
newList.forEach { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also { IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value mutable[index] = it.value
} }
} }
val mutableOldList = oldList.toMutableList() val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable) mutableOldList.applyDiff(mutable)
assertEquals( assertEquals(

View File

@ -16,6 +16,7 @@ fun <T> Flow<T>.toMutableState(
return state return state
} }
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState( inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope) ): MutableState<T> = toMutableState(value, scope)

View File

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

View File

@ -1,19 +1,19 @@
[versions] [versions]
kt = "1.6.10" kt = "1.6.21"
kt-serialization = "1.3.2" kt-serialization = "1.3.2"
kt-coroutines = "1.6.1" kt-coroutines = "1.6.1"
jb-compose = "1.1.1" jb-compose = "1.2.0-alpha01-dev675"
jb-exposed = "0.37.3" jb-exposed = "0.38.2"
jb-dokka = "1.6.10" jb-dokka = "1.6.21"
klock = "2.7.0" klock = "2.7.0"
uuid = "0.4.0" uuid = "0.4.0"
ktor = "1.6.8" ktor = "2.0.0"
gh-release = "2.2.12" gh-release = "2.3.7"
android-gradle = "7.0.4" android-gradle = "7.0.4"
dexcount = "3.0.1" dexcount = "3.0.1"
@ -46,6 +46,8 @@ ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", 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-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", 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" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }

View File

@ -1,14 +1,18 @@
package dev.inmo.micro_utils.ktor.client 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.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.ws import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.ws
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.cio.websocket.Frame import io.ktor.websocket.Frame
import io.ktor.http.cio.websocket.readBytes import io.ktor.websocket.readBytes
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
/** /**
@ -17,43 +21,41 @@ import kotlinx.serialization.DeserializationStrategy
*/ */
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true }, crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> { ): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow { return channelFlow {
val producerScope = this@channelFlow
do { do {
val reconnect = try { val reconnect = runCatchingSafely {
safely { ws(correctedUrl, requestBuilder) {
ws(correctedUrl, requestBuilder) { for (received in incoming) {
for (received in incoming) { when (received) {
when (received) { is Frame.Binary -> send(conversation(received.data))
is Frame.Binary -> producerScope.send(conversation(received.readBytes())) else -> {
else -> { close()
producerScope.close() return@ws
return@ws
}
} }
} }
} }
} }
checkReconnection(null) checkReconnection(null)
} catch (e: Throwable) { }.getOrElse { e ->
checkReconnection(e).also { checkReconnection(e).also {
if (!it) { if (!it) {
producerScope.close(e) close(e)
} }
} }
} }
} while (reconnect) } while (reconnect && isActive)
if (!producerScope.isClosedForSend) {
safely( if (isActive) {
{ it.printStackTrace() } safely {
) { close()
producerScope.close()
} }
} }
} }
@ -65,8 +67,8 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
*/ */
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow( ) = createStandardWebsocketFlow(

View File

@ -4,8 +4,10 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.client.statement.readBytes
import io.ktor.http.* import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.* import kotlinx.serialization.*
@ -85,16 +87,16 @@ class UnifiedRequester(
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
checkReconnection: (Throwable?) -> Boolean, checkReconnection: suspend (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder) ) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder) ) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
} }
val defaultRequester = UnifiedRequester() val defaultRequester = UnifiedRequester()
@ -103,10 +105,8 @@ suspend fun <ResultType> HttpClient.uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get<StandardKtorSerialInputData>( ) = get(url).let {
url serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
).let {
serialFormat.decodeDefault(resultDeserializer, it)
} }
@ -123,10 +123,12 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
bodyInfo: BodyPair<BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post<StandardKtorSerialInputData>(url) { ) = post(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second) setBody(
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
}.let { }.let {
serialFormat.decodeDefault(resultDeserializer, it) serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
} }
suspend fun <ResultType> HttpClient.unimultipart( suspend fun <ResultType> HttpClient.unimultipart(
@ -139,7 +141,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>( ): ResultType = submitFormWithBinaryData(
url, url,
formData = formData { formData = formData {
append( append(
@ -155,7 +157,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
} }
) { ) {
requestBuilder() requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it) } }.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
suspend fun <BodyType, ResultType> HttpClient.unimultipart( suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String, url: String,

View File

@ -4,9 +4,10 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.TemporalFileId import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.onUpload import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import java.net.URLConnection import java.net.URLConnection
@ -20,7 +21,7 @@ actual suspend fun HttpClient.tempUpload(
onUpload: (Long, Long) -> Unit onUpload: (Long, Long) -> Unit
): TemporalFileId { ): TemporalFileId {
val inputProvider = file.inputProvider() val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData<String>( val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath, fullTempUploadDraftPath,
formData = formData { formData = formData {
append( append(
@ -34,6 +35,6 @@ actual suspend fun HttpClient.tempUpload(
} }
) { ) {
onUpload(onUpload) onUpload(onUpload)
} }.bodyAsText()
return TemporalFileId(fileId) return TemporalFileId(fileId)
} }

View File

@ -1,3 +1,5 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.* import kotlinx.serialization.*

View File

@ -19,7 +19,8 @@ kotlin {
api libs.ktor.server api libs.ktor.server
api libs.ktor.server.cio api libs.ktor.server.cio
api libs.ktor.server.host.common api libs.ktor.server.host.common
api libs.ktor.websockets api libs.ktor.server.websockets
api libs.ktor.server.statusPages
} }
} }
} }

View File

@ -2,26 +2,26 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol import io.ktor.http.URLProtocol
import io.ktor.http.cio.websocket.* import io.ktor.server.application.install
import io.ktor.routing.Route import io.ktor.server.application.pluginOrNull
import io.ktor.routing.application import io.ktor.server.routing.Route
import io.ktor.websocket.* import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
protocol: URLProtocol = URLProtocol.WS, protocol: URLProtocol? = null,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData? converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) { ) {
application.apply { application.apply {
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets) pluginOrNull(WebSockets) ?: install(WebSockets)
} }
webSocket(suburl, protocol.name) { webSocket(suburl, protocol ?.name) {
safely { safely {
flow.collect { flow.collect {
converter(it) ?.let { data -> converter(it) ?.let { data ->
@ -37,7 +37,7 @@ fun <T> Route.includeWebsocketHandling(
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol = URLProtocol.WS, protocol: URLProtocol? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling( ) = includeWebsocketHandling(
suburl, suburl,

View File

@ -3,25 +3,21 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.content.PartData import io.ktor.http.content.*
import io.ktor.http.content.forEachPart import io.ktor.server.application.ApplicationCall
import io.ktor.request.receive import io.ktor.server.application.call
import io.ktor.request.receiveMultipart import io.ktor.server.request.receive
import io.ktor.response.respond import io.ktor.server.request.receiveMultipart
import io.ktor.response.respondBytes import io.ktor.server.response.respond
import io.ktor.routing.Route import io.ktor.server.response.respondBytes
import io.ktor.util.asStream import io.ktor.server.routing.Route
import io.ktor.util.cio.writeChannel import io.ktor.server.websocket.WebSocketServerSession
import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.* import kotlinx.serialization.DeserializationStrategy
import java.io.File import kotlinx.serialization.SerializationStrategy
import java.io.File.createTempFile
class UnifiedRouter( class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
@ -31,7 +27,7 @@ class UnifiedRouter(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
protocol: URLProtocol = URLProtocol.WS, protocol: URLProtocol? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter) ) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
@ -197,7 +193,9 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream) it.streamProvider().use {
it.copyTo(fileStream)
}
} }
} }
} }
@ -239,7 +237,9 @@ suspend fun ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream) it.streamProvider().use {
it.copyTo(fileStream)
}
} }
} }
} else { } else {

View File

@ -1,7 +1,7 @@
package dev.inmo.micro_utils.ktor.server package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.application.Application import io.ktor.server.application.Application
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.* import io.ktor.server.engine.*

View File

@ -7,14 +7,14 @@ import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath
import dev.inmo.micro_utils.ktor.common.TemporalFileId import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider import io.ktor.http.content.streamProvider
import io.ktor.request.receiveMultipart import io.ktor.server.application.call
import io.ktor.response.respond import io.ktor.server.request.receiveMultipart
import io.ktor.routing.Route import io.ktor.server.response.respond
import io.ktor.routing.post import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex

View File

@ -1,14 +1,15 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.application.Application import io.ktor.server.application.Application
import io.ktor.application.install import io.ktor.server.application.install
import io.ktor.features.CachingHeaders import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator( data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeaders.Configuration.invoke() } fun interface Element { operator fun CachingHeadersConfig.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(CachingHeaders) { install(CachingHeaders) {

View File

@ -1,8 +1,9 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.application.* import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element
import io.ktor.routing.Route import io.ktor.server.application.*
import io.ktor.routing.Routing import io.ktor.server.routing.Route
import io.ktor.server.routing.Routing
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -18,7 +19,7 @@ class ApplicationRoutingConfigurator(
} }
override fun Application.configure() { override fun Application.configure() {
featureOrNull(Routing) ?.apply { pluginOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }
} ?: install(Routing) { } ?: install(Routing) {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }

View File

@ -1,14 +1,15 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.application.Application import io.ktor.server.application.Application
import io.ktor.application.install import io.ktor.server.application.install
import io.ktor.sessions.Sessions import io.ktor.server.sessions.Sessions
import io.ktor.server.sessions.SessionsConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator( class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun Sessions.Configuration.invoke() } fun interface Element { operator fun SessionsConfig.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(Sessions) { install(Sessions) {

View File

@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.application.Application import io.ktor.server.application.Application
interface KtorApplicationConfigurator { interface KtorApplicationConfigurator {
fun Application.configure() fun Application.configure()

View File

@ -1,14 +1,15 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.application.Application import io.ktor.server.application.Application
import io.ktor.application.install import io.ktor.server.application.install
import io.ktor.features.StatusPages import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class StatusPagesConfigurator( class StatusPagesConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPages.Configuration.invoke() } fun interface Element { operator fun StatusPagesConfig.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(StatusPages) { install(StatusPages) {

View File

@ -1,5 +1,6 @@
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
@ -164,7 +165,7 @@ suspend fun main(vararg args: String) {
val ietfLanguageCodes = json.decodeFromString( val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()), ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink) client.get(ietfLanguageCodesLink).bodyAsText()
).map { ).map {
it.copy( it.copy(
title = it.title title = it.title
@ -175,7 +176,7 @@ suspend fun main(vararg args: String) {
} }
val ietfLanguageCodesWithTagsMap = json.decodeFromString( val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()), ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink) client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText()
).filter { it.withSubtag != it.tag }.groupBy { it.tag } ).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map { val tags = ietfLanguageCodes.map {

View File

@ -1,7 +1,6 @@
package dev.inmo.micro_utils.mime_types package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.KSerializer import kotlinx.serialization.*
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@ -16,6 +15,7 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class) @Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> { object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)

View File

@ -27,4 +27,5 @@ inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? =
null null
} }
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty() inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()

View File

@ -1,7 +1,7 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import io.ktor.application.ApplicationCall
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.server.application.ApplicationCall
val Parameters.extractPagination: Pagination val Parameters.extractPagination: Pagination
get() = SimplePagination( get() = SimplePagination(

View File

@ -1,5 +1,6 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
@Suppress("UNCHECKED_CAST")
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> { interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
suspend fun FromKey.toOutKey() = this as ToKey suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue suspend fun FromValue.toOutValue() = this as ToValue

View File

@ -126,9 +126,10 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: StandardKeyValueRepo<ToKey, ToValue>, private val to: StandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : StandardKeyValueRepo<FromKey, FromValue>, ) : StandardKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper), ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),

View File

@ -132,6 +132,7 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: OneToManyKeyValueRepo<ToKey, ToValue>, private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>

View File

@ -175,9 +175,11 @@ class FileWriteStandardKeyValueRepo(
} }
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26") @Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class FileStandardKeyValueRepo( class FileStandardKeyValueRepo(
folder: File, folder: File,
filesChangedProcessingScope: CoroutineScope? = null filesChangedProcessingScope: CoroutineScope? = null
) : StandardKeyValueRepo<String, File>, ) : StandardKeyValueRepo<String, File>,
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope), WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder) ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder) {
}

View File

@ -16,6 +16,7 @@ fun <T : Any> Context.keyValueStore(
name: String = "default", name: String = "default",
cacheValues: Boolean = false cacheValues: Boolean = false
): StandardKeyValueRepo<String, T> { ): StandardKeyValueRepo<String, T> {
@Suppress("UNCHECKED_CAST")
return cache.getOrPut(name) { return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues) KeyValueStore<T>(this, name, cacheValues)
} as KeyValueStore<T> } as KeyValueStore<T>
@ -62,6 +63,7 @@ class KeyValueStore<T : Any> internal constructor (
} }
override suspend fun get(k: String): T? { override suspend fun get(k: String): T? {
@Suppress("UNCHECKED_CAST")
return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
} }
@ -73,7 +75,10 @@ class KeyValueStore<T : Any> internal constructor (
PaginationResult( PaginationResult(
it.page, it.page,
it.pagesNumber, it.pagesNumber,
it.results.map { it as T }.let { if (reversed) it.reversed() else it }, it.results.map {
@Suppress("UNCHECKED_CAST")
it as T
}.let { if (reversed) it.reversed() else it },
it.size it.size
) )
} }

View File

@ -7,10 +7,10 @@ import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.extractPagination import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.* import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.application.call
import io.ktor.routing.get import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer

View File

@ -6,8 +6,8 @@ import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.StandardCRUDRepo import dev.inmo.micro_utils.repos.StandardCRUDRepo
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.route import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
fun <ObjectType, IdType, InputValue> Route.configureStandardCrudRepoRoutes( fun <ObjectType, IdType, InputValue> Route.configureStandardCrudRepoRoutes(

View File

@ -5,10 +5,9 @@ import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.* import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.* import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.post import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.* import kotlinx.serialization.builtins.*

View File

@ -6,8 +6,8 @@ import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.StandardKeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.route import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
fun <K, V> Route.configureStandardKeyValueRepoRoutes( fun <K, V> Route.configureStandardKeyValueRepoRoutes(

View File

@ -8,10 +8,10 @@ import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.key_value.* import dev.inmo.micro_utils.repos.ktor.common.key_value.*
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.application.call
import io.ktor.routing.get import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer

View File

@ -6,8 +6,8 @@ import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.key_value.* import dev.inmo.micro_utils.repos.ktor.common.key_value.*
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.post import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.* import kotlinx.serialization.builtins.*

View File

@ -6,8 +6,8 @@ import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.route import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes( fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(

View File

@ -6,15 +6,14 @@ import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.extractPagination import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.*
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.* import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.application.call
import io.ktor.routing.get import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer

View File

@ -5,10 +5,9 @@ import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.* import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.* import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.routing.Route import io.ktor.server.routing.Route
import io.ktor.routing.post import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.* import kotlinx.serialization.builtins.*