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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user