Skip to content

Commit

Permalink
code corrections and add filter in handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Данила Беляков authored and Данила Беляков committed Nov 19, 2024
1 parent 4ce16a2 commit 9b51a85
Show file tree
Hide file tree
Showing 63 changed files with 769 additions and 312 deletions.
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .kotlin/errors/errors-1731969919007.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

19 changes: 4 additions & 15 deletions alice-ktx/src/main/kotlin/com/github/alice/ktx/Dispatch.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,24 @@
package com.github.alice.ktx

import com.github.alice.ktx.api.dialog.DialogApi
import com.github.alice.ktx.common.AliceDsl
import com.github.alice.ktx.handlers.Handler
import com.github.alice.ktx.handlers.error.NetworkErrorHandler
import com.github.alice.ktx.middleware.Middleware
import com.github.alice.ktx.middleware.MiddlewareType
import com.github.alice.ktx.models.FSMStrategy
import com.github.alice.ktx.models.request.MessageRequest
import com.github.alice.ktx.context.FSMContext

/**
* Расширение для `Skill.Builder`, позволяющее настроить `Dispatcher`.
*
* @param body Функция, принимающая объект `Dispatcher` и позволяющая настроить его.
* Эта функция будет вызвана в контексте `Dispatcher`.
*/
@AliceDsl
fun Skill.Builder.dispatch(body: Dispatcher.() -> Unit) {
dispatcherConfiguration = body
}

/**
* Класс `Dispatcher` управляет обработчиками команд, обработчиками сетевых ошибок и мидлварами.
*
* @param fsmStrategy Стратегия конечного автомата состояний (FSM), используемая для управления состояниями.
*/
class Dispatcher internal constructor(
internal val fsmStrategy: FSMStrategy,
val dialogApi: DialogApi? = null,
internal val fsmContext: (message: MessageRequest) -> FSMContext,
internal val enableApiStorage: Boolean = false
) {
@AliceDsl
class Dispatcher internal constructor() {
internal val commandHandlers = linkedSetOf<Handler>()
internal val networkErrorHandlers = linkedSetOf<NetworkErrorHandler>()
internal val middlewares = mutableMapOf<MiddlewareType, LinkedHashSet<Middleware>>()
Expand Down
94 changes: 59 additions & 35 deletions alice-ktx/src/main/kotlin/com/github/alice/ktx/Skill.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.github.alice.ktx

import com.github.alice.ktx.api.dialog.DialogApi
import com.github.alice.ktx.common.AliceDsl
import com.github.alice.ktx.middleware.MiddlewareType
import com.github.alice.ktx.models.FSMStrategy
import com.github.alice.ktx.models.Request
import com.github.alice.ktx.models.request
import com.github.alice.ktx.models.request.MessageRequest
import com.github.alice.ktx.models.response.MessageResponse
import com.github.alice.ktx.models.toEventRequest
import com.github.alice.ktx.server.WebServer
import com.github.alice.ktx.server.WebServerResponseListener
import com.github.alice.ktx.context.FSMContext
import com.github.alice.ktx.context.impl.BaseFSMContext
import com.github.alice.ktx.server.WebServerListener
import com.github.alice.ktx.fsm.FSMContext
import com.github.alice.ktx.fsm.MutableFSMContext
import com.github.alice.ktx.fsm.impl.BaseFSMContext
import com.github.alice.ktx.handlers.environments.ProcessRequestEnvironment
import com.github.alice.ktx.storage.Storage
import com.github.alice.ktx.storage.apiStorage.EnableApiStorage
import com.github.alice.ktx.storage.impl.memoryStorage
Expand All @@ -24,7 +24,8 @@ import kotlinx.serialization.json.Json
* Эта функция будет вызвана в контексте `Skill.Builder`.
* @return Настроенный объект `Skill`.
*/
fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().build(body)
@AliceDsl
fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().apply(body).build()

/**
* Класс `Skill` представляет собой навык, который обрабатывает запросы и управляет состоянием.
Expand All @@ -34,38 +35,46 @@ fun skill(body: Skill.Builder.() -> Unit): Skill = Skill.Builder().build(body)
*/
class Skill internal constructor(
private val webServer: WebServer,
private val dispatcher: Dispatcher
private val dispatcher: Dispatcher,
private val dialogApi: DialogApi?,
private val defaultFSMStrategy: FSMStrategy,
private val fsmContext: (message: MessageRequest) -> FSMContext,
private val enableApiStorage: Boolean = false
) {

/**
* Конструктор `Builder` для создания экземпляра `Skill`.
*/
@AliceDsl
class Builder {

var id: String? = null
var json: Json = Json { ignoreUnknownKeys = true }
var dialogApi: DialogApi? = null
lateinit var webServer: WebServer
var defaultFSMStrategy: FSMStrategy = FSMStrategy.USER
internal var dispatcherConfiguration: Dispatcher.() -> Unit = { }

var skillId: String? = null
var json: Json = Json {
isLenient = true
ignoreUnknownKeys = true
encodeDefaults = true
}

var dialogApi: DialogApi? = null
var storage: Storage = memoryStorage()

var defaultFSMStrategy: FSMStrategy = FSMStrategy.USER
var fsmContext: (message: MessageRequest) -> FSMContext = { message ->
BaseFSMContext(storage, defaultFSMStrategy, message, id)
BaseFSMContext(storage, defaultFSMStrategy, message, skillId)
}

internal fun build(body: Builder.() -> Unit): Skill {
body()
internal var dispatcherConfiguration: Dispatcher.() -> Unit = { }

fun build(): Skill {
return Skill(
webServer = webServer,
dispatcher = Dispatcher(
fsmStrategy = defaultFSMStrategy,
dialogApi = dialogApi,
fsmContext = fsmContext,
enableApiStorage = storage.javaClass.isAnnotationPresent(EnableApiStorage::class.java)
).apply(dispatcherConfiguration)
dialogApi = dialogApi,
defaultFSMStrategy = defaultFSMStrategy,
fsmContext = fsmContext,
enableApiStorage = storage.javaClass.isAnnotationPresent(EnableApiStorage::class.java),
dispatcher = Dispatcher().apply(dispatcherConfiguration)
)
}
}
Expand All @@ -83,25 +92,24 @@ class Skill internal constructor(
*
* @return Реализованный объект `WebServerResponseListener`, который обрабатывает входящие сообщения и ошибки.
*/
private fun webServerResponseCallback(): WebServerResponseListener = object : WebServerResponseListener {
override suspend fun messageHandle(model: MessageRequest): MessageResponse? {
val request = dispatcher.request(model)
val eventRequest = request.toEventRequest()
private fun webServerResponseCallback(): WebServerListener = object : WebServerListener {
override suspend fun handleRequest(model: MessageRequest): MessageResponse? {
val requestEnvironment = createRequestEnvironment(model)

runMiddlewares(request, MiddlewareType.OUTER)?.let { return it }
runMiddlewares(requestEnvironment, MiddlewareType.OUTER)?.let { return it }
dispatcher.commandHandlers.forEach { handler ->
if (handler.event(eventRequest)) {
runMiddlewares(request, MiddlewareType.INNER)?.let { return it }
return handler.handle(request)
if (handler.shouldHandle(requestEnvironment)) {
runMiddlewares(requestEnvironment, MiddlewareType.INNER)?.let { return it }
return handler.processRequest(requestEnvironment)
}
}
return null
}

override suspend fun responseFailure(model: MessageRequest, ex: Exception): MessageResponse? {
val request = dispatcher.request(model)
override suspend fun handleError(model: MessageRequest, exception: Exception): MessageResponse? {
val request = createRequestEnvironment(model)
dispatcher.networkErrorHandlers.forEach { errorHandler ->
errorHandler.responseFailure(request, ex)?.let { response ->
errorHandler.responseFailure(request, exception)?.let { response ->
return response
}
}
Expand All @@ -114,13 +122,29 @@ class Skill internal constructor(
* @param type Тип мидлвари, который следует выполнить.
* @return `MessageResponse?` — ответ от мидлвари, или `null`, если обработка не завершена.
*/
private suspend fun runMiddlewares(request: Request, type: MiddlewareType): MessageResponse? {
private suspend fun runMiddlewares(
request: ProcessRequestEnvironment,
type: MiddlewareType
): MessageResponse? {
dispatcher.middlewares[type]?.forEach { middleware ->
middleware.invoke(request)?.let { response ->
middleware.process(request)?.let { response ->
return response
}
}
return null
}
}

private suspend fun createRequestEnvironment(message: MessageRequest): ProcessRequestEnvironment {
val context = fsmContext(message)
context.init()

return object : ProcessRequestEnvironment {
override val message: MessageRequest = message
override val context: MutableFSMContext = context
override val dialogApi: DialogApi? = this@Skill.dialogApi
override val fsmStrategy: FSMStrategy = this@Skill.defaultFSMStrategy
override val enableApiStorage: Boolean = this@Skill.enableApiStorage
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sealed interface Response<T> {
* @param T Тип данных, которые были бы возвращены в успешном ответе.
* @param message Сообщение об ошибке, объясняющее причину неудачи.
*/
data class Failed<T>(val message: String): Response<T>
data class Failed<T>(val message: String, val code: Int): Response<T>

/**
* Представляет успешный ответ.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ suspend inline fun <reified T> HttpResponse.response(): Response<T> {
return if(status.value in 200..299)
Response.Success(this.body())
else
Response.Failed(this.body<ErrorBody>().message)
Response.Failed(this.body<ErrorBody>().message, status.value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.github.alice.ktx.api.dialog.yandex.models.image.response.Images
import com.github.alice.ktx.api.dialog.yandex.models.sounds.response.SoundUpload
import com.github.alice.ktx.api.dialog.yandex.models.sounds.response.Sounds
import com.github.alice.ktx.api.dialog.yandex.models.status.Status
import com.github.alice.ktx.common.AliceDsl
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
Expand All @@ -24,10 +25,11 @@ import io.ktor.util.*
import kotlinx.serialization.json.Json
import java.io.File

@AliceDsl
fun Skill.Builder.ktorYandexDialogApi(body: KtorYandexDialogApi.Builder.() -> Unit): KtorYandexDialogApi {
val id = id
val id = skillId
?: throw IllegalArgumentException("Skill ID не может быть null. Убедитесь, что ID установлен перед вызовом метода.")
return KtorYandexDialogApi.Builder().setJson(json).apply(body).build(id)
return KtorYandexDialogApi.Builder().json(json).apply(body).build(id)
}

/**
Expand All @@ -41,14 +43,15 @@ class KtorYandexDialogApi(
private val configuration: HttpClientConfig<CIOEngineConfig>.() -> Unit
): DialogApi {

@AliceDsl
class Builder {

lateinit var oauthToken: String
lateinit var json: Json

private lateinit var json: Json
var configuration: HttpClientConfig<CIOEngineConfig>.() -> Unit = {}

internal fun setJson(json: Json): Builder {
internal fun json(json: Json): Builder {
this.json = json
return this
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.alice.ktx.api.dialog.yandex.models.image

import com.github.alice.ktx.common.LocalDateTimeSerializer
import com.github.alice.ktx.common.serializers.LocalDateTimeSerializer
import kotlinx.serialization.Serializable
import java.time.LocalDateTime

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.alice.ktx.api.dialog.yandex.models.sounds

import com.github.alice.ktx.common.LocalDateTimeSerializer
import com.github.alice.ktx.common.serializers.LocalDateTimeSerializer
import kotlinx.serialization.Serializable
import java.time.LocalDateTime

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.alice.ktx.common

@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
annotation class AliceDsl

@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
annotation class AliceResponseDsl
57 changes: 57 additions & 0 deletions alice-ktx/src/main/kotlin/com/github/alice/ktx/common/Redis.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.github.alice.ktx.common

import io.lettuce.core.RedisClient
import io.lettuce.core.api.StatefulRedisConnection

/**
* Формирует строку URI для подключения к Redis на основе заданных параметров.
*
* @param host Хост Redis-сервера. По умолчанию "localhost".
* @param port Порт Redis-сервера. По умолчанию 6379.
* @param username Необязательное имя пользователя для аутентификации. Если null, имя пользователя не указывается.
* @param password Необязательный пароль для аутентификации. Если null, пароль не указывается.
* @return Строка URI для подключения к Redis.
*
* Примеры:
* - Если указаны только `host` и `port`: "redis://localhost:6379"
* - Если указан `password` без `username`: "redis://:password@localhost:6379"
* - Если указаны и `username`, и `password`: "redis://username:password@localhost:6379"
*/
fun redisUri(
host: String = "localhost",
port: Int = 6379,
username: String? = null,
password: String? = null
): String {
return if (password != null) {
if (username != null) {
"redis://$username:$password@$host:$port"
} else {
"redis://:$password@$host:$port"
}
} else {
"redis://$host:$port"
}
}

/**
* Устанавливает соединение с Redis-сервером, используя параметры подключения.
*
* Этот метод создает подключение к Redis с использованием клиента, полученного
* из URI, сформированного на основе переданных параметров.
*
* @param host Хост Redis-сервера. По умолчанию "localhost".
* @param port Порт Redis-сервера. По умолчанию 6379.
* @param username Необязательное имя пользователя для аутентификации. Если null, имя пользователя не указывается.
* @param password Необязательный пароль для аутентификации. Если null, пароль не указывается.
* @return Объект StatefulRedisConnection, представляющий подключение к Redis.
*/
fun connectToRedis(
host: String = "localhost",
port: Int = 6379,
username: String? = null,
password: String? = null
): StatefulRedisConnection<String, String> {
val redisClient = RedisClient.create(redisUri(host, port, username, password))
return redisClient.connect()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.alice.ktx.common
package com.github.alice.ktx.common.serializers

import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.alice.ktx.context
package com.github.alice.ktx.fsm

/**
* Интерфейс для доступа к информации состояния конкретного пользователя.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.alice.ktx.context
package com.github.alice.ktx.fsm

import com.github.alice.ktx.models.FSMStrategy
import kotlin.reflect.KClass
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.alice.ktx.context
package com.github.alice.ktx.fsm

import com.github.alice.ktx.models.FSMStrategy
import kotlin.reflect.KClass
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.alice.ktx.context.impl
package com.github.alice.ktx.fsm.impl

import com.github.alice.ktx.models.FSMStrategy
import com.github.alice.ktx.models.request.MessageRequest
import com.github.alice.ktx.context.FSMContext
import com.github.alice.ktx.fsm.FSMContext
import com.github.alice.ktx.storage.Storage
import com.github.alice.ktx.storage.apiStorage.ApiStorageDetails
import com.github.alice.ktx.storage.models.StorageKey
Expand Down
Loading

0 comments on commit 9b51a85

Please sign in to comment.