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_mp.BossEncoder
|
||||
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.mp_logger.LogTag
|
||||
import net.sergeych.mp_logger.debug
|
||||
import net.sergeych.mp_logger.exception
|
||||
import net.sergeych.mp_logger.warning
|
||||
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
|
||||
@ -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 commandHost the Api __this adapter provides to a remote__. It differs from the interface expected on the
|
||||
* 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
|
||||
*/
|
||||
open class Adapter<T>(
|
||||
private val instance: T,
|
||||
private val commandHost: CommandHost<T>,
|
||||
private val exceptionRegistry: ExceptionsRegistry = ExceptionsRegistry(),
|
||||
private val sendEncoded: suspend (data: ByteArray) -> Unit,
|
||||
) : LogTag("ADPTR") {
|
||||
|
||||
@ -113,15 +117,13 @@ open class Adapter<T>(
|
||||
val handler = commandHost.handler(pe.name)
|
||||
val result = handler.invoke(instance, pe.args)
|
||||
sendPackage(
|
||||
Package.Response(
|
||||
pe.id, result
|
||||
Package.Response(pe.id, result)
|
||||
)
|
||||
)
|
||||
} catch (ae: ApiException) {
|
||||
} catch (ae: ParsecException) {
|
||||
sendPackage(Package.Response(pe.id, null, ae.code, ae.text))
|
||||
} catch (ex: Throwable) {
|
||||
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)
|
||||
else
|
||||
dr.completeExceptionally(
|
||||
pe.errorCode?.let { ApiException(it, pe.errorText) }
|
||||
?: ApiException(BAD_RESPONSE_PACKAGE)
|
||||
pe.errorCode?.let { exceptionRegistry.getException(it, pe.errorText) }
|
||||
?: 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_mp.BossEncoder
|
||||
import net.sergeych.cloudoc.api.ApiError
|
||||
import net.sergeych.parsec3.AdapterDelegate
|
||||
import net.sergeych.parsec3.CommandNotFoundException
|
||||
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
|
||||
|
@ -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(
|
||||
val toId: Int,
|
||||
val result: ByteArray? = null,
|
||||
val errorCode: ApiError? = null,
|
||||
val errorCode: String? = null,
|
||||
val errorText: String? = null,
|
||||
) : Package() {
|
||||
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