[Tarantool-patches] [PATCH 6/6] error: Transmit an error through IPROTO_OK as object

Leonid Vasiliev lvasiliev at tarantool.org
Tue Mar 24 15:46:04 MSK 2020


An ability to transfer an error through IPROTO_OK as object has been added.

Accordinally to https://github.com/tarantool/tarantool/issues/4398
ability to transfer an error through net.box (an IPROTO_OK case)
has been added. The error will be transmit through net.box as object
if the extended transfer error format is used. In this case
for serialization will be used a __serialize_ex method.

@TarantoolBot document
Title: error: transmit an error object throw IPROTO_OK
The error will be transmitted through IPROTO_OK as an object
if the extended transmission error format is used
(before it was transmitted as a string).
---
 src/box/execute.c                    |  1 +
 src/box/lua/call.c                   | 29 ++++++++++++---------
 src/box/lua/error.cc                 | 13 ++++++++++
 src/box/lua/execute.c                |  2 +-
 src/box/lua/net_box.lua              | 10 ++++++++
 src/box/lua/tuple.c                  | 12 ++++-----
 src/box/session.cc                   |  4 +++
 src/box/session.h                    | 17 +++---------
 src/box/sql/func.c                   |  4 ++-
 src/lib/core/mp_extension_types.h    |  4 +--
 src/lib/core/port.h                  |  4 +--
 src/lua/error.c                      |  2 --
 src/lua/error.h                      |  3 ++-
 src/lua/error.lua                    | 22 +++++++++++++++-
 src/lua/msgpack.c                    | 26 ++++++++++---------
 src/lua/msgpack.h                    |  8 +++---
 src/lua/utils.c                      | 28 +++++++++++++++-----
 src/lua/utils.h                      | 27 +++++++++++++++++--
 test/box-tap/extended_error.test.lua | 50 +++++++++++++++++++++++++++++++++---
 19 files changed, 199 insertions(+), 67 deletions(-)

diff --git a/src/box/execute.c b/src/box/execute.c
index 24f8529..e48c694 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -49,6 +49,7 @@
 #include "box/sql_stmt_cache.h"
 #include "session.h"
 #include "rmean.h"
+#include "lua/utils.h"
 
 const char *sql_info_key_strs[] = {
 	"row_count",
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index f1bbde7..8e2d5d2 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 = &current_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/error.cc b/src/box/lua/error.cc
index 4a66335..efe9e8e 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -189,6 +189,15 @@ luaT_error_custom_type(lua_State *L)
 }
 
 static int
+luaT_error_code(lua_State *L)
+{
+	struct error *e = luaL_checkerror(L, -1);
+	const uint32_t code = box_error_code(e);
+	lua_pushinteger(L, code);
+	return 1;
+}
+
+static int
 luaT_error_set_lua_bt(lua_State *L)
 {
 	if (lua_gettop(L) < 2)
@@ -337,6 +346,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_set_lua_bt);
 			lua_setfield(L, -2, "set_lua_bt");
 		}
+		{
+			lua_pushcfunction(L, luaT_error_code);
+			lua_setfield(L, -2, "error_code");
+		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
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/net_box.lua b/src/box/lua/net_box.lua
index 7fd317c..281b465 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -329,6 +329,15 @@ local function create_transport(host, port, user, password, callback,
             return nil, box.error.new({code = self.errno,
                                        reason = self.response})
         elseif not self.id then
+            if type(self.response) == 'table' then
+                for k, v in pairs(self.response) do
+                    if type(v) == 'table' and v['error_magic']
+                        and v['error_magic'] == 13371338 then
+                        local err = parse_extended_error(v)
+                        self.response[k] = err
+                    end
+                end
+            end
             return self.response
         elseif not worker_fiber then
             return nil, box.error.new(E_NO_CONNECTION)
@@ -632,6 +641,7 @@ local function create_transport(host, port, user, password, callback,
             ffi.copy(wpos, body_rpos, body_len)
             body_len = tonumber(body_len)
             if status == IPROTO_OK_KEY then
+                -- tuta interesno
                 request.response = body_len
                 requests[id] = nil
                 request.id = nil
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 1e3c3d6..18efd1e 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);
 	}
@@ -435,7 +435,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);
 
diff --git a/src/box/session.cc b/src/box/session.cc
index c0224ca..6e13fd2 100644
--- a/src/box/session.cc
+++ b/src/box/session.cc
@@ -148,6 +148,9 @@ session_create(enum session_type type)
 	/* Set default negotiation parameters */
 	session->neg_param.err_format_ver = ERR_FORMAT_DEF;
 
+	/* Set default Lua serializer context */
+	session->serializer_ctx.err_format_ver = ERR_FORMAT_DEF;
+
 	/* For on_connect triggers. */
 	credentials_create(&session->credentials, guest_user);
 	struct mh_i64ptr_node_t node;
@@ -383,5 +386,6 @@ session_update_neg_parameters(struct session *session,
 			      const struct negotiation_params *params)
 {
 	session->neg_param.err_format_ver = params->err_format_ver;
+	session->serializer_ctx.err_format_ver = params->err_format_ver;
 	return 0;
 }
diff --git a/src/box/session.h b/src/box/session.h
index c775fd0..91b6850 100644
--- a/src/box/session.h
+++ b/src/box/session.h
@@ -36,6 +36,7 @@
 #include "fiber.h"
 #include "user.h"
 #include "authentication.h"
+#include "lua/utils.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -81,20 +82,8 @@ union session_meta {
 };
 
 /**
- * An error transmission formats
- */
-enum error_formats {
-	/** Default(old) format */
-	ERR_FORMAT_DEF,
-	/** Extended format */
-	ERR_FORMAT_EX,
-	/** The max version of error format */
-	ERR_FORMAT_UNK
-};
-
-/**
  * Parameters which may be changed at negotiation phase of session
-*/
+ */
 struct negotiation_params {
 	/** Version of a format for an error transmission */
 	uint8_t err_format_ver;
@@ -132,6 +121,8 @@ struct session {
 	struct trigger fiber_on_stop;
 	/** Negotiation parameters */
 	struct negotiation_params neg_param;
+	/** Session Lua serializer context */
+	struct luaL_serializer_ctx serializer_ctx;
 };
 
 struct session_vtab {
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..0c0fae0 100644
--- a/src/lib/core/mp_extension_types.h
+++ b/src/lib/core/mp_extension_types.h
@@ -40,8 +40,8 @@
  * 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,
 };
 
 #endif
diff --git a/src/lib/core/port.h b/src/lib/core/port.h
index bfdfa46..2aa9338 100644
--- a/src/lib/core/port.h
+++ b/src/lib/core/port.h
@@ -136,9 +136,9 @@ port_dump_msgpack(struct port *port, struct obuf *out)
 }
 
 static inline int
-port_dump_msgpack_16(struct port *port, struct obuf *out)
+port_dump_msgpack_16(struct port *port, struct obuf *buf)
 {
-	return port->vtab->dump_msgpack_16(port, out);
+	return port->vtab->dump_msgpack_16(port, buf);
 }
 
 static inline void
diff --git a/src/lua/error.c b/src/lua/error.c
index 3a12e20..c72f835 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -36,8 +36,6 @@
 
 #include <string.h>
 
-static int CTID_CONST_STRUCT_ERROR_REF = 0;
-
 /*
  * Memory for the traceback string is obtained with malloc,
  * and can be freed with free.
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/error.lua b/src/lua/error.lua
index 26abec8..da6b3cc 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -33,6 +33,14 @@ int
 exception_get_int(struct error *e, const struct method_info *method);
 ]]
 
+-- error details
+local error_details = {
+    REASON = 0,
+    CODE = 1,
+    BACKTRACE = 2,
+    CUSTOM_TYPE = 3
+}
+
 local REFLECTION_CACHE = {}
 
 local function reflection_enumerate(err)
@@ -152,6 +160,17 @@ local function error_serialize(err)
     return error_message(err)
 end
 
+local function error_serialize_ex(err)
+    local result = {
+        ["error_magic"] = 13371338,
+        [error_details.CODE] = box.error.error_code(err),
+        [error_details.CUSTOM_TYPE] = error_custom_type(err),
+        [error_details.REASON] = error_message(err),
+        [error_details.BACKTRACE] = error_backtrace(err)
+    }
+    return result
+end
+
 local function error_is_custom(err)
     return ffi.string(err._type.name) == 'CustomError'
 end
@@ -161,7 +180,8 @@ local error_methods = {
     ["raise"] = error_raise,
     ["match"] = error_match, -- Tarantool 1.6 backward compatibility
     ["is_custom"] = error_is_custom,
-    ["__serialize"] = error_serialize
+    ["__serialize"] = error_serialize,
+    ["__serialize_ex"] = error_serialize_ex
 }
 
 local function error_index(err, key)
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index edbc15b..e1f0e8c 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);
@@ -209,7 +209,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 +222,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 +233,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 +356,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 de14c77..9a2b0b9 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 *
@@ -436,16 +437,21 @@ lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg,
 	int top = lua_gettop(L);
 	lua_pushcfunction(L, lua_gettable_wrapper);
 	lua_pushvalue(L, idx);
-	lua_pushliteral(L, LUAL_SERIALIZE);
+	const char *serialize = LUAL_SERIALIZE;
+	if (field->use_serialize_ex)
+		serialize = LUAL_SERIALIZE_EX;
+
+	lua_pushstring(L, serialize);
+
 	if (lua_pcall(L, 2, 1, 0) == 0  && !lua_isnil(L, -1)) {
 		if (!lua_isfunction(L, -1))
-			luaL_error(L, "invalid " LUAL_SERIALIZE  " value");
+			luaL_error(L, "invalid %s value", serialize);
 		/* copy object itself */
 		lua_pushvalue(L, idx);
 		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 +514,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 +640,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)
@@ -649,6 +656,8 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 	double intpart;
 	size_t size;
 
+	field->use_serialize_ex = false;
+
 #define CHECK_NUMBER(x) ({							\
 	if (!isfinite(x) && !cfg->encode_invalid_numbers) {			\
 		if (!cfg->encode_invalid_as_nil) {				\
@@ -746,6 +755,11 @@ 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_UNKNOWN_EXTENSION;
+				field->use_serialize_ex = true;
 			} else {
 				field->ext_type = MP_UNKNOWN_EXTENSION;
 			}
@@ -781,6 +795,7 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 	default:
 		field->type = MP_EXT;
 		field->ext_type = MP_UNKNOWN_EXTENSION;
+		field->use_serialize_ex = false;
 	}
 #undef CHECK_NUMBER
 	return 0;
@@ -792,6 +807,7 @@ 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() */
 
 	if (cfg->encode_load_metatables) {
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 0b36727..9e02003 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -266,12 +266,32 @@ struct luaL_serializer {
 	struct rlist on_update;
 };
 
+/**
+ * An error serialization formats
+ */
+enum error_formats {
+	/** Default(old) format */
+	ERR_FORMAT_DEF,
+	/** Extended format */
+	ERR_FORMAT_EX,
+	/** The max version of error format */
+	ERR_FORMAT_UNK
+};
+
+/**
+ * A serializer context (additional settings for a serializer)
+ */
+struct luaL_serializer_ctx {
+	uint8_t err_format_ver;
+};
+
 extern int luaL_nil_ref;
 extern int luaL_map_metatable_ref;
 extern int luaL_array_metatable_ref;
 
 #define LUAL_SERIALIZER "serializer"
 #define LUAL_SERIALIZE "__serialize"
+#define LUAL_SERIALIZE_EX "__serialize_ex"
 
 struct luaL_serializer *
 luaL_newserializer(struct lua_State *L, const char *modname, const luaL_Reg *reg);
@@ -325,6 +345,7 @@ struct luaL_field {
 	/* subtypes of MP_EXT */
 	enum mp_extension_type ext_type;
 	bool compact;                /* a flag used by YAML serializer */
+	bool use_serialize_ex;
 };
 
 /**
@@ -361,6 +382,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
  *
@@ -368,7 +390,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);
 
 /**
@@ -407,7 +430,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
index 58a4984..b38a7ab 100755
--- a/test/box-tap/extended_error.test.lua
+++ b/test/box-tap/extended_error.test.lua
@@ -6,7 +6,7 @@ local tap = require('tap')
 local uri = require('uri')
 
 local test = tap.test('check an extended error')
-test:plan(2)
+test:plan(4)
 
 
 function error_func()
@@ -17,6 +17,12 @@ function custom_error_func()
     box.error(box.error.CUSTOM_ERROR, "My Custom Type", "A modern custom error")
 end
 
+function custom_error_func_2()
+    local err = box.error.new(box.error.CUSTOM_ERROR,
+                              "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)")
@@ -86,9 +92,11 @@ 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)
     local _, err = pcall(connection.call, connection, 'error_func')
+    local err_2 = connection:call('custom_error_func_2')
 
     local backtrace_pattern =
     "^stack traceback:\n" ..
@@ -109,15 +117,22 @@ local function test_old_transmission(host, port)
     }
 
     local check_result = check_error(err, check_list)
-    test:ok(check_result, 'Check the old transmission type')
+    local check_result_2 = type(err_2) == 'string'
+        and err_2 == 'User custom error: 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')
 
     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 backtrace_pattern =
     "^Remote%(.+:%d+%):\n" ..
@@ -135,6 +150,21 @@ local function test_extended_transmission(host, port)
     ".*box%-tap/extended_error.test.lua:%d+: in function 'test_extended_transmission'\n" ..
     ".*box%-tap/extended_error.test.lua:%d+: in main chunk$"
 
+    local backtrace_pattern_2 =
+    "^Remote%(.+:%d+%):\n" ..
+    "stack traceback:\n" ..
+    "%s+%[C%]: in function 'new'\n" ..
+    ".*box%-tap/extended_error.test.lua:%d+: in function <.*box%-tap/extended_error.test.lua:%d+>\n" ..
+    "%s+%[C%]: at 0x%w+\n" ..
+    "End Remote\n" ..
+    "stack traceback:\n" ..
+    "%s+builtin/box/net_box.lua:%d+: in function 'parse_extended_error'\n" ..
+    "%s+builtin/box/net_box.lua:%d+: in function 'perform_request'\n" ..
+    "%s+builtin/box/net_box.lua:%d+: in function '_request'\n" ..
+    "%s+builtin/box/net_box.lua:%d+: in function 'call'\n" ..
+    ".*box%-tap/extended_error.test.lua:%d+: in function 'test_extended_transmission'\n" ..
+    ".*box%-tap/extended_error.test.lua:%d+: in main chunk$"
+
     local check_list = {
         type = 'CustomError',
         custom_type = 'My Custom Type',
@@ -144,8 +174,20 @@ local function test_extended_transmission(host, port)
         is_custom = true
     }
 
+    local check_list_2 = {
+        type = 'CustomError',
+        custom_type = 'My Custom Type',
+        message = 'User custom error: A modern custom error',
+        trace = '^File builtin/box/net_box.lua\nLine %d+$',
+        bt = backtrace_pattern_2,
+        is_custom = true
+    }
+
     local check_result = check_error(err, check_list)
-    test:ok(check_result, 'Check the extended transmission type')
+    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
 
@@ -162,6 +204,8 @@ if version_at_least(box.info.version, 2, 4, 1) then
 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)
 
-- 
2.7.4



More information about the Tarantool-patches mailing list