From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp54.i.mail.ru (smtp54.i.mail.ru [217.69.128.34]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id C72244696C6 for ; Fri, 10 Apr 2020 11:10:49 +0300 (MSK) From: Leonid Vasiliev Date: Fri, 10 Apr 2020 11:10:40 +0300 Message-Id: <2bb678a8aed7e1533ca2c626048080c4c1c5520b.1586505741.git.lvasiliev@tarantool.org> In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: v.shpilevoy@tarantool.org Cc: tarantool-patches@dev.tarantool.org A possibility to create an error with a custom subtype was added. In accordance with https://github.com/tarantool/tarantool/issues/4398 a custom error type has been added to the box.error. Now, it's possible to create an error with a custom subtype (string value) for use it in applications. @TarantoolBot document Title: error.custom_type A custom error type has been added to the box.error. Now, it's possible to create an error with a custom subtype (string value) for use it in applications. To API has been added: - For create CustomError: box.error.new(type(str), format, ...) box.error.new({type = str, reason = str}) - error.type - CustomType error return custom type other errors return "base" error tybe - base_type - return "base" errror type Example: err_custom = box.error.new("My Custom Type", "Reason") Now: err_custom.type == "My Custom Type" err_custom.base_type == "CustomType" err_custom.message == "Reason" Needed for #4398 --- src/box/errcode.h | 1 + src/box/error.cc | 59 +++++++++++++++++ src/box/error.h | 38 ++++++++++- src/box/lua/error.cc | 147 ++++++++++++++++++++++++++++++------------ src/lua/error.lua | 11 ++++ test/app/fiber.result | 5 +- test/box/error.result | 10 +-- test/engine/func_index.result | 18 +++--- 8 files changed, 234 insertions(+), 55 deletions(-) diff --git a/src/box/errcode.h b/src/box/errcode.h index 4441717..9f5f993 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -265,6 +265,7 @@ struct errcode_record { /*210 */_(ER_SQL_PREPARE, "Failed to prepare SQL statement: %s") \ /*211 */_(ER_WRONG_QUERY_ID, "Prepared statement with id %u does not exist") \ /*212 */_(ER_SEQUENCE_NOT_STARTED, "Sequence '%s' is not started") \ + /*213 */_(ER_CUSTOM_ERROR, "%s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/box/error.cc b/src/box/error.cc index 233b312..8179e52 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code, /* }}} */ +const char * +box_custom_error_type(const box_error_t *e) +{ + CustomError *custom_error = type_cast(CustomError, e); + if (custom_error) + return custom_error->custom_type(); + + return NULL; +} + +struct error * +box_custom_error_new(const char *file, unsigned line, + const char *custom, const char *fmt, ...) +{ + struct error *e = BuildCustomError(file, line, custom); + CustomError *custom_error = type_cast(CustomError, e); + if (custom_error != NULL) { + va_list ap; + va_start(ap, fmt); + error_vformat_msg(e, fmt, ap); + va_end(ap); + } + return e; +} + struct rmean *rmean_error = NULL; const char *rmean_error_strings[RMEAN_ERROR_LAST] = { @@ -290,3 +315,37 @@ BuildAccessDeniedError(const char *file, unsigned int line, return e; } } + +static struct method_info customerror_methods[] = { + make_method(&type_CustomError, "custom_type", &CustomError::custom_type), + METHODS_SENTINEL +}; + +const struct type_info type_CustomError = + make_type("CustomError", &type_ClientError, + customerror_methods); + +CustomError::CustomError(const char *file, unsigned int line, + const char *custom_type) + :ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR) +{ + error_format_msg(this, tnt_errcode_desc(m_errcode), + custom_type ?: ""); + + if (custom_type) { + strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1); + m_custom_type[sizeof(m_custom_type) - 1] = '\0'; + } else { + m_custom_type[0] = '\0'; + } +} + +struct error * +BuildCustomError(const char *file, unsigned int line, const char *custom_type) +{ + try { + return new CustomError(file, line, custom_type); + } catch (OutOfMemory *e) { + return e; + } +} diff --git a/src/box/error.h b/src/box/error.h index ca5d5b2..5013488 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -53,6 +53,9 @@ struct error * BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); +struct error * +BuildCustomError(const char *file, unsigned int line, const char *custom_type); + /** \cond public */ struct error; @@ -138,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code, /** \endcond public */ /** + * Return the error custom type, + * \param error + * \return pointer to custom error type. On error, return NULL + */ +const char * +box_custom_error_type(const box_error_t *e); + +/** * Add error to the diagnostic area. In contrast to box_error_set() * it does not replace previous error being set, but rather link * them into list. @@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code, /** * Construct error object without setting it in the diagnostics - * area. On the memory allocation fail returns NULL. + * area. On the memory allocation fail returns OutOfMemory error. */ struct error * box_error_new(const char *file, unsigned line, uint32_t code, const char *fmt, ...); +/** + * Construct error object without setting it in the diagnostics + * area. On the memory allocation fail returns OutOfMemory error. + */ +struct error * +box_custom_error_new(const char *file, unsigned line, + const char *custom, const char *fmt, ...); + extern const struct type_info type_ClientError; extern const struct type_info type_XlogError; extern const struct type_info type_AccessDeniedError; +extern const struct type_info type_CustomError; #if defined(__cplusplus) } /* extern "C" */ @@ -290,6 +310,22 @@ struct XlogGapError: public XlogError virtual void raise() { throw this; } }; +class CustomError: public ClientError +{ +public: + CustomError(const char *file, unsigned int line, + const char *custom_type); + + const char* + custom_type() + { + return m_custom_type; + } +private: + /** Custom type name */ + char m_custom_type[64]; +}; + #endif /* defined(__cplusplus) */ #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */ diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index 4432bc8..1bcce1f 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -42,64 +42,101 @@ extern "C" { #include "lua/utils.h" #include "box/error.h" -/** - * Parse Lua arguments (they can come as single table - * f({code : number, reason : string}) or as separate members - * f(code, reason)) and construct struct error with given values. - * In case one of arguments is missing its corresponding field - * in struct error is filled with default value. - */ -static struct error * -luaT_error_create(lua_State *L, int top_base) +struct error_args { + uint32_t code; + const char *reason; + const char *custom; + bool tb_mode; + bool tb_parsed; +}; + +static int +luaT_error_parse_args(struct lua_State *L, int top_base, + struct error_args *args) { - uint32_t code = 0; - const char *reason = NULL; - bool tb_parsed = false; - bool tb_mode = false; - const char *file = ""; - unsigned line = 0; - lua_Debug info; int top = lua_gettop(L); - if (top >= top_base && lua_type(L, top_base) == LUA_TNUMBER) { - code = lua_tonumber(L, top_base); - reason = tnt_errcode_desc(code); + int top_type = lua_type(L, top_base); + if (top >= top_base && + (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) { + if (top_type == LUA_TNUMBER) { + args->code = lua_tonumber(L, top_base); + } else if (top_type == LUA_TSTRING) { + args->code = ER_CUSTOM_ERROR; + args->custom = lua_tostring(L, top_base); + } else { + return -1; + } + args->reason = tnt_errcode_desc(args->code); if (top > top_base) { /* Call string.format(reason, ...) to format message */ lua_getglobal(L, "string"); if (lua_isnil(L, -1)) - goto raise; + return 0; lua_getfield(L, -1, "format"); if (lua_isnil(L, -1)) - goto raise; - lua_pushstring(L, reason); + return 0; + lua_pushstring(L, args->reason); for (int i = top_base + 1; i <= top; i++) lua_pushvalue(L, i); lua_call(L, top - top_base + 1, 1); - reason = lua_tostring(L, -1); - } else if (strchr(reason, '%') != NULL) { + args->reason = lua_tostring(L, -1); + } else if (strchr(args->reason, '%') != NULL) { /* Missing arguments to format string */ - return NULL; + return -1; } - } else if (top == top_base && lua_istable(L, top_base)) { + } else if (top == top_base && top_type == LUA_TTABLE) { lua_getfield(L, top_base, "code"); - code = lua_tonumber(L, -1); + if (lua_isnil(L, -1) == 0) + args->code = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, top_base, "reason"); - reason = lua_tostring(L, -1); - if (reason == NULL) - reason = ""; + if (lua_isnil(L, -1) == 0) + args->reason = lua_tostring(L, -1); + lua_pop(L, 1); + lua_getfield(L, top_base, "type"); + if (lua_isnil(L, -1) == 0) + args->custom = lua_tostring(L, -1); lua_pop(L, 1); lua_getfield(L, top_base, "traceback"); if (lua_isboolean(L, -1)) { - tb_parsed = true; - tb_mode = lua_toboolean(L, -1); + args->tb_parsed = true; + args->tb_mode = lua_toboolean(L, -1); } lua_pop(L, -1); } else { + return -1; + } + + return 0; +} + +/** + * Parse Lua arguments (they can come as single table + * f({code : number, reason : string}) or as separate members + * f(code, reason)) and construct struct error with given values. + * In case one of arguments is missing its corresponding field + * in struct error is filled with default value. + */ +static struct error * +luaT_error_create(lua_State *L, int top_base) +{ + struct error_args args; + args.code = UINT32_MAX; + args.reason = NULL; + args.custom = NULL; + args.tb_mode = false; + args.tb_parsed = false; + + if (luaT_error_parse_args(L, top_base, &args) != 0) { return NULL; } -raise: + if (args.reason == NULL) + args.reason = ""; + + const char *file = ""; + unsigned line = 0; + lua_Debug info; if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) { if (*info.short_src) { file = info.short_src; @@ -111,9 +148,16 @@ raise: line = info.currentline; } - struct error *err = box_error_new(file, line, code, "%s", reason); - if (tb_parsed) - err->traceback_mode = tb_mode; + struct error *err = NULL; + if (args.custom) { + err = box_custom_error_new(file, line, args.custom, + "%s", args.reason); + } else { + err = box_error_new(file, line, args.code, "%s", args.reason); + } + + if (args.tb_parsed) + err->traceback_mode = args.tb_mode; return err; } @@ -162,17 +206,36 @@ luaT_error_last(lua_State *L) static int luaT_error_new(lua_State *L) { - if (lua_gettop(L) == 0) - return luaL_error(L, "Usage: box.error.new(code, args)"); + if (lua_gettop(L) == 0) { + return luaL_error(L, "Usage: box.error.new(code, args) or "\ + "box.error.new(type, args)"); + } struct error *e = luaT_error_create(L, 1); - if (e == NULL) - return luaL_error(L, "Usage: box.error.new(code, args)"); + if (e == NULL) { + return luaL_error(L, "Usage: box.error.new(code, args) or "\ + "box.error.new(type, args)"); + } lua_settop(L, 0); luaT_pusherror(L, e); return 1; } static int +luaT_error_custom_type(lua_State *L) +{ + struct error *e = luaL_checkerror(L, -1); + + const char *custom_type = box_custom_error_type(e); + if (custom_type == NULL) { + lua_pushfstring(L, "The error has't custom type"); + return 1; + } + + lua_pushstring(L, custom_type); + return 1; +} + +static int luaT_error_clear(lua_State *L) { if (lua_gettop(L) >= 1) @@ -328,6 +391,10 @@ box_lua_error_init(struct lua_State *L) { lua_pushcfunction(L, luaT_error_cfg); lua_setfield(L, -2, "cfg"); } + { + lua_pushcfunction(L, luaT_error_custom_type); + lua_setfield(L, -2, "custom_type"); + } lua_setfield(L, -2, "__index"); } lua_setmetatable(L, -2); diff --git a/src/lua/error.lua b/src/lua/error.lua index 46d2866..5b32607 100644 --- a/src/lua/error.lua +++ b/src/lua/error.lua @@ -78,6 +78,16 @@ local function reflection_get(err, method) end local function error_type(err) + local res + if ffi.string(err._type.name) == 'CustomError' then + res = box.error.custom_type(err) + else + res = ffi.string(err._type.name) + end + return res +end + +local function error_base_type(err) return ffi.string(err._type.name) end @@ -141,6 +151,7 @@ local error_fields = { ["errno"] = error_errno; ["prev"] = error_prev; ["traceback"] = error_traceback; + ["base_type"] = error_base_type } local function error_unpack(err) diff --git a/test/app/fiber.result b/test/app/fiber.result index fc6b3c9..00d915c 100644 --- a/test/app/fiber.result +++ b/test/app/fiber.result @@ -1040,11 +1040,12 @@ e:unpack(); --- - traceback: Traceback is absent code: 1 + base_type: ClientError + type: ClientError + message: Illegal parameters, oh my trace: - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]' line: 1 - type: ClientError - message: Illegal parameters, oh my ... flag = false; --- diff --git a/test/box/error.result b/test/box/error.result index 22788a1..611044b 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -36,11 +36,12 @@ e:unpack() | --- | - traceback: Traceback is absent | code: 1 + | base_type: ClientError + | type: ClientError + | message: Illegal parameters, bla bla | trace: | - file: '[C]' | line: 4294967295 - | type: ClientError - | message: Illegal parameters, bla bla | ... e.type | --- @@ -106,7 +107,7 @@ e | ... box.error.new() | --- - | - error: 'Usage: box.error.new(code, args)' + | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)' | ... -- @@ -431,6 +432,7 @@ t; | 210: box.error.SQL_PREPARE | 211: box.error.WRONG_QUERY_ID | 212: box.error.SEQUENCE_NOT_STARTED + | 213: box.error.CUSTOM_ERROR | ... test_run:cmd("setopt delimiter ''"); @@ -489,7 +491,7 @@ assert(box.error.last() == nil) -- box.error.new(err) | --- - | - error: 'Usage: box.error.new(code, args)' + | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)' | ... -- box.error() is supposed to re-throw last diagnostic error. diff --git a/test/engine/func_index.result b/test/engine/func_index.result index 28befca..aee3ca4 100644 --- a/test/engine/func_index.result +++ b/test/engine/func_index.result @@ -278,14 +278,15 @@ e:unpack() | --- | - traceback: Traceback is absent | code: 198 - | trace: - | - file: - | line: + | base_type: ClientError | type: ClientError - | message: 'Failed to build a key for functional index ''idx'' of space ''withdata'': - | can''t evaluate function' | prev: '[string "return function(tuple) local ..."]:1: attempt to | call global ''require'' (a nil value)' + | message: 'Failed to build a key for functional index ''idx'' of space ''withdata'': + | can''t evaluate function' + | trace: + | - file: + | line: | ... e = e.prev | --- @@ -293,12 +294,13 @@ e = e.prev e:unpack() | --- | - traceback: Traceback is absent - | trace: - | - file: - | line: + | base_type: LuajitError | type: LuajitError | message: '[string "return function(tuple) local ..."]:1: attempt | to call global ''require'' (a nil value)' + | trace: + | - file: + | line: | ... e = e.prev | --- -- 2.7.4