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 18CA64696C3 for ; Fri, 10 Apr 2020 11:10:53 +0300 (MSK) From: Leonid Vasiliev Date: Fri, 10 Apr 2020 11:10:43 +0300 Message-Id: <767fd7dee482d61cda4e2c7c9e938628bbd7f84d.1586505741.git.lvasiliev@tarantool.org> In-Reply-To: References: In-Reply-To: References: Subject: [Tarantool-patches] [PATCH v2 5/5] iproto: Update error MsgPack encoding 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 New MsgPack encode format for error (MP_ERROR) has been added. If the extended error format is enabled using iproto session settings MP_ERROR type will be used for transferring error through network, MP_STR was used before. Needed for #4398 --- src/box/CMakeLists.txt | 1 + src/box/error.cc | 28 ++- src/box/error.h | 11 +- src/box/lua/call.c | 29 ++- src/box/lua/execute.c | 2 +- src/box/lua/init.c | 56 +++++ src/box/lua/mp_error.cc | 454 +++++++++++++++++++++++++++++++++++ src/box/lua/mp_error.h | 49 ++++ src/box/lua/net_box.lua | 2 +- src/box/lua/tuple.c | 28 +-- src/box/sql/func.c | 4 +- src/lib/core/mp_extension_types.h | 5 +- src/lua/error.c | 2 - src/lua/error.h | 3 +- src/lua/msgpack.c | 28 ++- src/lua/msgpack.h | 8 +- src/lua/utils.c | 16 +- src/lua/utils.h | 6 +- test/box-tap/extended_error.test.lua | 164 +++++++++++++ 19 files changed, 821 insertions(+), 75 deletions(-) create mode 100644 src/box/lua/mp_error.cc create mode 100644 src/box/lua/mp_error.h create mode 100755 test/box-tap/extended_error.test.lua diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 56758bd..32415b5 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -151,6 +151,7 @@ add_library(box STATIC lua/stat.c lua/ctl.c lua/error.cc + lua/mp_error.cc lua/session.c lua/net_box.c lua/xlog.c diff --git a/src/box/error.cc b/src/box/error.cc index 8179e52..f2e60c1 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -253,6 +253,13 @@ XlogGapError::XlogGapError(const char *file, unsigned line, (long long) vclock_sum(to), s_to ? s_to : ""); } +XlogGapError::XlogGapError(const char *file, unsigned line, + const char *msg) + : XlogError(&type_XlogGapError, file, line) +{ + error_format_msg(this, "%s", msg); +} + struct error * BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to) @@ -264,6 +271,16 @@ BuildXlogGapError(const char *file, unsigned line, } } +struct error * +ReBuildXlogGapError(const char *file, unsigned line, const char *msg) +{ + try { + return new XlogGapError(file, line, msg); + } catch (OutOfMemory *e) { + return e; + } +} + struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); static struct method_info accessdeniederror_methods[] = { @@ -289,15 +306,8 @@ AccessDeniedError::AccessDeniedError(const char *file, unsigned int line, struct on_access_denied_ctx ctx = {access_type, object_type, object_name}; trigger_run(&on_access_denied, (void *) &ctx); - /* - * We want to use ctx parameters as error parameters - * later, so we have to alloc space for it. - * As m_access_type and m_object_type are constant - * literals they are statically allocated. We must copy - * only m_object_name. - */ - m_object_type = object_type; - m_access_type = access_type; + m_object_type = strdup(object_type); + m_access_type = strdup(access_type); m_object_name = strdup(object_name); } diff --git a/src/box/error.h b/src/box/error.h index 5013488..d4d064b 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -54,6 +54,9 @@ BuildXlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); struct error * +ReBuildXlogGapError(const char *file, unsigned line, const char *msg); + +struct error * BuildCustomError(const char *file, unsigned int line, const char *custom_type); /** \cond public */ @@ -250,6 +253,8 @@ public: ~AccessDeniedError() { free(m_object_name); + free(m_object_type); + free(m_access_type); } const char * @@ -272,11 +277,11 @@ public: private: /** Type of object the required access was denied to */ - const char *m_object_type; + char *m_object_type; /** Name of object the required access was denied to */ char *m_object_name; /** Type of declined access */ - const char *m_access_type; + char *m_access_type; }; /** @@ -306,6 +311,8 @@ struct XlogGapError: public XlogError { XlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to); + XlogGapError(const char *file, unsigned line, + const char *msg); virtual void raise() { throw this; } }; diff --git a/src/box/lua/call.c b/src/box/lua/call.c index 5d3579e..6d1d247 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -46,6 +46,7 @@ #include "small/obuf.h" #include "trivia/util.h" #include "mpstream.h" +#include "box/session.h" /** * A helper to find a Lua function by name and put it @@ -174,7 +175,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ for (int i = 1; i <= nrets; ++i) { struct luaL_field field; - if (luaL_tofield(L, cfg, i, &field) < 0) + if (luaL_tofield(L, cfg, NULL, i, &field) < 0) return luaT_error(L); struct tuple *tuple; if (field.type == MP_EXT && @@ -188,11 +189,11 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ lua_pushvalue(L, i); mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else { /* `return ..., array, ...` */ - luamp_encode(L, cfg, stream, i); + luamp_encode(L, cfg, NULL, stream, i); } } return nrets; @@ -203,7 +204,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Inspect the first result */ struct luaL_field root; - if (luaL_tofield(L, cfg, 1, &root) < 0) + if (luaL_tofield(L, cfg, NULL, 1, &root) < 0) return luaT_error(L); struct tuple *tuple; if (root.type == MP_EXT && (tuple = luaT_istuple(L, 1)) != NULL) { @@ -217,7 +218,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, */ mpstream_encode_array(stream, 1); assert(lua_gettop(L) == 1); - luamp_encode_r(L, cfg, stream, &root, 0); + luamp_encode_r(L, cfg, NULL, stream, &root, 0); return 1; } @@ -233,7 +234,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, for (uint32_t t = 1; t <= root.size; t++) { lua_rawgeti(L, 1, t); struct luaL_field field; - if (luaL_tofield(L, cfg, -1, &field) < 0) + if (luaL_tofield(L, cfg, NULL, -1, &field) < 0) return luaT_error(L); if (field.type == MP_EXT && (tuple = luaT_istuple(L, -1))) { tuple_to_mpstream(tuple, stream); @@ -249,13 +250,13 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * Encode the first field of tuple using * existing information from luaL_tofield */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); assert(lua_gettop(L) == 1); /* Encode remaining fields as usual */ for (uint32_t f = 2; f <= root.size; f++) { lua_rawgeti(L, 1, f); - luamp_encode(L, cfg, stream, -1); + luamp_encode(L, cfg, NULL, stream, -1); lua_pop(L, 1); } return 1; @@ -265,10 +266,10 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg, * { tuple/array, ..., { scalar }, ... }` */ mpstream_encode_array(stream, 1); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } else { /* `return { tuple/array, ..., tuple/array, ... }` */ - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); } lua_pop(L, 1); assert(lua_gettop(L) == 1); @@ -379,6 +380,7 @@ execute_lua_eval(lua_State *L) struct encode_lua_ctx { struct port_lua *port; struct mpstream *stream; + struct luaL_serializer_ctx *serializer_ctx; }; static int @@ -393,8 +395,10 @@ encode_lua_call(lua_State *L) */ struct luaL_serializer *cfg = luaL_msgpack_default; int size = lua_gettop(ctx->port->L); - for (int i = 1; i <= size; ++i) - luamp_encode(ctx->port->L, cfg, ctx->stream, i); + for (int i = 1; i <= size; ++i) { + luamp_encode(ctx->port->L, cfg, ctx->serializer_ctx, + ctx->stream, i); + } ctx->port->size = size; mpstream_flush(ctx->stream); return 0; @@ -429,6 +433,7 @@ port_lua_do_dump(struct port *base, struct mpstream *stream, struct encode_lua_ctx ctx; ctx.port = port; ctx.stream = stream; + ctx.serializer_ctx = ¤t_session()->serializer_ctx; struct lua_State *L = tarantool_L; int top = lua_gettop(L); if (lua_cpcall(L, handler, &ctx) != 0) { diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c index b4843b2..b5db3c9 100644 --- a/src/box/lua/execute.c +++ b/src/box/lua/execute.c @@ -328,7 +328,7 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) bind->name = NULL; bind->name_len = 0; } - if (luaL_tofield(L, luaL_msgpack_default, -1, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, NULL, -1, &field) < 0) return -1; switch (field.type) { case MP_UINT: diff --git a/src/box/lua/init.c b/src/box/lua/init.c index 63e8b82..b57df4c 100644 --- a/src/box/lua/init.c +++ b/src/box/lua/init.c @@ -36,11 +36,13 @@ #include "lua/utils.h" /* luaT_error() */ #include "lua/trigger.h" +#include "lua/msgpack.h" #include "box/box.h" #include "box/txn.h" #include "box/func.h" #include "box/vclock.h" +#include "box/session.h" #include "box/lua/error.h" #include "box/lua/tuple.h" @@ -62,6 +64,9 @@ #include "box/lua/execute.h" #include "box/lua/key_def.h" #include "box/lua/merger.h" +#include "box/lua/mp_error.h" + +#include "mpstream.h" static uint32_t CTID_STRUCT_TXN_SAVEPOINT_PTR = 0; @@ -386,6 +391,54 @@ static const struct luaL_Reg boxlib_backup[] = { {NULL, NULL} }; +/** + * A MsgPack extensions handler that supports tuples and errors encode. + */ +static enum mp_type +luamp_encode_extension_box(struct lua_State *L, int idx, + struct mpstream *stream) +{ + struct tuple *tuple = luaT_istuple(L, idx); + if (tuple != NULL) { + tuple_to_mpstream(tuple, stream); + return MP_ARRAY; + } + + if (current_session()->serializer_ctx.err_format_ver == ERR_FORMAT_EX) { + struct error *err = luaL_iserror(L, idx); + if (err != NULL) + error_to_mpstream(err, stream); + } + + return MP_EXT; +} + +/** + * A MsgPack extensions handler that supports errors decode. + */ +static void +luamp_decode_extension_box(struct lua_State *L, const char **data) +{ + assert(mp_typeof(**data) == MP_EXT); + int8_t ext_type; + uint32_t len = mp_decode_extl(data, &ext_type); + + if(ext_type != MP_ERROR) { + luaL_error(L, "Unsuported MsgPack extension type: %u", + ext_type); + return; + } + + struct error *err = error_unpack(data, len); + if (err == NULL) { + luaL_error(L, "Can't parse an error from MsgPack"); + return; + } + + luaT_pusherror(L, err); + return; +} + #include "say.h" void @@ -426,6 +479,9 @@ box_lua_init(struct lua_State *L) luaopen_merger(L); lua_pop(L, 1); + luamp_set_encode_extension(luamp_encode_extension_box); + luamp_set_decode_extension(luamp_decode_extension_box); + /* Load Lua extension */ for (const char **s = lua_sources; *s; s += 2) { const char *modname = *s; diff --git a/src/box/lua/mp_error.cc b/src/box/lua/mp_error.cc new file mode 100644 index 0000000..f99da0f --- /dev/null +++ b/src/box/lua/mp_error.cc @@ -0,0 +1,454 @@ +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "box/lua/mp_error.h" +#include "box/error.h" +#include "mpstream.h" +#include "msgpuck.h" +#include "mp_extension_types.h" + +enum mp_error_details { + MP_ERROR_DET_TYPE, + MP_ERROR_DET_FILE, + MP_ERROR_DET_LINE, + MP_ERROR_DET_REASON, + MP_ERROR_DET_ERRNO, + MP_ERROR_DET_CODE, + MP_ERROR_DET_BACKTRACE, + MP_ERROR_DET_CUSTOM_TYPE, + MP_ERROR_DET_AD_OBJ_TYPE, + MP_ERROR_DET_AD_OBJ_NAME, + MP_ERROR_DET_AD_ACCESS_TYPE +}; + +enum mp_error_types { + MP_ERROR_TYPE_UNKNOWN, + MP_ERROR_TYPE_CLIENT, + MP_ERROR_TYPE_CUSTOM, + MP_ERROR_TYPE_ACCESS_DENIED, + MP_ERROR_TYPE_XLOG, + MP_ERROR_TYPE_XLOG_GAP, + MP_ERROR_TyPE_SYSTEM, + MP_ERROR_TyPE_SOCKET, + MP_ERROR_TyPE_OOM, + MP_ERROR_TyPE_TIMED_OUT, + MP_ERROR_TyPE_CHANNEL_IS_CLOSED, + MP_ERROR_TyPE_FIBER_IS_CANCELLED, + MP_ERROR_TyPE_LUAJIT, + MP_ERROR_TyPE_ILLEGAL_PARAMS, + MP_ERROR_TyPE_COLLATION, + MP_ERROR_TyPE_SWIM, + MP_ERROR_TyPE_CRYPTO +}; + +struct mp_error { + uint32_t error_code; + uint8_t error_type; + uint32_t line; + uint32_t saved_errno; + char *file; + char *backtrace; + char *reason; + char *custom_type; + char *ad_obj_type; + char *ad_obj_name; + char *ad_access_type; +}; + +static void +mp_error_init(struct mp_error *mp_error) +{ + mp_error->error_type = MP_ERROR_TYPE_UNKNOWN; + mp_error->file = NULL; + mp_error->backtrace = NULL; + mp_error->reason = NULL; + mp_error->custom_type = NULL; + mp_error->ad_obj_type = NULL; + mp_error->ad_obj_name = NULL; + mp_error->ad_access_type = NULL; +} + +static void +mp_error_cleanup(struct mp_error *mp_error) +{ + mp_error->error_type = MP_ERROR_TYPE_UNKNOWN; + free(mp_error->file); + free(mp_error->backtrace); + free(mp_error->reason); + free(mp_error->custom_type); + free(mp_error->ad_obj_type); + free(mp_error->ad_obj_name); + free(mp_error->ad_access_type); +} + +static uint8_t +mp_error_type_from_str(const char *type_str) +{ + if (type_str == NULL) { + return MP_ERROR_TYPE_UNKNOWN; + } else if (strcmp(type_str, "ClientError") == 0) { + return MP_ERROR_TYPE_CLIENT; + } else if (strcmp(type_str, "CustomError") == 0) { + return MP_ERROR_TYPE_CUSTOM; + } else if (strcmp(type_str, "AccessDeniedError") == 0) { + return MP_ERROR_TYPE_ACCESS_DENIED; + } else if (strcmp(type_str, "XlogError") == 0) { + return MP_ERROR_TYPE_XLOG; + } else if (strcmp(type_str, "XlogGapError") == 0) { + return MP_ERROR_TYPE_XLOG_GAP; + } else if (strcmp(type_str, "SystemError") == 0) { + return MP_ERROR_TyPE_SYSTEM; + } else if (strcmp(type_str, "SocketError") == 0) { + return MP_ERROR_TyPE_SOCKET; + } else if (strcmp(type_str, "OutOfMemory") == 0) { + return MP_ERROR_TyPE_OOM; + } else if (strcmp(type_str, "TimedOut") == 0) { + return MP_ERROR_TyPE_TIMED_OUT; + } else if (strcmp(type_str, "ChannelIsClosed") == 0) { + return MP_ERROR_TyPE_CHANNEL_IS_CLOSED; + } else if (strcmp(type_str, "FiberIsCancelled") == 0) { + return MP_ERROR_TyPE_FIBER_IS_CANCELLED; + } else if (strcmp(type_str, "LuajitError") == 0) { + return MP_ERROR_TyPE_LUAJIT; + } else if (strcmp(type_str, "IllegalParams") == 0) { + return MP_ERROR_TyPE_ILLEGAL_PARAMS; + } else if (strcmp(type_str, "CollationError") == 0) { + return MP_ERROR_TyPE_COLLATION; + } else if (strcmp(type_str, "SwimError") == 0) { + return MP_ERROR_TyPE_SWIM; + } else if (strcmp(type_str, "CryptoError") == 0) { + return MP_ERROR_TyPE_CRYPTO; + } + + return MP_ERROR_TYPE_UNKNOWN; +} + +void +error_to_mpstream(struct error *error, struct mpstream *stream) +{ + uint8_t err_type = mp_error_type_from_str(box_error_type(error)); + + uint32_t errcode; + const char *custom_type = NULL; + const char *ad_obj_type = NULL; + const char *ad_obj_name = NULL; + const char *ad_access_type = NULL; + + /* Error type, reason, errno, file and line are the necessary fields */ + uint32_t details_num = 5; + + uint32_t data_size = 0; + + data_size += mp_sizeof_uint(MP_ERROR_DET_TYPE); + data_size += mp_sizeof_uint(err_type); + data_size += mp_sizeof_uint(MP_ERROR_DET_LINE); + data_size += mp_sizeof_uint(error->line); + data_size += mp_sizeof_uint(MP_ERROR_DET_FILE); + data_size += mp_sizeof_str(strlen(error->file)); + data_size += mp_sizeof_uint(MP_ERROR_DET_REASON); + data_size += mp_sizeof_str(strlen(error->errmsg)); + data_size += mp_sizeof_uint(MP_ERROR_DET_ERRNO); + data_size += mp_sizeof_uint(error->saved_errno); + + if (error->lua_traceback) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_DET_BACKTRACE); + data_size += mp_sizeof_str(strlen(error->lua_traceback)); + } + + if (err_type == MP_ERROR_TYPE_CLIENT || + err_type == MP_ERROR_TYPE_ACCESS_DENIED || + err_type == MP_ERROR_TYPE_CUSTOM) { + ++details_num; + errcode = box_error_code(error); + data_size += mp_sizeof_uint(MP_ERROR_DET_CODE); + data_size += mp_sizeof_uint(errcode); + if (err_type == MP_ERROR_TYPE_CUSTOM) { + ++details_num; + data_size += mp_sizeof_uint(MP_ERROR_DET_CUSTOM_TYPE); + custom_type = box_custom_error_type(error); + data_size += mp_sizeof_str(strlen(custom_type)); + } else if (err_type == MP_ERROR_TYPE_ACCESS_DENIED) { + AccessDeniedError *ad_err = type_cast(AccessDeniedError, + error); + details_num += 3; + ad_obj_type = ad_err->object_type(); + ad_obj_name = ad_err->object_name(); + ad_access_type = ad_err->access_type(); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_OBJ_TYPE); + data_size += mp_sizeof_str(strlen(ad_obj_type)); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_OBJ_NAME); + data_size += mp_sizeof_str(strlen(ad_obj_name)); + data_size += mp_sizeof_uint(MP_ERROR_DET_AD_ACCESS_TYPE); + data_size += mp_sizeof_str(strlen(ad_access_type)); + } + } + + data_size += mp_sizeof_map(details_num); + uint32_t data_size_ext = mp_sizeof_ext(data_size); + char *ptr = mpstream_reserve(stream, data_size_ext); + + char *data = ptr; + data = mp_encode_extl(data, MP_ERROR, data_size); + data = mp_encode_map(data, details_num); + data = mp_encode_uint(data, MP_ERROR_DET_TYPE); + data = mp_encode_uint(data, err_type); + data = mp_encode_uint(data, MP_ERROR_DET_LINE); + data = mp_encode_uint(data, err_type); + data = mp_encode_uint(data, MP_ERROR_DET_FILE); + data = mp_encode_str(data, error->file, strlen(error->file)); + data = mp_encode_uint(data, MP_ERROR_DET_REASON); + data = mp_encode_str(data, error->errmsg, strlen(error->errmsg)); + data = mp_encode_uint(data, MP_ERROR_DET_ERRNO); + data = mp_encode_uint(data, error->saved_errno); + if(error->lua_traceback) { + data = mp_encode_uint(data, MP_ERROR_DET_BACKTRACE); + data = mp_encode_str(data, error->lua_traceback, + strlen(error->lua_traceback)); + } + + if (err_type == MP_ERROR_TYPE_CLIENT || + err_type == MP_ERROR_TYPE_ACCESS_DENIED || + err_type == MP_ERROR_TYPE_CUSTOM) { + data = mp_encode_uint(data, MP_ERROR_DET_CODE); + data = mp_encode_uint(data, errcode); + if (err_type == MP_ERROR_TYPE_CUSTOM) { + data = mp_encode_uint(data, MP_ERROR_DET_CUSTOM_TYPE); + data = mp_encode_str(data, custom_type, + strlen(custom_type)); + } else if (err_type == MP_ERROR_TYPE_ACCESS_DENIED) { + data = mp_encode_uint(data, MP_ERROR_DET_AD_OBJ_TYPE); + data = mp_encode_str(data, ad_obj_type, + strlen(ad_obj_type)); + data = mp_encode_uint(data, MP_ERROR_DET_AD_OBJ_NAME); + data = mp_encode_str(data, ad_obj_name, + strlen(ad_obj_name)); + data = mp_encode_uint(data, MP_ERROR_DET_AD_ACCESS_TYPE); + data = mp_encode_str(data, ad_access_type, + strlen(ad_access_type)); + } + } + + assert(data == ptr + data_size_ext); + mpstream_advance(stream, data_size_ext); +} + +static struct error * +build_error(struct mp_error *mp_error) +{ + struct error *err; + switch (mp_error->error_type) { + case MP_ERROR_TYPE_UNKNOWN: + err = NULL; + break; + case MP_ERROR_TYPE_CLIENT: + { + ClientError *e = new ClientError(mp_error->file, mp_error->line, + ER_UNKNOWN); + e->m_errcode = mp_error->error_code; + err = (struct error *)e; + break; + } + case MP_ERROR_TYPE_CUSTOM: + err = BuildCustomError(mp_error->file, mp_error->line, + mp_error->custom_type); + break; + case MP_ERROR_TYPE_ACCESS_DENIED: + err = BuildAccessDeniedError(mp_error->file, mp_error->line, + mp_error->ad_access_type, + mp_error->ad_obj_type, + mp_error->ad_obj_name, ""); + break; + case MP_ERROR_TYPE_XLOG: + err = BuildXlogError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TYPE_XLOG_GAP: + err = ReBuildXlogGapError(mp_error->file, mp_error->line, + mp_error->reason); + break; + case MP_ERROR_TyPE_SYSTEM: + err = BuildSystemError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_SOCKET: + err = BuildSocketError(mp_error->file, mp_error->line, "", ""); + error_format_msg(err, "", mp_error->reason); + break; + case MP_ERROR_TyPE_OOM: + err = BuildOutOfMemory(mp_error->file, mp_error->line, + 0, "", ""); + error_format_msg(err, "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_TIMED_OUT: + err = BuildTimedOut(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_CHANNEL_IS_CLOSED: + err = BuildChannelIsClosed(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_FIBER_IS_CANCELLED: + err = BuildFiberIsCancelled(mp_error->file, mp_error->line); + break; + case MP_ERROR_TyPE_LUAJIT: + err = BuildLuajitError(mp_error->file, mp_error->line, + mp_error->reason); + break; + case MP_ERROR_TyPE_ILLEGAL_PARAMS: + err = BuildIllegalParams(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_COLLATION: + err = BuildCollationError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_SWIM: + err = BuildSwimError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + case MP_ERROR_TyPE_CRYPTO: + err = BuildCryptoError(mp_error->file, mp_error->line, + "%s", mp_error->reason); + break; + default: + break; + } + + err->traceback_mode = false; + err->saved_errno = mp_error->saved_errno; + error_format_msg(err, "%s", mp_error->reason); + + return err; +} + +struct error * +error_unpack(const char **data, uint32_t len) +{ + const char *svp = *data; + if (mp_typeof(**data) != MP_MAP) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR format"); + return NULL; + } + + struct mp_error mp_err; + mp_error_init(&mp_err); + + uint32_t map_size = mp_decode_map(data); + + struct error *err = NULL; + for (uint32_t i = 0; i < map_size; ++i) { + if (mp_typeof(**data) != MP_UINT) { + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; + } + + uint8_t key = mp_decode_uint(data); + const char *str; + uint32_t str_len; + switch(key) { + case MP_ERROR_DET_TYPE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.error_type = mp_decode_uint(data); + break; + case MP_ERROR_DET_FILE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.file = strndup(str, str_len); + break; + case MP_ERROR_DET_LINE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.line = mp_decode_uint(data); + break; + case MP_ERROR_DET_REASON: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.reason = strndup(str, str_len); + break; + case MP_ERROR_DET_ERRNO: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.saved_errno = mp_decode_uint(data); + break; + case MP_ERROR_DET_CODE: + if (mp_typeof(**data) != MP_UINT) + goto error; + mp_err.error_code = mp_decode_uint(data); + break; + case MP_ERROR_DET_BACKTRACE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.backtrace = strndup(str, str_len); + break; + case MP_ERROR_DET_CUSTOM_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.custom_type = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_OBJ_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_obj_type = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_OBJ_NAME: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_obj_name = strndup(str, str_len); + break; + case MP_ERROR_DET_AD_ACCESS_TYPE: + if (mp_typeof(**data) != MP_STR) + goto error; + str = mp_decode_str(data, &str_len); + mp_err.ad_access_type = strndup(str, str_len); + break; + default: + mp_next(data); + } + } + + assert(*data == svp + len); + + err = build_error(&mp_err); + mp_error_cleanup(&mp_err); + return err; + +error: + diag_set(ClientError, ER_INVALID_MSGPACK, + "Invalid MP_ERROR MsgPack format"); + return NULL; +} diff --git a/src/box/lua/mp_error.h b/src/box/lua/mp_error.h new file mode 100644 index 0000000..9eab213 --- /dev/null +++ b/src/box/lua/mp_error.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +#include "stdint.h" + +struct mpstream; + +void +error_to_mpstream(struct error *error, struct mpstream *stream); + +struct error * +error_unpack(const char **data, uint32_t len); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index c8f76b0..d3997af 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -1050,7 +1050,7 @@ local function new_sm(host, port, opts, connection, greeting) -- Set extended error format for session. if opts.error_extended then - local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 1) + local ext_err_supported = version_at_least(remote.peer_version_id, 2, 4, 0) if not ext_err_supported then box.error(box.error.PROC_LUA, "Server doesn't support extended error format") diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c index 1e3c3d6..af2c5b4 100644 --- a/src/box/lua/tuple.c +++ b/src/box/lua/tuple.c @@ -120,7 +120,7 @@ luaT_tuple_new(struct lua_State *L, int idx, box_tuple_format_t *format) int argc = lua_gettop(L); mpstream_encode_array(&stream, argc); for (int k = 1; k <= argc; ++k) { - luamp_encode(L, luaL_msgpack_default, &stream, k); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, k); } } else { /* Create the tuple from a Lua table. */ @@ -252,18 +252,18 @@ luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg, return tuple_to_mpstream(tuple, stream); struct luaL_field field; - if (luaL_tofield(L, cfg, index, &field) < 0) + if (luaL_tofield(L, cfg, NULL, index, &field) < 0) luaT_error(L); if (field.type == MP_ARRAY) { lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } else if (field.type == MP_NIL) { mpstream_encode_array(stream, 0); } else { mpstream_encode_array(stream, 1); lua_pushvalue(L, index); - luamp_encode_r(L, cfg, stream, &field, 0); + luamp_encode_r(L, cfg, NULL, stream, &field, 0); lua_pop(L, 1); } } @@ -275,7 +275,7 @@ luamp_encode_tuple(struct lua_State *L, struct luaL_serializer *cfg, struct tuple *tuple = luaT_istuple(L, index); if (tuple != NULL) { return tuple_to_mpstream(tuple, stream); - } else if (luamp_encode(L, cfg, stream, index) != MP_ARRAY) { + } else if (luamp_encode(L, cfg, NULL, stream, index) != MP_ARRAY) { diag_set(ClientError, ER_TUPLE_NOT_ARRAY); luaT_error(L); } @@ -290,20 +290,6 @@ tuple_to_mpstream(struct tuple *tuple, struct mpstream *stream) mpstream_advance(stream, bsize); } -/* A MsgPack extensions handler that supports tuples */ -static enum mp_type -luamp_encode_extension_box(struct lua_State *L, int idx, - struct mpstream *stream) -{ - struct tuple *tuple = luaT_istuple(L, idx); - if (tuple != NULL) { - tuple_to_mpstream(tuple, stream); - return MP_ARRAY; - } - - return MP_EXT; -} - /** * Convert a tuple into lua table. Named fields are stored as * {name = value} pairs. Not named fields are stored as @@ -435,7 +421,7 @@ lbox_tuple_transform(struct lua_State *L) mpstream_encode_array(&stream, 3); mpstream_encode_str(&stream, "!"); mpstream_encode_uint(&stream, offset); - luamp_encode(L, luaL_msgpack_default, &stream, i); + luamp_encode(L, luaL_msgpack_default, NULL, &stream, i); } mpstream_flush(&stream); @@ -582,8 +568,6 @@ box_lua_tuple_init(struct lua_State *L) luaL_register_module(L, tuplelib_name, lbox_tuplelib); lua_pop(L, 1); - luamp_set_encode_extension(luamp_encode_extension_box); - tuple_serializer_update_options(); trigger_create(&tuple_serializer.update_trigger, on_msgpack_serializer_update, NULL, NULL); diff --git a/src/box/sql/func.c b/src/box/sql/func.c index 6e724c8..bbc1f6f 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -257,8 +257,10 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size) return NULL; for (int i = 0; i < argc; i++) { struct luaL_field field; - if (luaL_tofield(L, luaL_msgpack_default, -1 - i, &field) < 0) + if (luaL_tofield(L, luaL_msgpack_default, + NULL, -1 - i, &field) < 0) { goto error; + } switch (field.type) { case MP_BOOL: mem_set_bool(&val[i], field.bval); diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h index bc9873f..a2a5079 100644 --- a/src/lib/core/mp_extension_types.h +++ b/src/lib/core/mp_extension_types.h @@ -40,8 +40,9 @@ * You may assign values in range [0, 127] */ enum mp_extension_type { - MP_UNKNOWN_EXTENSION = 0, - MP_DECIMAL = 1, + MP_UNKNOWN_EXTENSION = 0, + MP_DECIMAL = 1, + MP_ERROR = 2 }; #endif diff --git a/src/lua/error.c b/src/lua/error.c index cd6ab54..109f947 100644 --- a/src/lua/error.c +++ b/src/lua/error.c @@ -34,8 +34,6 @@ #include #include "utils.h" -static int CTID_CONST_STRUCT_ERROR_REF = 0; - struct error * luaL_iserror(struct lua_State *L, int narg) { diff --git a/src/lua/error.h b/src/lua/error.h index 16cdaf7..4e4dc04 100644 --- a/src/lua/error.h +++ b/src/lua/error.h @@ -37,7 +37,6 @@ extern "C" { #endif /* defined(__cplusplus) */ - /** \cond public */ struct error; @@ -62,6 +61,8 @@ void luaT_pusherror(struct lua_State *L, struct error *e); /** \endcond public */ +extern uint32_t CTID_CONST_STRUCT_ERROR_REF; + struct error * luaL_iserror(struct lua_State *L, int narg); diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c index edbc15b..6e0cf15 100644 --- a/src/lua/msgpack.c +++ b/src/lua/msgpack.c @@ -108,8 +108,8 @@ luamp_set_decode_extension(luamp_decode_extension_f handler) enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level) + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + struct luaL_field *field, int level) { int top = lua_gettop(L); enum mp_type type; @@ -154,13 +154,13 @@ restart: /* used by MP_EXT */ lua_pushnil(L); /* first key */ while (lua_next(L, top) != 0) { lua_pushvalue(L, -2); /* push a copy of key to top */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); /* pop a copy of key */ - if (luaL_tofield(L, cfg, lua_gettop(L), field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); /* pop value */ } assert(lua_gettop(L) == top); @@ -179,9 +179,9 @@ restart: /* used by MP_EXT */ mpstream_encode_array(stream, size); for (uint32_t i = 0; i < size; i++) { lua_rawgeti(L, top, i + 1); - if (luaL_tofield(L, cfg, top + 1, field) < 0) + if (luaL_tofield(L, cfg, ctx, top + 1, field) < 0) return luaT_error(L); - luamp_encode_r(L, cfg, stream, field, level + 1); + luamp_encode_r(L, cfg, ctx, stream, field, level + 1); lua_pop(L, 1); } assert(lua_gettop(L) == top); @@ -191,6 +191,8 @@ restart: /* used by MP_EXT */ case MP_DECIMAL: mpstream_encode_decimal(stream, field->decval); return MP_EXT; + case MP_ERROR: + return luamp_encode_extension(L, top, stream); default: /* Run trigger if type can't be encoded */ type = luamp_encode_extension(L, top, stream); @@ -209,7 +211,8 @@ restart: /* used by MP_EXT */ enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index) + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + int index) { int top = lua_gettop(L); if (index < 0) @@ -221,9 +224,9 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, } struct luaL_field field; - if (luaL_tofield(L, cfg, lua_gettop(L), &field) < 0) + if (luaL_tofield(L, cfg, ctx, lua_gettop(L), &field) < 0) return luaT_error(L); - enum mp_type top_type = luamp_encode_r(L, cfg, stream, &field, 0); + enum mp_type top_type = luamp_encode_r(L, cfg, ctx, stream, &field, 0); if (!on_top) { lua_remove(L, top + 1); /* remove a value copy */ @@ -232,6 +235,7 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, return top_type; } + void luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, const char **data) @@ -354,7 +358,7 @@ lua_msgpack_encode(lua_State *L) mpstream_init(&stream, buf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error, L); - luamp_encode(L, cfg, &stream, 1); + luamp_encode(L, cfg, NULL, &stream, 1); mpstream_flush(&stream); if (index > 1) { diff --git a/src/lua/msgpack.h b/src/lua/msgpack.h index d0ade30..e5c7a94 100644 --- a/src/lua/msgpack.h +++ b/src/lua/msgpack.h @@ -43,6 +43,7 @@ extern "C" { struct luaL_serializer; struct mpstream; +struct luaL_serializer_ctx; /** * Default instance of msgpack serializer (msgpack = require('msgpack')). @@ -63,12 +64,13 @@ enum { LUAMP_ALLOC_FACTOR = 256 }; /* low-level function needed for execute_lua_call() */ enum mp_type luamp_encode_r(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, struct luaL_field *field, - int level); + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + struct luaL_field *field, int level); enum mp_type luamp_encode(struct lua_State *L, struct luaL_serializer *cfg, - struct mpstream *stream, int index); + struct luaL_serializer_ctx *ctx, struct mpstream *stream, + int index); void luamp_decode(struct lua_State *L, struct luaL_serializer *cfg, diff --git a/src/lua/utils.c b/src/lua/utils.c index 54d18ac..c0f173b 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -46,6 +46,7 @@ static uint32_t CTID_STRUCT_IBUF_PTR; static uint32_t CTID_CHAR_PTR; static uint32_t CTID_CONST_CHAR_PTR; uint32_t CTID_DECIMAL; +uint32_t CTID_CONST_STRUCT_ERROR_REF; void * @@ -445,7 +446,7 @@ lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg, lua_pcall(L, 1, 1, 0); /* replace obj with the unpacked value */ lua_replace(L, idx); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } /* else ignore lua_gettable exceptions */ lua_settop(L, top); /* remove temporary objects */ @@ -508,7 +509,7 @@ lua_field_try_serialize(struct lua_State *L) /* copy object itself */ lua_pushvalue(L, 1); lua_call(L, 1, 1); - s->is_error = (luaL_tofield(L, cfg, -1, field) != 0); + s->is_error = (luaL_tofield(L, cfg, NULL, -1, field) != 0); s->is_value_returned = true; return 1; } @@ -634,12 +635,13 @@ lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx, lua_call(L, 1, 1); lua_replace(L, idx); lua_settop(L, top); - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); } int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + struct luaL_serializer_ctx *ctx, int index, struct luaL_field *field) { if (index < 0) @@ -746,6 +748,10 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, if (cd->ctypeid == CTID_DECIMAL) { field->ext_type = MP_DECIMAL; field->decval = (decimal_t *) cdata; + } else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF && + ctx && + ctx->err_format_ver == ERR_FORMAT_EX) { + field->ext_type = MP_ERROR; } else { field->ext_type = MP_UNKNOWN_EXTENSION; } @@ -792,8 +798,8 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, { if (idx < 0) idx = lua_gettop(L) + idx + 1; - assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ + assert(field->type == MP_EXT && field->ext_type == MP_UNKNOWN_EXTENSION); /* must be called after tofield() */ if (cfg->encode_load_metatables) { int type = lua_type(L, idx); if (type == LUA_TCDATA) { diff --git a/src/lua/utils.h b/src/lua/utils.h index 5875ba3..4ccce03 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -381,6 +381,7 @@ struct luaL_field { * * @param L stack * @param cfg configuration + * @param serializer_ctx the Lua serializer context * @param index stack index * @param field conversion result * @@ -388,7 +389,8 @@ struct luaL_field { * @retval -1 Error. */ int -luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index, +luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, + struct luaL_serializer_ctx *ctx, int index, struct luaL_field *field); /** @@ -427,7 +429,7 @@ static inline void luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx, struct luaL_field *field) { - if (luaL_tofield(L, cfg, idx, field) < 0) + if (luaL_tofield(L, cfg, NULL, idx, field) < 0) luaT_error(L); if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION) return; diff --git a/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua new file mode 100755 index 0000000..45c200f --- /dev/null +++ b/test/box-tap/extended_error.test.lua @@ -0,0 +1,164 @@ +#! /usr/bin/env tarantool + +local netbox = require('net.box') +local os = require('os') +local tap = require('tap') +local uri = require('uri') + +local test = tap.test('check an extended error') +test:plan(4) + + +function error_func() + box.error(box.error.PROC_LUA, "An old good error") +end + +function custom_error_func() + box.error("My Custom Type", "A modern custom error") +end + +function custom_error_func_2() + local err = box.error.new("My Custom Type", "A modern custom error") + return err +end + +local function version_at_least(peer_version_str, major, minor, patch) + local major_p, minor_p, patch_p = string.match(peer_version_str, + "(%d)%.(%d)%.(%d)") + major_p = tonumber(major_p) + minor_p = tonumber(minor_p) + patch_p = tonumber(patch_p) + + if major_p < major + or (major_p == major and minor_p < minor) + or (major_p == major and minor_p == minor and patch_p < patch) then + return false + end + + return true +end + +local function grant_func(user, name) + box.schema.func.create(name, {if_not_exists = true}) + box.schema.user.grant(user, 'execute', 'function', name, { + if_not_exists = true + }) +end + +local function check_error(err, check_list) + if type(check_list) ~= 'table' then + return false + end + + for k, v in pairs(check_list) do + if k == 'type' then + if err.base_type ~= v then + return false + end + elseif k == 'custom_type' then + if err.type ~= v then + return false + end + elseif k == 'message' then + if err.message ~= v then + return false + end + elseif k == 'trace' then + local trace = "File " .. err.trace[1]['file'] + .. "\nLine " .. err.trace[1]['line'] + if not string.match(trace, v) then + return false + end + elseif k == 'errno' then + if err.errno ~= v then + return false + end + elseif k == 'is_custom' then + if (err.base_type == 'CustomError') ~= v then + return false + end + else + return false + end + end + + return true +end + +local function test_old_transmission(host, port) + grant_func('guest', 'error_func') + grant_func('guest', 'custom_error_func_2') + + local connection = netbox.connect(host, port) + box.error.cfg({traceback_supplementation = true}) + local _, err = pcall(connection.call, connection, 'error_func') + local err_2 = connection:call('custom_error_func_2') + + + local check_list = { + type = 'ClientError', + message = 'An old good error', + trace = '^File builtin/box/net_box.lua\nLine %d+$', + is_custom = false + } + + local check_result = check_error(err, check_list) + local check_result_2 = type(err_2) == 'string' and err_2 == 'A modern custom error' + + test:ok(check_result, 'Check the old transmission type(IPROTO_ERROR)') + test:ok(check_result_2, 'Check the old transmission type(IPROTO_OK)') + + connection:close() +end + +local function test_extended_transmission(host, port) + grant_func('guest', 'custom_error_func') + grant_func('guest', 'custom_error_func_2') + box.schema.user.grant('guest','read,write', 'space', '_session_settings') + + local connection = netbox.connect(host, port, {error_extended = true}) + local _, err = pcall(connection.call, connection, 'custom_error_func') + local err_2 = connection:call('custom_error_func_2') + + local check_list = { + type = 'CustomError', + custom_type = 'My Custom Type', + message = 'A modern custom error', + trace = '^File builtin/box/net_box.lua\nLine %d+$', + is_custom = true + } + + local check_list_2 = { + type = 'CustomError', + custom_type = 'My Custom Type', + message = 'A modern custom error', + trace = '.*extended_error.test.lua\nLine 2$', + is_custom = true + } + + local check_result = check_error(err, check_list) + local check_result_2 = check_error(err_2, check_list_2) + test:ok(check_result, 'Check the extended transmission type(IPROTO_ERROR)') + test:ok(check_result_2, 'Check the extended transmission type(IPROTO_OK)') + + connection:close() +end + +box.cfg{ + listen = os.getenv('LISTEN') +} +local tarantool_ver = string.match(box.info.version, "%d%.%d%.%d") +local host= uri.parse(box.cfg.listen).host or 'localhost' +local port = uri.parse(box.cfg.listen).service + +if version_at_least(box.info.version, 2, 4, 0) then + test_extended_transmission(host, port) +else + test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' .. + ' don\'t support extended transmission') + test:ok(true, 'Current version of tarantool(' .. tarantool_ver .. ')' .. + ' don\'t support extended transmission') +end +test_old_transmission(host, port) + +os.exit(test:check() and 0 or 1) -- 2.7.4