added extendable serialized exeptions handling registry
This commit is contained in:
parent
db7a85fb7b
commit
32280ffc61
@ -7,15 +7,15 @@ import kotlinx.serialization.json.Json
|
|||||||
import net.sergeych.boss_serialization.BossDecoder
|
import net.sergeych.boss_serialization.BossDecoder
|
||||||
import net.sergeych.boss_serialization_mp.BossEncoder
|
import net.sergeych.boss_serialization_mp.BossEncoder
|
||||||
import net.sergeych.boss_serialization_mp.decodeBoss
|
import net.sergeych.boss_serialization_mp.decodeBoss
|
||||||
import net.sergeych.cloudoc.api.ApiError.BAD_RESPONSE_PACKAGE
|
|
||||||
import net.sergeych.cloudoc.api.ApiError.UNKNOWN_ERROR
|
|
||||||
import net.sergeych.cloudoc.api.ApiException
|
|
||||||
import net.sergeych.cloudoc.api.Package
|
import net.sergeych.cloudoc.api.Package
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.debug
|
import net.sergeych.mp_logger.debug
|
||||||
import net.sergeych.mp_logger.exception
|
import net.sergeych.mp_logger.exception
|
||||||
import net.sergeych.mp_logger.warning
|
import net.sergeych.mp_logger.warning
|
||||||
import net.sergeych.mptools.toDump
|
import net.sergeych.mptools.toDump
|
||||||
|
import net.sergeych.parsec3.ExceptionsRegistry
|
||||||
|
import net.sergeych.parsec3.InvalidFrameException
|
||||||
|
import net.sergeych.parsec3.ParsecException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create adapter, an interface to provide local API commands and invoke remote API commands
|
* Create adapter, an interface to provide local API commands and invoke remote API commands
|
||||||
@ -66,11 +66,15 @@ import net.sergeych.mptools.toDump
|
|||||||
* @param instance any instance that represent the state of the interface. Could be `Unit` for stateless.
|
* @param instance any instance that represent the state of the interface. Could be `Unit` for stateless.
|
||||||
* @param commandHost the Api __this adapter provides to a remote__. It differs from the interface expected on the
|
* @param commandHost the Api __this adapter provides to a remote__. It differs from the interface expected on the
|
||||||
* remote side.
|
* remote side.
|
||||||
|
* @param exceptionRegistry allows to transform serialized parsec exception (see [ParsecException]) to
|
||||||
|
* application-specific exception classes. Default implementation only decodes
|
||||||
|
* parsec3 built-in exceptions.
|
||||||
* @param sendEncoded a method that performs actual sending of the packed binary frame to the remote side
|
* @param sendEncoded a method that performs actual sending of the packed binary frame to the remote side
|
||||||
*/
|
*/
|
||||||
open class Adapter<T>(
|
open class Adapter<T>(
|
||||||
private val instance: T,
|
private val instance: T,
|
||||||
private val commandHost: CommandHost<T>,
|
private val commandHost: CommandHost<T>,
|
||||||
|
private val exceptionRegistry: ExceptionsRegistry = ExceptionsRegistry(),
|
||||||
private val sendEncoded: suspend (data: ByteArray) -> Unit,
|
private val sendEncoded: suspend (data: ByteArray) -> Unit,
|
||||||
) : LogTag("ADPTR") {
|
) : LogTag("ADPTR") {
|
||||||
|
|
||||||
@ -113,15 +117,13 @@ open class Adapter<T>(
|
|||||||
val handler = commandHost.handler(pe.name)
|
val handler = commandHost.handler(pe.name)
|
||||||
val result = handler.invoke(instance, pe.args)
|
val result = handler.invoke(instance, pe.args)
|
||||||
sendPackage(
|
sendPackage(
|
||||||
Package.Response(
|
Package.Response(pe.id, result)
|
||||||
pe.id, result
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} catch (ae: ApiException) {
|
} catch (ae: ParsecException) {
|
||||||
sendPackage(Package.Response(pe.id, null, ae.code, ae.text))
|
sendPackage(Package.Response(pe.id, null, ae.code, ae.text))
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
sendPackage(Package.Response(pe.id, null, UNKNOWN_ERROR, ex.toString()))
|
sendPackage(Package.Response(pe.id, null, "UNKNOWN_ERROR", ex.toString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,8 +136,8 @@ open class Adapter<T>(
|
|||||||
dr.complete(pe.result)
|
dr.complete(pe.result)
|
||||||
else
|
else
|
||||||
dr.completeExceptionally(
|
dr.completeExceptionally(
|
||||||
pe.errorCode?.let { ApiException(it, pe.errorText) }
|
pe.errorCode?.let { exceptionRegistry.getException(it, pe.errorText) }
|
||||||
?: ApiException(BAD_RESPONSE_PACKAGE)
|
?: InvalidFrameException("invalid package: no result, no error code")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ package channel
|
|||||||
|
|
||||||
import net.sergeych.boss_serialization.BossDecoder
|
import net.sergeych.boss_serialization.BossDecoder
|
||||||
import net.sergeych.boss_serialization_mp.BossEncoder
|
import net.sergeych.boss_serialization_mp.BossEncoder
|
||||||
import net.sergeych.cloudoc.api.ApiError
|
|
||||||
import net.sergeych.parsec3.AdapterDelegate
|
import net.sergeych.parsec3.AdapterDelegate
|
||||||
|
import net.sergeych.parsec3.CommandNotFoundException
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +46,7 @@ open class CommandHost<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handler(name: String) = handlers.get(name) ?: ApiError.NOT_FOUND.raise("command not found")
|
fun handler(name: String) = handlers.get(name) ?: throw CommandNotFoundException(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a command delegate that creates type-safe command descriptor containint command name and
|
* Provide a command delegate that creates type-safe command descriptor containint command name and
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package net.sergeych.parsec3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry to restore exceptions from parsec block data. Serializing exceptions is dangerous: being a OS-bound
|
||||||
|
* objects, exceptions can carry too much sensitive or useless information (e.g. call stack), and serializng
|
||||||
|
* actual exceptions could be a pain, so parsec3 serializes exception information as 2 parameters: a code string
|
||||||
|
* which is very much like old good (and awful in a way) `C ERRNO`, and an optional message.
|
||||||
|
*
|
||||||
|
* This class reconstructs exceptions from these parameters using a registry, that is pre-filled with application
|
||||||
|
* codes and actual exception classes. Then [Adapter<T>] uses it to restore and throw actual exception on the calling
|
||||||
|
* party side.
|
||||||
|
*
|
||||||
|
* The good idea is to share one object inheriting from refistry to hold all exceptions info in one place
|
||||||
|
* and share it betweem client and server code.
|
||||||
|
*/
|
||||||
|
open class ExceptionsRegistry {
|
||||||
|
|
||||||
|
private val handlers = mutableMapOf<String,(String?)->Throwable>().also {
|
||||||
|
// predefined exceptions:
|
||||||
|
it[commandNotFoundCode] = { CommandNotFoundException(it ?: "???") }
|
||||||
|
it[unknownErrorCode] = { UnknownException(it ?: "???") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an exception with a code with a handler that creates its instance. Note that the
|
||||||
|
* handler _should not throw anything_ but rather create an instance of the exception.
|
||||||
|
*/
|
||||||
|
fun <T: Throwable>register(code: String, block: (message: String?) -> T) {
|
||||||
|
handlers[code] = block
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* raise the exception using the proper handler. Throws [UnknownCodeException] of there is no handler
|
||||||
|
* for a given code.
|
||||||
|
*/
|
||||||
|
internal fun raise(code: String,message: String?): Nothing {
|
||||||
|
throw getException(code, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the exception instanceusing the proper handler, or an [UnknownCodeException] if handler not found
|
||||||
|
*/
|
||||||
|
internal fun getException(code: String,message: String?): Throwable =
|
||||||
|
handlers[code]?.let { it(message) } ?: UnknownCodeException(code, message)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val commandNotFoundCode = "_COMMAND_NOT_FOUND"
|
||||||
|
val unknownErrorCode = "_UNKNOWN_ERROR"
|
||||||
|
val invalidFrameCode = "_FRAME_INVALID"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,7 +12,7 @@ sealed class Package {
|
|||||||
data class Response(
|
data class Response(
|
||||||
val toId: Int,
|
val toId: Int,
|
||||||
val result: ByteArray? = null,
|
val result: ByteArray? = null,
|
||||||
val errorCode: ApiError? = null,
|
val errorCode: String? = null,
|
||||||
val errorText: String? = null,
|
val errorText: String? = null,
|
||||||
) : Package() {
|
) : Package() {
|
||||||
init {
|
init {
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.sergeych.parsec3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for exceptions that pass through parsec3 channel. Application exceptinos
|
||||||
|
* intended to be transmitted through this channel should inherit from it and be registered
|
||||||
|
* with proper [ExceptionsRegistry] instance provided to the exceptions-receiving adaper (better
|
||||||
|
* to both sides the same).
|
||||||
|
*/
|
||||||
|
open class ParsecException(val code: String, val text: String?=null, reason: Throwable?=null) : Exception(
|
||||||
|
text?.let { "($code): $text" } ?: code, reason
|
||||||
|
)
|
||||||
|
|
||||||
|
class CommandNotFoundException(name: String): ParsecException(ExceptionsRegistry.commandNotFoundCode,name)
|
||||||
|
|
||||||
|
class InvalidFrameException(text: String): ParsecException(ExceptionsRegistry.invalidFrameCode, text)
|
||||||
|
|
||||||
|
class UnknownCodeException(code: String,message: String?): ParsecException(code, message)
|
||||||
|
|
||||||
|
class UnknownException(message: String?): ParsecException(ExceptionsRegistry.unknownErrorCode, message)
|
@ -1,27 +0,0 @@
|
|||||||
package net.sergeych.cloudoc.api
|
|
||||||
|
|
||||||
enum class ApiError {
|
|
||||||
UNKNOWN_ERROR,
|
|
||||||
INTERNAL_ERROR,
|
|
||||||
BAD_RESPONSE_PACKAGE,
|
|
||||||
NOT_FOUND,
|
|
||||||
BAD_LOGIN,
|
|
||||||
OBJECT_ALREADY_EXISTS,
|
|
||||||
ACCESS_FORBIDDEN,
|
|
||||||
ILLEGAL_STATE,
|
|
||||||
NOT_LOGGED_IN,
|
|
||||||
BAD_PARAMETER,
|
|
||||||
EMAIL_IN_USE,
|
|
||||||
NAME_IN_USE;
|
|
||||||
|
|
||||||
fun raise(text: String? = null): Nothing {
|
|
||||||
throw ApiException(this, text ?: defaultText())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun defaultText(): String = name.lowercase().replace('_', ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiException(val code: ApiError, _text: String? = null, cause: Throwable? = null):
|
|
||||||
Exception(_text ?: code.defaultText(), cause) {
|
|
||||||
val text by lazy { _text ?: code.defaultText() }
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user