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

@ -1,11 +1,13 @@
[versions]
kotlin = "2.1.0"
coroutines = "1.9.0"
datetime = "0.6.1"
kotlinx-coroutines = "1.9.0"
kotlinx-datetime = "0.6.1"
kotlinx-serialization = "1.6.3"
okhhtp = "4.12.0"
jackson-module = "2.18.2"
slf4j = "2.0.9"
logback-classic = "1.4.11"
openapi-generator = "7.3.0"
# BASE
jvm-compiler = "17"
@ -15,13 +17,18 @@ jvm-language = "21"
jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
java-gradle-plugin = { id = "java-gradle-plugin" }
openapi-generator = { id = "org.openapi.generator", version.ref = "openapi-generator" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
kotlin-jackson-module = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module" }
kotlin-jackson-datatype = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson-module" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhhtp" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }

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")

403
specs/specs-v1.yaml Normal file
View File

@ -0,0 +1,403 @@
openapi: 3.0.4
info:
title: "Messenger ${VERSION_APP}"
version: 1.0.0
description: Программное обеспечение для обмена информацией между пользователями.
servers:
- url: http://localhost:8080/api/v1
paths:
/chat/create:
post:
tags:
- Chat
summary: Создать новый чат
operationId: createChat
requestBody:
description: Запрос на создание чата
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatCreateRequest'
responses:
'200':
description: Чат успешно создан
content:
application/json:
schema:
$ref: '#/components/schemas/ChatCreateResponse'
/chat/read:
post:
tags:
- Chat
summary: Получить чат по ID
operationId: readChat
requestBody:
description: Запрос на получение чата
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatReadRequest'
responses:
'200':
description: Информация о чате
content:
application/json:
schema:
$ref: '#/components/schemas/ChatReadResponse'
/chat/search:
post:
tags:
- Chat
summary: Поиск чатов
operationId: searchChats
requestBody:
description: Запрос на поиск чатов по заданным критериям
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatSearchRequest'
responses:
'200':
description: Список найденных чатов
content:
application/json:
schema:
$ref: '#/components/schemas/ChatSearchResponse'
/chat/update:
post:
tags:
- Chat
summary: Обновить чат
operationId: updateChat
requestBody:
description: Запрос на обновление информации о чате
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatUpdateRequest'
responses:
'200':
description: Чат успешно обновлён
content:
application/json:
schema:
$ref: '#/components/schemas/ChatUpdateResponse'
/chat/delete:
post:
tags:
- Chat
summary: Архивировать (удалить) чат
operationId: deleteChat
requestBody:
description: Запрос на архивирование чата
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatDeleteRequest'
responses:
'200':
description: Чат успешно архивирован
content:
application/json:
schema:
$ref: '#/components/schemas/ChatDeleteResponse'
components:
schemas:
Error:
type: object
properties:
code:
type: string
group:
type: string
fieldName:
type: string
message:
type: string
ResponseResult:
type: string
enum:
- success
- error
# Интерфейс реквеста
IRequest:
type: object
description: Базовый интерфейс для всех запросов
properties:
requestType:
type: string
description: Поле-дескриминатор для вычисления типа запроса
example: create
discriminator:
propertyName: requestType
mapping:
create: '#/components/schemas/ChatCreateRequest'
read: '#/components/schemas/ChatReadRequest'
search: '#/components/schemas/ChatSearchRequest'
update: '#/components/schemas/ChatUpdateRequest'
delete: '#/components/schemas/ChatDeleteRequest'
# Интерфейс респонса
IResponse:
type: object
description: Базовый интерфейс для всех ответов
properties:
responseType:
type: string
description: Поле-дескриминатор для вычисления типа запроса
example: create
result:
$ref: '#/components/schemas/ResponseResult'
errors:
type: array
items:
$ref: '#/components/schemas/Error'
discriminator:
propertyName: responseType
mapping:
create: '#/components/schemas/ChatCreateResponse'
read: '#/components/schemas/ChatReadResponse'
search: '#/components/schemas/ChatSearchResponse'
update: '#/components/schemas/ChatUpdateResponse'
delete: '#/components/schemas/ChatDeleteResponse'
# Основная схема объекта Chat
Chat:
type: object
properties:
id:
type: string
description: Уникальный идентификатор чата (UUID)
title:
type: string
description: Название чата
description:
type: string
description: Описание чата
type:
type: string
enum: [private, group, channel]
description: Тип чата
mode:
type: string
enum: [work, personal]
description: Режим чата
owner_id:
type: string
description: Уникальный идентификатор владельца (UUID)
participants:
type: array
uniqueItems: true
items:
type: string
description: Список участников (user IDs)
created_at:
type: string
format: date-time
description: Дата создания чата
updated_at:
type: string
format: date-time
description: Дата обновления чата
is_archived:
type: boolean
description: Флаг архивации чата
metadata:
type: string
format: json
description: Дополнительные данные (например, project_id, jira_link и пр.)
# --- Создание чата ---
ChatCreateRequest:
allOf:
- $ref: '#/components/schemas/IRequest'
- $ref: '#/components/schemas/RequestDebug'
- type: object
properties:
chat:
type: object
properties:
title:
type: string
description: Название чата
description:
type: string
description: Описание чата
type:
type: string
enum: [private, group, channel]
description: Тип чата
mode:
type: string
enum: [work, personal]
description: Режим чата
owner_id:
type: string
description: Уникальный идентификатор владельца (UUID)
participants:
type: array
uniqueItems: true
items:
type: string
description: Список идентификаторов участников
metadata:
type: string
format: json
description: Дополнительные данные (необязательно)
ChatCreateResponse:
allOf:
- $ref: '#/components/schemas/IResponse'
- type: object
properties:
chat:
$ref: '#/components/schemas/Chat'
# --- Чтение чата ---
ChatReadRequest:
allOf:
- $ref: '#/components/schemas/IRequest'
- $ref: '#/components/schemas/RequestDebug'
- type: object
properties:
chatId:
type: string
description: Идентификатор чата для чтения
example: string
ChatReadResponse:
allOf:
- $ref: '#/components/schemas/IResponse'
- type: object
properties:
chat:
$ref: '#/components/schemas/Chat'
# --- Поиск чатов ---
ChatSearchRequest:
allOf:
- $ref: '#/components/schemas/IRequest'
- $ref: '#/components/schemas/RequestDebug'
- type: object
properties:
criteria:
type: object
description: Критерии поиска чатов
properties:
title:
type: string
description: Поиск по названию
type:
type: string
enum: [private, group, channel]
description: Поиск по типу чата
mode:
type: string
enum: [work, personal]
description: Поиск по режиму чата
participant:
type: string
description: Поиск по участнику (ID пользователя)
ChatSearchResponse:
allOf:
- $ref: '#/components/schemas/IResponse'
- type: object
properties:
chats:
type: array
items:
$ref: '#/components/schemas/Chat'
# --- Обновление чата ---
ChatUpdateRequest:
allOf:
- $ref: '#/components/schemas/IRequest'
- $ref: '#/components/schemas/RequestDebug'
- type: object
properties:
chat:
type: object
properties:
chatId:
type: string
description: Идентификатор чата для обновления
title:
type: string
description: Новое название чата
description:
type: string
description: Новое описание чата
mode:
type: string
enum: [work, personal]
description: Новый режим чата
is_archived:
type: boolean
description: Флаг архивации (если требуется)
metadata:
type: string
format: json
description: Дополнительные данные
ChatUpdateResponse:
allOf:
- $ref: '#/components/schemas/IResponse'
- type: object
properties:
chat:
$ref: '#/components/schemas/Chat'
# --- Архивирование (удаление) чата ---
ChatDeleteRequest:
allOf:
- $ref: '#/components/schemas/IRequest'
- $ref: '#/components/schemas/RequestDebug'
- type: object
properties:
chatId:
type: string
description: Идентификатор чата для архивирования
ChatDeleteResponse:
allOf:
- $ref: '#/components/schemas/IResponse'
# STUBS ======================
DebugMode:
type: string
enum:
- prod
- test
- stub
RequestDebug:
type: object
properties:
debug:
$ref: '#/components/schemas/Debug'
Debug:
type: object
properties:
mode:
$ref: '#/components/schemas/DebugMode'
stub:
$ref: '#/components/schemas/DebugStubs'
DebugStubs:
type: string
description: Перечисления всех стабов
enum:
- success
- notFound
- cannotDelete
- valueError
- missingData