module 4 lesson 1

This commit is contained in:
Александр Веденёв
2025-03-04 19:05:44 +07:00
parent 492d7ef010
commit 66fa1774f2
39 changed files with 1545 additions and 13 deletions

View File

@ -4,4 +4,29 @@ plugins {
}
group = "ru.otus.messenger"
version = "0.0.1"
version = "0.0.1"
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
group = rootProject.group
version = rootProject.version
}
ext {
val specDir = layout.projectDirectory.dir("../specs")
set("spec-v1", specDir.file("specs-v1.yaml").toString())
}
tasks {
arrayOf("build", "clean", "check").forEach {tsk ->
create(tsk) {
group = "build"
dependsOn(subprojects.map { it.getTasksByName(tsk,false)})
}
}
}

View File

@ -0,0 +1,16 @@
plugins {
id("build-jvm")
}
group = rootProject.group
version = rootProject.version
dependencies {
implementation(kotlin("stdlib"))
implementation(libs.kotlin.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(project(":ok-messenger-api-v1"))
implementation(project(":ok-messenger-common"))
testImplementation(kotlin("test-junit"))
}

View File

@ -0,0 +1,148 @@
package ru.otus.messenger.api.v1.mappers
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import ru.otus.messenger.api.v1.mappers.exceptions.UnknownRequestClass
import ru.otus.messenger.api.v1.models.*
import ru.otus.messenger.common.models.*
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.stubs.Stubs
fun ChatContext.fromTransport(request: IRequest) = when (request) {
is ChatCreateRequest -> fromTransport(request)
is ChatReadRequest -> fromTransport(request)
is ChatDeleteRequest -> fromTransport(request)
is ChatSearchRequest -> fromTransport(request)
is ChatUpdateRequest -> fromTransport(request)
else -> throw UnknownRequestClass(request.javaClass)
}
private fun String?.toChatId() = this?.let { ChatId(it) } ?: ChatId.NONE
private fun String?.toOwnerId() = this?.let { ChatOwnerId(it) } ?: ChatOwnerId.NONE
private fun String?.toChatUserId() = this?.let { ChatUserId(it) } ?: ChatUserId.NONE
private fun String?.toChatMetadata() = this?.let { ChatMetadata(Json.parseToJsonElement(it).jsonObject) } ?: ChatMetadata.NONE
private fun Boolean?.toChatArchiveFlag() = this?.let { ChatArchiveFlag(it) } ?: ChatArchiveFlag.NONE
private fun String?.toParticipants() = this?.let { mutableSetOf(it.toChatUserId()) } ?: mutableSetOf()
private fun Debug?.transportToWorkMode(): WorkMode = when (this?.mode) {
DebugMode.PROD -> WorkMode.PROD
DebugMode.TEST -> WorkMode.TEST
DebugMode.STUB -> WorkMode.STUB
null -> WorkMode.PROD
}
private fun Debug?.transportToStubCase(): Stubs = when (this?.stub) {
DebugStubs.SUCCESS -> Stubs.SUCCESS
DebugStubs.NOT_FOUND -> Stubs.NOT_FOUND
DebugStubs.VALUE_ERROR -> Stubs.VALUE_ERROR
DebugStubs.MISSING_DATA -> Stubs.MISSING_DATA
DebugStubs.CANNOT_DELETE -> Stubs.CANNOT_DELETE
null -> Stubs.NONE
}
fun ChatContext.fromTransport(request: ChatCreateRequest) {
command = ChatCommand.CREATE
chatRequest = request.chat?.toInternal() ?: MessengerChat()
workMode = request.debug.transportToWorkMode()
stubCase = request.debug.transportToStubCase()
}
fun ChatContext.fromTransport(request: ChatReadRequest) {
command = ChatCommand.READ
chatRequest = request.chatId.toChatId().toInternal()
workMode = request.debug.transportToWorkMode()
stubCase = request.debug.transportToStubCase()
}
fun ChatContext.fromTransport(request: ChatDeleteRequest) {
command = ChatCommand.DELETE
chatRequest = request.chatId.toChatId().toInternal()
workMode = request.debug.transportToWorkMode()
stubCase = request.debug.transportToStubCase()
}
fun ChatContext.fromTransport(request: ChatUpdateRequest) {
command = ChatCommand.UPDATE
chatRequest = request.chat?.toInternal() ?: MessengerChat()
workMode = request.debug.transportToWorkMode()
stubCase = request.debug.transportToStubCase()
}
fun ChatContext.fromTransport(request: ChatSearchRequest) {
command = ChatCommand.SEARCH
chatRequest = request.criteria?.toInternal() ?: MessengerChat()
workMode = request.debug.transportToWorkMode()
stubCase = request.debug.transportToStubCase()
}
private fun ChatCreateRequestAllOfChat.toInternal(): MessengerChat = MessengerChat(
title = this.title ?: "",
description = this.description ?: "",
type = this.type.fromTransport(),
mode = this.mode.fromTransport(),
ownerId = this.ownerId.toOwnerId(),
participants = this.participants.fromTransport(),
metadata = this.metadata.toChatMetadata(),
)
private fun ChatUpdateRequestAllOfChat.toInternal(): MessengerChat = MessengerChat(
id = this.chatId.toChatId(),
title = this.title ?: "",
description = this.description ?: "",
mode = this.mode.fromTransport(),
isArchived = this.isArchived.toChatArchiveFlag(),
metadata = this.metadata.toChatMetadata(),
)
private fun ChatSearchRequestAllOfCriteria.toInternal(): MessengerChat = MessengerChat(
title = this.title ?: "",
type = this.type.fromTransport(),
mode = this.mode.fromTransport(),
participants = this.participant.toParticipants(),
)
private fun ChatId.toInternal(): MessengerChat = if (this.asString() != "") {
MessengerChat(id = this)
} else {
MessengerChat()
}
private fun ChatCreateRequestAllOfChat.Type?.fromTransport() = when (this) {
ChatCreateRequestAllOfChat.Type.PRIVATE -> ChatType.PRIVATE
ChatCreateRequestAllOfChat.Type.GROUP -> ChatType.GROUP
ChatCreateRequestAllOfChat.Type.CHANNEL -> ChatType.CHANNEL
null -> ChatType.NONE
}
private fun ChatCreateRequestAllOfChat.Mode?.fromTransport() = when (this) {
ChatCreateRequestAllOfChat.Mode.PERSONAL -> ChatMode.PERSONAL
ChatCreateRequestAllOfChat.Mode.WORK -> ChatMode.WORK
null -> ChatMode.NONE
}
private fun ChatUpdateRequestAllOfChat.Mode?.fromTransport() = when (this) {
ChatUpdateRequestAllOfChat.Mode.PERSONAL -> ChatMode.PERSONAL
ChatUpdateRequestAllOfChat.Mode.WORK -> ChatMode.WORK
null -> ChatMode.NONE
}
private fun ChatSearchRequestAllOfCriteria.Type?.fromTransport() = when (this) {
ChatSearchRequestAllOfCriteria.Type.PRIVATE -> ChatType.PRIVATE
ChatSearchRequestAllOfCriteria.Type.GROUP -> ChatType.GROUP
ChatSearchRequestAllOfCriteria.Type.CHANNEL -> ChatType.CHANNEL
null -> ChatType.NONE
}
private fun ChatSearchRequestAllOfCriteria.Mode?.fromTransport() = when (this) {
ChatSearchRequestAllOfCriteria.Mode.PERSONAL -> ChatMode.PERSONAL
ChatSearchRequestAllOfCriteria.Mode.WORK -> ChatMode.WORK
null -> ChatMode.NONE
}
private fun Set<String>?.fromTransport(): MutableSet<ChatUserId> = if (this != null) {
this.map { it.toChatUserId() }
.toMutableSet()
.takeIf { it.isNotEmpty() } ?: mutableSetOf(ChatUserId(""))
} else {
mutableSetOf()
}

View File

@ -0,0 +1,105 @@
package ru.otus.messenger.api.v1.mappers
import kotlinx.datetime.Instant
import ru.otus.messenger.api.v1.models.*
import ru.otus.messenger.common.models.*
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.NONE
import ru.otus.messenger.common.exceptions.UnknownChatCommand
fun ChatContext.toTransportChat(): IResponse = when (val cmd = command) {
ChatCommand.CREATE -> toTransportCreate()
ChatCommand.READ -> toTransportRead()
ChatCommand.DELETE -> toTransportDelete()
ChatCommand.SEARCH -> toTransportSearch()
ChatCommand.UPDATE -> toTransportUpdate()
ChatCommand.NONE -> throw UnknownChatCommand(cmd)
}
fun ChatContext.toTransportCreate() = ChatCreateResponse(
result = state.toResult(),
errors = errors.toTransportErrors(),
chat = chatResponse.toTransportChat()
)
fun ChatContext.toTransportRead() = ChatReadResponse(
result = state.toResult(),
errors = errors.toTransportErrors(),
chat = chatResponse.toTransportChat()
)
fun ChatContext.toTransportDelete() = ChatDeleteResponse(
result = state.toResult(),
errors = errors.toTransportErrors(),
)
fun ChatContext.toTransportSearch() = ChatSearchResponse(
result = state.toResult(),
errors = errors.toTransportErrors(),
chats = chatsResponse.toTransportChats()
)
fun ChatContext.toTransportUpdate() = ChatUpdateResponse(
result = state.toResult(),
errors = errors.toTransportErrors(),
chat = chatResponse.toTransportChat()
)
private fun MessengerChat.toTransportChat(): Chat = Chat(
id = id.takeIf { it != ChatId.NONE }?.asString(),
title = title.takeIf { it.isNotBlank() },
description = description.takeIf { it.isNotBlank() },
ownerId = ownerId.takeIf { it != ChatOwnerId.NONE }?.asString(),
type = type.toTransportChat(),
mode = mode.toTransportChat(),
participants = participants.toTransportChat(),
createdAt = createdAt.takeIf { it != Instant.NONE }?.toString(),
updatedAt = updatedAt.takeIf { it != Instant.NONE }?.toString(),
isArchived = isArchived.toTransportChat(),
metadata = metadata.toTransportChat(),
)
private fun List<MessengerChat>.toTransportChats(): List<Chat>? = this
.map { it.toTransportChat() }
.takeIf { it.isNotEmpty() }
private fun ChatType.toTransportChat() = when (this) {
ChatType.PRIVATE -> Chat.Type.PRIVATE
ChatType.GROUP -> Chat.Type.GROUP
ChatType.CHANNEL -> Chat.Type.CHANNEL
ChatType.NONE -> null
}
private fun ChatMode.toTransportChat() = when (this) {
ChatMode.PERSONAL -> Chat.Mode.PERSONAL
ChatMode.WORK -> Chat.Mode.WORK
ChatMode.NONE -> null
}
private fun ChatArchiveFlag.toTransportChat() = this.asBoolean()
private fun ChatMetadata.toTransportChat() = this.asString()
private fun MutableSet<ChatUserId>.toTransportChat(): Set<String>? = this
.map { it.asString() }
.toSet()
.takeIf { it.isNotEmpty() }
private fun List<ChatError>.toTransportErrors(): List<Error>? = this
.map { it.toTransportChat() }
.takeIf { it.isNotEmpty() }
private fun ChatError.toTransportChat() = Error(
code = code.takeIf { it.isNotBlank() },
group = group.takeIf { it.isNotBlank() },
fieldName = field.takeIf { it.isNotBlank() },
message = message.takeIf { it.isNotBlank() },
)
private fun ChatState.toResult(): ResponseResult? = when (this) {
ChatState.RUNNING -> ResponseResult.SUCCESS
ChatState.FAILING -> ResponseResult.ERROR
ChatState.FINISHING -> ResponseResult.SUCCESS
ChatState.NONE -> null
}

View File

@ -0,0 +1,3 @@
package ru.otus.messenger.api.v1.mappers.exceptions
class UnknownRequestClass(clazz: Class<*>) : RuntimeException("Class $clazz cannot be mapped")

View File

@ -0,0 +1,86 @@
import java.util.UUID
import kotlin.test.assertEquals
import org.junit.Test
import ru.otus.messenger.api.v1.mappers.fromTransport
import ru.otus.messenger.api.v1.mappers.toTransportChat
import ru.otus.messenger.api.v1.models.Chat
import ru.otus.messenger.api.v1.models.ChatCreateRequest
import ru.otus.messenger.api.v1.models.ChatCreateRequestAllOfChat
import ru.otus.messenger.api.v1.models.ChatCreateResponse
import ru.otus.messenger.api.v1.models.Debug
import ru.otus.messenger.api.v1.models.DebugMode
import ru.otus.messenger.api.v1.models.DebugStubs
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.models.ChatCommand
import ru.otus.messenger.common.models.ChatError
import ru.otus.messenger.common.models.ChatMode
import ru.otus.messenger.common.models.ChatOwnerId
import ru.otus.messenger.common.models.ChatState
import ru.otus.messenger.common.models.ChatType
import ru.otus.messenger.common.models.MessengerChat
import ru.otus.messenger.common.models.RequestId
import ru.otus.messenger.common.models.WorkMode
import ru.otus.messenger.common.stubs.Stubs
class MapperCreateTest {
@Test
fun fromTransport() {
val req = ChatCreateRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.SUCCESS
),
chat = ChatCreateRequestAllOfChat(
title = "title",
description = "description",
type = ChatCreateRequestAllOfChat.Type.PRIVATE,
mode = ChatCreateRequestAllOfChat.Mode.PERSONAL,
ownerId = UUID.randomUUID().toString(),
)
)
val context = ChatContext()
context.fromTransport(req)
assertEquals(Stubs.SUCCESS, context.stubCase)
assertEquals(WorkMode.STUB, context.workMode)
assertEquals("title", context.chatRequest.title)
assertEquals(ChatType.PRIVATE, context.chatRequest.type)
assertEquals(ChatMode.PERSONAL, context.chatRequest.mode)
}
@Test
fun toTransport() {
val context = ChatContext(
requestId = RequestId(UUID.randomUUID().toString()),
command = ChatCommand.CREATE,
state = ChatState.RUNNING,
chatResponse = MessengerChat(
title = "title",
description = "description",
type = ChatType.PRIVATE,
mode = ChatMode.PERSONAL,
ownerId = ChatOwnerId(UUID.randomUUID().toString()),
),
errors = mutableListOf(
ChatError(
code = "err",
group = "request",
field = "title",
message = "wrong title",
)
)
)
val req = context.toTransportChat() as ChatCreateResponse
assertEquals("title", req.chat?.title)
assertEquals(Chat.Type.PRIVATE, req.chat?.type)
assertEquals(Chat.Mode.PERSONAL, req.chat?.mode)
assertEquals(1, req.errors?.size)
assertEquals("err", req.errors?.firstOrNull()?.code)
assertEquals("request", req.errors?.firstOrNull()?.group)
assertEquals("title", req.errors?.firstOrNull()?.fieldName)
assertEquals("wrong title", req.errors?.firstOrNull()?.message)
}
}

View File

@ -0,0 +1,69 @@
import java.util.UUID
import kotlin.test.assertEquals
import org.junit.Test
import ru.otus.messenger.api.v1.mappers.fromTransport
import ru.otus.messenger.api.v1.mappers.toTransportChat
import ru.otus.messenger.api.v1.models.ChatDeleteRequest
import ru.otus.messenger.api.v1.models.ChatDeleteResponse
import ru.otus.messenger.api.v1.models.Debug
import ru.otus.messenger.api.v1.models.DebugMode
import ru.otus.messenger.api.v1.models.DebugStubs
import ru.otus.messenger.api.v1.models.ResponseResult
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.models.ChatCommand
import ru.otus.messenger.common.models.ChatError
import ru.otus.messenger.common.models.ChatMode
import ru.otus.messenger.common.models.ChatState
import ru.otus.messenger.common.models.ChatType
import ru.otus.messenger.common.models.RequestId
import ru.otus.messenger.common.models.WorkMode
import ru.otus.messenger.common.stubs.Stubs
class MapperDeleteTest {
@Test
fun fromTransport() {
val req = ChatDeleteRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.SUCCESS
),
chatId = "chat-id",
)
val context = ChatContext()
context.fromTransport(req)
assertEquals(Stubs.SUCCESS, context.stubCase)
assertEquals(WorkMode.STUB, context.workMode)
assertEquals("", context.chatRequest.title)
assertEquals("chat-id", context.chatRequest.id.asString())
assertEquals(ChatType.NONE, context.chatRequest.type)
assertEquals(ChatMode.NONE, context.chatRequest.mode)
}
@Test
fun toTransport() {
val context = ChatContext(
requestId = RequestId(UUID.randomUUID().toString()),
command = ChatCommand.DELETE,
state = ChatState.RUNNING,
errors = mutableListOf(
ChatError(
code = "err",
group = "request",
field = "title",
message = "wrong title",
)
)
)
val req = context.toTransportChat() as ChatDeleteResponse
assertEquals(ResponseResult.SUCCESS, req.result)
assertEquals(1, req.errors?.size)
assertEquals("err", req.errors?.firstOrNull()?.code)
assertEquals("request", req.errors?.firstOrNull()?.group)
assertEquals("title", req.errors?.firstOrNull()?.fieldName)
assertEquals("wrong title", req.errors?.firstOrNull()?.message)
}
}

View File

@ -0,0 +1,80 @@
import java.util.UUID
import kotlin.test.assertEquals
import org.junit.Test
import ru.otus.messenger.api.v1.mappers.fromTransport
import ru.otus.messenger.api.v1.mappers.toTransportChat
import ru.otus.messenger.api.v1.models.Chat
import ru.otus.messenger.api.v1.models.ChatReadRequest
import ru.otus.messenger.api.v1.models.ChatReadResponse
import ru.otus.messenger.api.v1.models.Debug
import ru.otus.messenger.api.v1.models.DebugMode
import ru.otus.messenger.api.v1.models.DebugStubs
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.models.ChatCommand
import ru.otus.messenger.common.models.ChatError
import ru.otus.messenger.common.models.ChatMode
import ru.otus.messenger.common.models.ChatOwnerId
import ru.otus.messenger.common.models.ChatState
import ru.otus.messenger.common.models.ChatType
import ru.otus.messenger.common.models.MessengerChat
import ru.otus.messenger.common.models.RequestId
import ru.otus.messenger.common.models.WorkMode
import ru.otus.messenger.common.stubs.Stubs
class MapperReadTest {
@Test
fun fromTransport() {
val req = ChatReadRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.SUCCESS
),
chatId = "chat-id",
)
val context = ChatContext()
context.fromTransport(req)
assertEquals(Stubs.SUCCESS, context.stubCase)
assertEquals(WorkMode.STUB, context.workMode)
assertEquals("", context.chatRequest.title)
assertEquals("chat-id", context.chatRequest.id.asString())
assertEquals(ChatType.NONE, context.chatRequest.type)
assertEquals(ChatMode.NONE, context.chatRequest.mode)
}
@Test
fun toTransport() {
val context = ChatContext(
requestId = RequestId(UUID.randomUUID().toString()),
command = ChatCommand.READ,
state = ChatState.RUNNING,
chatResponse = MessengerChat(
title = "title",
description = "description",
type = ChatType.PRIVATE,
mode = ChatMode.PERSONAL,
ownerId = ChatOwnerId(UUID.randomUUID().toString()),
),
errors = mutableListOf(
ChatError(
code = "err",
group = "request",
field = "title",
message = "wrong title",
)
)
)
val req = context.toTransportChat() as ChatReadResponse
assertEquals("title", req.chat?.title)
assertEquals(Chat.Type.PRIVATE, req.chat?.type)
assertEquals(Chat.Mode.PERSONAL, req.chat?.mode)
assertEquals(1, req.errors?.size)
assertEquals("err", req.errors?.firstOrNull()?.code)
assertEquals("request", req.errors?.firstOrNull()?.group)
assertEquals("title", req.errors?.firstOrNull()?.fieldName)
assertEquals("wrong title", req.errors?.firstOrNull()?.message)
}
}

View File

@ -0,0 +1,97 @@
import java.util.UUID
import kotlin.test.assertEquals
import org.junit.Test
import ru.otus.messenger.api.v1.mappers.fromTransport
import ru.otus.messenger.api.v1.mappers.toTransportChat
import ru.otus.messenger.api.v1.models.Chat
import ru.otus.messenger.api.v1.models.ChatSearchRequest
import ru.otus.messenger.api.v1.models.ChatSearchRequestAllOfCriteria
import ru.otus.messenger.api.v1.models.ChatSearchResponse
import ru.otus.messenger.api.v1.models.Debug
import ru.otus.messenger.api.v1.models.DebugMode
import ru.otus.messenger.api.v1.models.DebugStubs
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.models.ChatCommand
import ru.otus.messenger.common.models.ChatError
import ru.otus.messenger.common.models.ChatMode
import ru.otus.messenger.common.models.ChatOwnerId
import ru.otus.messenger.common.models.ChatState
import ru.otus.messenger.common.models.ChatType
import ru.otus.messenger.common.models.MessengerChat
import ru.otus.messenger.common.models.RequestId
import ru.otus.messenger.common.models.WorkMode
import ru.otus.messenger.common.stubs.Stubs
class MapperSearchTest {
@Test
fun fromTransport() {
val req = ChatSearchRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.SUCCESS
),
criteria = ChatSearchRequestAllOfCriteria(
title = "title",
type = ChatSearchRequestAllOfCriteria.Type.PRIVATE,
mode = ChatSearchRequestAllOfCriteria.Mode.PERSONAL,
participant = UUID.randomUUID().toString(),
)
)
val context = ChatContext()
context.fromTransport(req)
assertEquals(Stubs.SUCCESS, context.stubCase)
assertEquals(WorkMode.STUB, context.workMode)
assertEquals("title", context.chatRequest.title)
assertEquals(ChatType.PRIVATE, context.chatRequest.type)
assertEquals(ChatMode.PERSONAL, context.chatRequest.mode)
}
@Test
fun toTransport() {
val context = ChatContext(
requestId = RequestId(UUID.randomUUID().toString()),
command = ChatCommand.SEARCH,
state = ChatState.RUNNING,
chatsResponse = mutableListOf(
MessengerChat(
title = "title",
description = "description",
type = ChatType.PRIVATE,
mode = ChatMode.PERSONAL,
ownerId = ChatOwnerId(UUID.randomUUID().toString()),
),
MessengerChat(
title = "title-1",
description = "description",
type = ChatType.GROUP,
mode = ChatMode.WORK,
ownerId = ChatOwnerId(UUID.randomUUID().toString()),
)
),
errors = mutableListOf(
ChatError(
code = "err",
group = "request",
field = "title",
message = "wrong title",
)
)
)
val req = context.toTransportChat() as ChatSearchResponse
assertEquals("title", req.chats?.get(0)?.title)
assertEquals("title-1", req.chats?.get(1)?.title)
assertEquals(Chat.Type.PRIVATE, req.chats?.get(0)?.type)
assertEquals(Chat.Type.GROUP, req.chats?.get(1)?.type)
assertEquals(Chat.Mode.PERSONAL, req.chats?.get(0)?.mode)
assertEquals(Chat.Mode.WORK, req.chats?.get(1)?.mode)
assertEquals(1, req.errors?.size)
assertEquals("err", req.errors?.firstOrNull()?.code)
assertEquals("request", req.errors?.firstOrNull()?.group)
assertEquals("title", req.errors?.firstOrNull()?.fieldName)
assertEquals("wrong title", req.errors?.firstOrNull()?.message)
}
}

View File

@ -0,0 +1,92 @@
import java.util.UUID
import kotlin.test.assertEquals
import org.junit.Test
import ru.otus.messenger.api.v1.mappers.fromTransport
import ru.otus.messenger.api.v1.mappers.toTransportChat
import ru.otus.messenger.api.v1.models.Chat
import ru.otus.messenger.api.v1.models.ChatUpdateRequest
import ru.otus.messenger.api.v1.models.ChatUpdateRequestAllOfChat
import ru.otus.messenger.api.v1.models.ChatUpdateResponse
import ru.otus.messenger.api.v1.models.Debug
import ru.otus.messenger.api.v1.models.DebugMode
import ru.otus.messenger.api.v1.models.DebugStubs
import ru.otus.messenger.common.ChatContext
import ru.otus.messenger.common.models.ChatCommand
import ru.otus.messenger.common.models.ChatError
import ru.otus.messenger.common.models.ChatMode
import ru.otus.messenger.common.models.ChatOwnerId
import ru.otus.messenger.common.models.ChatState
import ru.otus.messenger.common.models.ChatType
import ru.otus.messenger.common.models.MessengerChat
import ru.otus.messenger.common.models.RequestId
import ru.otus.messenger.common.models.WorkMode
import ru.otus.messenger.common.stubs.Stubs
class MapperUpdateTest {
@Test
fun fromTransport() {
val req = ChatUpdateRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.SUCCESS
),
chat = ChatUpdateRequestAllOfChat(
chatId = "chat-id",
title = "title",
description = "description",
mode = ChatUpdateRequestAllOfChat.Mode.PERSONAL,
isArchived = false,
metadata = """
{
"testParam": "test"
}
""".trimIndent()
)
)
val context = ChatContext()
context.fromTransport(req)
assertEquals(Stubs.SUCCESS, context.stubCase)
assertEquals(WorkMode.STUB, context.workMode)
assertEquals("chat-id", context.chatRequest.id.asString())
assertEquals("title", context.chatRequest.title)
assertEquals(ChatType.NONE, context.chatRequest.type)
assertEquals(ChatMode.PERSONAL, context.chatRequest.mode)
}
@Test
fun toTransport() {
val context = ChatContext(
requestId = RequestId(UUID.randomUUID().toString()),
command = ChatCommand.UPDATE,
state = ChatState.RUNNING,
chatResponse = MessengerChat(
title = "title",
description = "description",
type = ChatType.PRIVATE,
mode = ChatMode.PERSONAL,
ownerId = ChatOwnerId(UUID.randomUUID().toString()),
),
errors = mutableListOf(
ChatError(
code = "err",
group = "request",
field = "title",
message = "wrong title",
)
)
)
val req = context.toTransportChat() as ChatUpdateResponse
assertEquals("title", req.chat?.title)
assertEquals(Chat.Type.PRIVATE, req.chat?.type)
assertEquals(Chat.Mode.PERSONAL, req.chat?.mode)
assertEquals(1, req.errors?.size)
assertEquals("err", req.errors?.firstOrNull()?.code)
assertEquals("request", req.errors?.firstOrNull()?.group)
assertEquals("title", req.errors?.firstOrNull()?.fieldName)
assertEquals("wrong title", req.errors?.firstOrNull()?.message)
}
}

View File

@ -0,0 +1,56 @@
import kotlin.apply
plugins {
id("build-jvm")
alias(libs.plugins.openapi.generator)
}
sourceSets {
main {
java.srcDir(layout.buildDirectory.dir("generate-resources/main/src/main/kotlin"))
}
}
openApiGenerate {
val openapiGroup = "${rootProject.group}.api.v1"
generatorName.set("kotlin")
packageName.set(openapiGroup)
apiPackage.set("$openapiGroup.api")
modelPackage.set("$openapiGroup.models")
inputSpec.set(rootProject.ext["spec-v1"] as String)
/**
* Use only models
* Doc: https://openapi-generator.tech/docs/globals
*/
globalProperties.apply {
put("models", "")
put("modelDocs", "false")
}
/**
* Additional parameters from
* https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/kotlin.md
*/
configOptions.set(
mapOf(
"dateLibrary" to "string",
"enumPropertyNaming" to "UPPERCASE",
"serializationLibrary" to "jackson",
"collectionType" to "list"
)
)
}
dependencies {
implementation(kotlin("stdlib"))
implementation(libs.kotlin.jackson.module)
implementation(libs.kotlin.jackson.datatype)
testImplementation(kotlin("test-junit"))
}
tasks {
compileKotlin {
dependsOn(openApiGenerate)
}
}

View File

@ -0,0 +1,27 @@
package ru.otus.messenger.api.v1
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import ru.otus.messenger.api.v1.models.IRequest
import ru.otus.messenger.api.v1.models.IResponse
val apiV1Mapper: JsonMapper = JsonMapper.builder().run {
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
enable(MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL)
build()
}
@Suppress("unused")
fun apiV1RequestSerialize(request: IRequest): String = apiV1Mapper.writeValueAsString(request)
@Suppress("UNCHECKED_CAST", "unused")
fun <T : IRequest> apiV1RequestDeserialize(json: String): T =
apiV1Mapper.readValue(json, IRequest::class.java) as T
@Suppress("unused")
fun apiV1ResponseSerialize(response: IResponse): String = apiV1Mapper.writeValueAsString(response)
@Suppress("UNCHECKED_CAST", "unused")
fun <T : IResponse> apiV1ResponseDeserialize(json: String): T =
apiV1Mapper.readValue(json, IResponse::class.java) as T

View File

@ -0,0 +1,56 @@
package ru.otus.messenger.api.v1
import ru.otus.messenger.api.v1.models.*
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
class RequestV1SerializationTest {
private val request = ChatCreateRequest(
debug = Debug(
mode = DebugMode.STUB,
stub = DebugStubs.VALUE_ERROR
),
requestType = "create",
chat = ChatCreateRequestAllOfChat(
title = "New chat",
type = ChatCreateRequestAllOfChat.Type.GROUP,
mode = ChatCreateRequestAllOfChat.Mode.PERSONAL,
participants = emptyList(),
metadata = """
{
"isOwner": true,
"testMeta": "qwerty",
}
""".trimIndent()
)
)
@Test
fun serialize() {
val json = apiV1RequestSerialize(request)
assertContains(json, Regex("\"title\":\\s*\"${request.chat?.title}\""))
assertContains(json, Regex("\"mode\":\\s*\"${request.debug?.mode}\""))
assertContains(json, Regex("\"stub\":\\s*\"${request.debug?.stub}\""))
assertContains(json, Regex("\"requestType\":\\s*\"${request.requestType}\""))
}
@Test
fun deserialize() {
val json = apiV1RequestSerialize(request)
val obj = apiV1RequestDeserialize<ChatCreateRequest>(json)
assertEquals(request, obj)
}
@Test
fun deserializeNaked() {
val jsonString = """
{ "requestType": "create" }
""".trimIndent()
val obj = apiV1RequestDeserialize<ChatCreateRequest>(jsonString)
assertEquals(null, obj.chat)
}
}

View File

@ -0,0 +1,42 @@
package ru.otus.messenger.api.v1
import java.time.Instant
import java.util.UUID
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import ru.otus.messenger.api.v1.models.*
class ResponseV1SerializationTest {
private val response = ChatCreateResponse(
responseType = "create",
result = ResponseResult.SUCCESS,
chat = Chat(
id = UUID.randomUUID().toString(),
title = "Test chat title",
type = Chat.Type.CHANNEL,
mode = Chat.Mode.WORK,
participants = emptyList(),
createdAt = Instant.now().toString(),
isArchived = false,
metadata = null
)
)
@Test
fun serialize() {
val json = apiV1ResponseSerialize(response)
assertContains(json, Regex("\"title\":\\s*\"${response.chat?.title}\""))
assertContains(json, Regex("\"id\":\\s*\"${response.chat?.id}\""))
assertContains(json, Regex("\"responseType\":\\s*\"${response.responseType}\""))
}
@Test
fun deserialize() {
val json = apiV1ResponseSerialize(response)
val obj = apiV1ResponseDeserialize<ChatCreateResponse>(json)
assertEquals(response, obj)
}
}

View File

@ -0,0 +1,3 @@
# Модуль `ok-messenger-common`
Содержит объекты, которые используются всеми остальными модулями проекта.

View File

@ -0,0 +1,20 @@
plugins {
id("build-jvm")
}
group = rootProject.group
version = rootProject.version
sourceSets {
main {
java.srcDir("src/commonMain/kotlin")
}
}
dependencies {
implementation(kotlin("stdlib"))
implementation(libs.kotlin.datetime)
implementation(libs.kotlinx.serialization.json)
testImplementation(kotlin("test-junit"))
}

View File

@ -0,0 +1,22 @@
package ru.otus.messenger.common
import kotlinx.datetime.Instant
import ru.otus.messenger.common.models.*
import ru.otus.messenger.common.stubs.Stubs
data class ChatContext(
var command: ChatCommand = ChatCommand.NONE,
var state: ChatState = ChatState.NONE,
val errors: MutableList<ChatError> = mutableListOf(),
var workMode: WorkMode = WorkMode.PROD,
var stubCase: Stubs = Stubs.NONE,
var requestId: RequestId = RequestId.NONE,
var timeStart: Instant = Instant.NONE,
var chatRequest: MessengerChat = MessengerChat(),
var chatFilterRequest: ChatSearchFilter = ChatSearchFilter(),
var chatResponse: MessengerChat = MessengerChat(),
var chatsResponse: MutableList<MessengerChat> = mutableListOf(),
)

View File

@ -0,0 +1,7 @@
package ru.otus.messenger.common
import kotlinx.datetime.Instant
private val INSTANT_NONE = Instant.fromEpochMilliseconds(Long.MIN_VALUE)
val Instant.Companion.NONE
get() = INSTANT_NONE

View File

@ -0,0 +1,5 @@
package ru.otus.messenger.common.exceptions
import ru.otus.messenger.common.models.ChatCommand
class UnknownChatCommand(command: ChatCommand) : Throwable("Wrong command $command at mapping toTransport stage")

View File

@ -0,0 +1,14 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
@JvmInline
value class ChatArchiveFlag(private val flag: Boolean) {
fun asString() = flag.toString()
fun asBoolean() = flag
companion object {
val NONE = ChatArchiveFlag(false)
}
}

View File

@ -0,0 +1,10 @@
package ru.otus.messenger.common.models
enum class ChatCommand {
NONE,
CREATE,
READ,
DELETE,
SEARCH,
UPDATE,
}

View File

@ -0,0 +1,9 @@
package ru.otus.messenger.common.models
data class ChatError(
val code: String = "",
val group: String = "",
val field: String = "",
val message: String = "",
val exception: Throwable? = null,
)

View File

@ -0,0 +1,12 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
@JvmInline
value class ChatId(private val id: String) {
fun asString() = id
companion object {
val NONE = ChatId("")
}
}

View File

@ -0,0 +1,14 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
@JvmInline
value class ChatMetadata(private val metadata: JsonObject) {
fun asString() = metadata.toString()
companion object {
val NONE = ChatMetadata(buildJsonObject {})
}
}

View File

@ -0,0 +1,7 @@
package ru.otus.messenger.common.models
enum class ChatMode {
NONE,
PERSONAL,
WORK,
}

View File

@ -0,0 +1,12 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
@JvmInline
value class ChatOwnerId(private val id: String) {
fun asString() = id
companion object {
val NONE = ChatOwnerId("")
}
}

View File

@ -0,0 +1,7 @@
package ru.otus.messenger.common.models
data class ChatSearchFilter(
var searchString: String = "",
var ownerId: ChatOwnerId = ChatOwnerId.NONE,
var type: ChatType = ChatType.NONE,
)

View File

@ -0,0 +1,8 @@
package ru.otus.messenger.common.models
enum class ChatState {
NONE,
RUNNING,
FAILING,
FINISHING,
}

View File

@ -0,0 +1,8 @@
package ru.otus.messenger.common.models
enum class ChatType {
NONE,
PRIVATE,
CHANNEL,
GROUP,
}

View File

@ -0,0 +1,12 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
@JvmInline
value class ChatUserId(private val id: String) {
fun asString() = id
companion object {
val NONE = ChatUserId("")
}
}

View File

@ -0,0 +1,24 @@
package ru.otus.messenger.common.models
import kotlinx.datetime.Instant
import ru.otus.messenger.common.NONE
data class MessengerChat(
var id: ChatId = ChatId.NONE,
var title: String = "",
var description: String = "",
var type: ChatType = ChatType.NONE,
var mode: ChatMode = ChatMode.NONE,
var ownerId: ChatOwnerId = ChatOwnerId.NONE,
val participants: MutableSet<ChatUserId> = mutableSetOf(),
var createdAt: Instant = Instant.NONE,
var updatedAt: Instant = Instant.NONE,
var isArchived: ChatArchiveFlag = ChatArchiveFlag.NONE,
var metadata: ChatMetadata = ChatMetadata.NONE,
) {
fun isEmpty() = this == NONE
companion object {
private val NONE = MessengerChat()
}
}

View File

@ -0,0 +1,12 @@
package ru.otus.messenger.common.models
import kotlin.jvm.JvmInline
@JvmInline
value class RequestId(private val id: String) {
fun asString() = id
companion object {
val NONE = RequestId("")
}
}

View File

@ -0,0 +1,7 @@
package ru.otus.messenger.common.models
enum class WorkMode {
PROD,
TEST,
STUB,
}

View File

@ -0,0 +1,11 @@
package ru.otus.messenger.common.stubs
enum class Stubs {
NONE,
SUCCESS,
NOT_FOUND,
CANNOT_DELETE,
MISSING_DATA,
VALUE_ERROR,
DB_ERROR,
}

View File

@ -1,3 +0,0 @@
plugins {
id("build-jvm")
}

View File

@ -1,3 +0,0 @@
fun main() {
print("Hello world")
}

View File

@ -23,4 +23,6 @@ rootProject.name = "ok-messenger-be"
//enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
include(":ok-messenger-tmp")
include(":ok-messenger-api-v1")
include(":ok-messenger-api-v1-mappers")
include(":ok-messenger-common")