[Tarantool-patches] [PATCH V5 6/6] error: add error MsgPack encoding

Leonid Vasiliev lvasiliev at tarantool.org
Sat Apr 18 18:29:41 MSK 2020


Co-authored-by: Vladislav Shpilevoy<v.shpilevoy at tarantool.org>

Closes #4398

@TarantoolBot document
Title: msgpack format for MP_ERROR

For sent an error over IProto MP_ERROR(0x03) subtype
has been added to MsgPack MP_EXT.
Now, if session setting 'error_marshaling_enabled'
is true error will be encoded as:
    ```
        +--------+----------+========+
        | MP_EXT | MP_ERROR | MP_MAP |
        +--------+----------+========+
    ```

 MP_ERROR: <MP_MAP> {
     MP_ERROR_STACK: <MP_ARRAY> {
         <MP_MAP> {
             MP_ERROR_TYPE:   <MP_STR>,
             MP_ERROR_FILE:   <MP_STR>,
             MP_ERROR_LINE:   <MP_UINT>,
             MP_ERROR_REASON: <MP_STR>,
             MP_ERROR_ERRNO:  <MP_STR>,
             MP_ERROR_FIELDS: <MP_MAP> {
                 <MP_STR>: ...,
                 <MP_STR>: ...,
                 ...
             },
         },
     }
 }

MP_ERROR_STACK = 0x00

At the map passes all necessary error details (depending
from the error type) to create a copy of the error
on the client side.
The necessary fields:
MP_ERROR_TYPE   = 0x00 - error type(string).
MP_ERROR_FILE   = 0x01 - file name from trace.
MP_ERROR_LINE   = 0x02 - line of the file from trace.
MP_ERROR_REASON = 0x03 - error message.
MP_ERROR_ERRNO  = 0x04 - saved errno.
MP_ERROR_FIELDS = 0x05 - additional fields passes at MAP (MP_ERROR_FIELDS).
Now additional fields may content:
- ClientError code
- Custom type
- Access Denied object type
- Access Denied object name
- Access Denied access type
---
 src/box/CMakeLists.txt               |   1 +
 src/box/lua/init.c                   |  56 ++++
 src/box/lua/tuple.c                  |  16 -
 src/box/mp_error.cc                  | 552 +++++++++++++++++++++++++++++++++++
 src/box/mp_error.h                   |  60 ++++
 src/lib/core/mp_extension_types.h    |   1 +
 src/lua/error.c                      |   2 -
 src/lua/error.h                      |   3 +-
 src/lua/msgpack.c                    |   3 +
 src/lua/utils.c                      |   8 +-
 test/box-tap/extended_error.test.lua | 136 +++++++++
 11 files changed, 817 insertions(+), 21 deletions(-)
 create mode 100644 src/box/mp_error.cc
 create mode 100644 src/box/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 6688303..81f6400 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -136,6 +136,7 @@ add_library(box STATIC
     wal.c
     call.c
     merger.c
+    mp_error.cc
     ${lua_sources}
     lua/init.c
     lua/call.c
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 63e8b82..beb59fe 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -36,11 +36,14 @@
 
 #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/mp_error.h"
 
 #include "box/lua/error.h"
 #include "box/lua/tuple.h"
@@ -63,6 +66,8 @@
 #include "box/lua/key_def.h"
 #include "box/lua/merger.h"
 
+#include "mpstream/mpstream.h"
+
 static uint32_t CTID_STRUCT_TXN_SAVEPOINT_PTR = 0;
 
 extern char session_lua[],
@@ -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()->meta.serializer_opts.error_marshaling_enabled) {
+		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 not 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/tuple.c b/src/box/lua/tuple.c
index aba906d..03b4b8a 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -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
@@ -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/mp_error.cc b/src/box/mp_error.cc
new file mode 100644
index 0000000..348b7a7
--- /dev/null
+++ b/src/box/mp_error.cc
@@ -0,0 +1,552 @@
+/*
+ * 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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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/mp_error.h"
+#include "box/error.h"
+#include "mpstream/mpstream.h"
+#include "msgpuck.h"
+#include "mp_extension_types.h"
+#include "fiber.h"
+
+/**
+ * MP_ERROR format:
+ *
+ * MP_ERROR: <MP_MAP> {
+ *     MP_ERROR_STACK: <MP_ARRAY> {
+ *         <MP_MAP> {
+ *             MP_ERROR_TYPE:   <MP_STR>,
+ *             MP_ERROR_FILE:   <MP_STR>,
+ *             MP_ERROR_LINE:   <MP_UINT>,
+ *             MP_ERROR_REASON: <MP_STR>,
+ *             MP_ERROR_ERRNO:  <MP_STR>,
+ *             MP_ERROR_FIELDS: <MP_MAP> {
+ *                 <MP_STR>: ...,
+ *                 <MP_STR>: ...,
+ *                 ...
+ *             },
+ *         },
+ *     }
+ * }
+ */
+
+/**
+ * MP_ERROR keys
+ */
+enum {
+	MP_ERROR_STACK = 0x00
+};
+
+/**
+ * Keys used for error encode/decode to MP_ERROR.
+ */
+enum {
+	/* Error type */
+	MP_ERROR_TYPE = 0x00,
+	/* File name from trace */
+	MP_ERROR_FILE = 0x01,
+	/* Line from trace */
+	MP_ERROR_LINE = 0x02,
+	/* Error message */
+	MP_ERROR_REASON = 0x03,
+	/* Saved errno */
+	MP_ERROR_ERRNO = 0x04,
+	/*
+	 * Key uses for error type-specific fields which
+	 * presents here as map: string key - value.
+	 */
+	MP_ERROR_FIELDS = 0x05
+};
+
+/**
+ * The structure is used for storing parameters
+ * during decoding MP_ERROR.
+ */
+struct mp_error {
+	uint32_t error_code;
+	uint32_t line;
+	uint32_t saved_errno;
+	const char *error_type;
+	const char *file;
+	const char *reason;
+	const char *custom_type;
+	const char *ad_obj_type;
+	const char *ad_obj_name;
+	const char *ad_access_type;
+};
+
+static void
+mp_error_create(struct mp_error *mp_error)
+{
+	memset(mp_error, 0, sizeof(*mp_error));
+}
+
+static uint32_t
+encode_error_size(const struct error *error)
+{
+	uint32_t errcode = 0;
+
+	bool is_client = false;
+	bool is_custom = false;
+	bool is_access_denied = false;
+
+	if (strcmp(error->type->name, "ClientError") == 0) {
+		is_client = true;
+	} else if (strcmp(error->type->name, "CustomError") == 0) {
+		is_custom = true;
+	} else if (strcmp(error->type->name, "AccessDeniedError") == 0) {
+		is_access_denied = true;
+	}
+
+	uint32_t details_num = 5;
+	uint32_t data_size = 0;
+
+	data_size += mp_sizeof_uint(MP_ERROR_TYPE);
+	data_size += mp_sizeof_str(strlen(error->type->name));
+	data_size += mp_sizeof_uint(MP_ERROR_LINE);
+	data_size += mp_sizeof_uint(error->line);
+	data_size += mp_sizeof_uint(MP_ERROR_FILE);
+	data_size += mp_sizeof_str(strlen(error->file));
+	data_size += mp_sizeof_uint(MP_ERROR_REASON);
+	data_size += mp_sizeof_str(strlen(error->errmsg));
+	data_size += mp_sizeof_uint(MP_ERROR_ERRNO);
+	data_size += mp_sizeof_uint(error->saved_errno);
+
+	if (is_client) {
+		++details_num;
+		data_size += mp_sizeof_uint(MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		data_size += mp_sizeof_map(1);
+		data_size += mp_sizeof_str(strlen("code"));
+		data_size += mp_sizeof_uint(errcode);
+	} else if (is_access_denied) {
+		++details_num;
+		data_size += mp_sizeof_uint(MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		data_size += mp_sizeof_map(4);
+		data_size += mp_sizeof_str(strlen("code"));
+		data_size += mp_sizeof_uint(errcode);
+		AccessDeniedError *ad_err = type_cast(AccessDeniedError, error);
+		data_size += mp_sizeof_str(strlen("AD_OBJ_TYPE"));
+		data_size += mp_sizeof_str(strlen(ad_err->object_type()));
+		data_size += mp_sizeof_str(strlen("AD_OBJ_NAME"));
+		data_size += mp_sizeof_str(strlen(ad_err->object_name()));
+		data_size += mp_sizeof_str(strlen("AD_ACCESS_TYPE"));
+		data_size += mp_sizeof_str(strlen(ad_err->access_type()));
+	} else if (is_custom) {
+		++details_num;
+		data_size += mp_sizeof_uint(MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		data_size += mp_sizeof_map(2);
+		data_size += mp_sizeof_str(strlen("code"));
+		data_size += mp_sizeof_uint(errcode);
+		data_size += mp_sizeof_str(strlen("custom"));
+		data_size +=
+			mp_sizeof_str(strlen(box_error_custom_type(error)));
+	}
+
+	data_size += mp_sizeof_map(details_num);
+
+	return data_size;
+}
+
+static void
+encode_error(const struct error *error, char **data)
+{
+	uint32_t errcode = 0;
+
+	bool is_client = false;
+	bool is_custom = false;
+	bool is_access_denied = false;
+
+	if (strcmp(error->type->name, "ClientError") == 0) {
+		is_client = true;
+	} else if (strcmp(error->type->name, "CustomError") == 0) {
+		is_custom = true;
+	} else if (strcmp(error->type->name, "AccessDeniedError") == 0) {
+		is_access_denied = true;
+	}
+
+	uint32_t details_num = 5;
+
+	if (is_client || is_access_denied || is_custom)
+		++details_num;
+
+	*data = mp_encode_map(*data, details_num);
+	*data = mp_encode_uint(*data, MP_ERROR_TYPE);
+	*data = mp_encode_str(*data, error->type->name,
+			     strlen(error->type->name));
+	*data = mp_encode_uint(*data, MP_ERROR_LINE);
+	*data = mp_encode_uint(*data, error->line);
+	*data = mp_encode_uint(*data, MP_ERROR_FILE);
+	*data = mp_encode_str(*data, error->file, strlen(error->file));
+	*data = mp_encode_uint(*data, MP_ERROR_REASON);
+	*data = mp_encode_str(*data, error->errmsg, strlen(error->errmsg));
+	*data = mp_encode_uint(*data, MP_ERROR_ERRNO);
+	*data = mp_encode_uint(*data, error->saved_errno);
+
+	if (is_client) {
+		*data = mp_encode_uint(*data, MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		*data = mp_encode_map(*data, 1);
+		*data = mp_encode_str(*data, "code",strlen("code"));
+		*data = mp_encode_uint(*data, errcode);
+	} else if (is_access_denied) {
+		*data = mp_encode_uint(*data, MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		*data = mp_encode_map(*data, 4);
+		*data = mp_encode_str(*data, "code",strlen("code"));
+		*data = mp_encode_uint(*data, errcode);
+		AccessDeniedError *ad_err = type_cast(AccessDeniedError, error);
+		*data = mp_encode_str(*data, "AD_OBJ_TYPE",
+				      strlen("AD_OBJ_TYPE"));
+		*data = mp_encode_str(*data, ad_err->object_type(),
+				      strlen(ad_err->object_type()));
+		*data = mp_encode_str(*data, "AD_OBJ_NAME",
+				      strlen("AD_OBJ_NAME"));
+		*data = mp_encode_str(*data, ad_err->object_name(),
+				      strlen(ad_err->object_name()));
+		*data = mp_encode_str(*data, "AD_ACCESS_TYPE",
+				      strlen("AD_ACCESS_TYPE"));
+		*data = mp_encode_str(*data, ad_err->access_type(),
+				      strlen(ad_err->access_type()));
+	} else if (is_custom) {
+		*data = mp_encode_uint(*data, MP_ERROR_FIELDS);
+		errcode = box_error_code(error);
+		*data = mp_encode_map(*data, 2);
+		*data = mp_encode_str(*data, "code",strlen("code"));
+		*data = mp_encode_uint(*data, errcode);
+		*data = mp_encode_str(*data, "custom", strlen("custom"));
+		const char *custom = box_error_custom_type(error);
+		*data = mp_encode_str(*data, custom, strlen(custom));
+	}
+}
+
+static struct error *
+build_error_xc(struct mp_error *mp_error)
+{
+	/*
+	 * To create an error the "raw" constructor using
+	 * because OOM error must be thrown in OOM case.
+	 * Builders returns a pointer to the static OOM error
+	 * in OOM case.
+	 */
+	struct error *err = NULL;
+
+	if (strcmp(mp_error->error_type, "ClientError") == 0) {
+		ClientError *e = new ClientError(mp_error->file, mp_error->line,
+						 ER_UNKNOWN);
+		e->m_errcode = mp_error->error_code;
+		err = e;
+	} else if (strcmp(mp_error->error_type, "CustomError") == 0) {
+		err = new CustomError(mp_error->file, mp_error->line,
+				      mp_error->custom_type,
+				      mp_error->error_code);
+	} else if (strcmp(mp_error->error_type, "AccessDeniedError") == 0) {
+		err = new AccessDeniedError(mp_error->file, mp_error->line,
+					    mp_error->ad_access_type,
+					    mp_error->ad_obj_type,
+					    mp_error->ad_obj_name, "", false);
+	} else if (strcmp(mp_error->error_type, "XlogError") == 0) {
+		err = new XlogError(&type_XlogError, mp_error->file,
+				    mp_error->line);
+		error_format_msg(err, "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "XlogGapError") == 0) {
+		err = new XlogGapError(mp_error->file, mp_error->line,
+				       mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "SystemError") == 0) {
+		err = new SystemError(mp_error->file, mp_error->line,
+				      "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "SocketError") == 0) {
+		err = new SocketError(mp_error->file, mp_error->line, "", "");
+		error_format_msg(err, "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "OutOfMemory") == 0) {
+		err = new OutOfMemory(mp_error->file, mp_error->line,
+				      0, "", "");
+		error_format_msg(err, "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "TimedOut") == 0) {
+		err = new TimedOut(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->error_type, "ChannelIsClosed") == 0) {
+		err = new ChannelIsClosed(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->error_type, "FiberIsCancelled") == 0) {
+		err = new FiberIsCancelled(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->error_type, "LuajitError") == 0) {
+		err = new LuajitError(mp_error->file, mp_error->line,
+				      mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "IllegalParams") == 0) {
+		err = new IllegalParams(mp_error->file, mp_error->line,
+					"%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "CollationError") == 0) {
+		err = new CollationError(mp_error->file, mp_error->line,
+					 "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "SwimError") == 0) {
+		err = new SwimError(mp_error->file, mp_error->line,
+				    "%s", mp_error->reason);
+	} else if (strcmp(mp_error->error_type, "CryptoError") == 0) {
+		err = new CryptoError(mp_error->file, mp_error->line,
+				      "%s", mp_error->reason);
+	} else {
+		err = new ClientError(mp_error->file, mp_error->line,
+				      ER_UNKNOWN);
+	}
+
+	if (err) {
+		err->saved_errno = mp_error->saved_errno;
+		error_format_msg(err, "%s", mp_error->reason);
+	}
+
+	return err;
+}
+
+static inline const char *
+region_strdup(struct region *region, const char *str, uint32_t len)
+{
+	char *res = (char *)region_alloc(region, len + 1);
+	if (res == NULL) {
+		diag_set(OutOfMemory, len + 1, "region_alloc", "res");
+		return NULL;
+	}
+	memcpy(res, str, len);
+	res[len] = 0;
+	return res;
+}
+
+static int
+decode_additional_fields(const char **data, struct region *region,
+			 struct mp_error *mp_err)
+{
+	uint32_t map_sz = mp_decode_map(data);
+	const char *key;
+	uint32_t key_len;
+	const char *str;
+	uint32_t str_len;
+	for (uint32_t i = 0; i < map_sz; ++i) {
+		if (mp_typeof(**data) != MP_STR)
+			return -1;
+		key = mp_decode_str(data, &key_len);
+
+		if (strncmp(key, "code", key_len) == 0) {
+			if (mp_typeof(**data) != MP_UINT)
+				return -1;
+			mp_err->error_code = mp_decode_uint(data);
+		} else if (strncmp(key, "AD_OBJ_TYPE", key_len) == 0) {
+			if (mp_typeof(**data) != MP_STR)
+				return -1;
+			str = mp_decode_str(data, &str_len);
+			mp_err->ad_obj_type = region_strdup(region, str,
+							    str_len);
+			if (mp_err->ad_obj_type == NULL)
+				return -1;
+		} else if (strncmp(key, "AD_OBJ_NAME", key_len) == 0) {
+			if (mp_typeof(**data) != MP_STR)
+				return -1;
+			str = mp_decode_str(data, &str_len);
+			mp_err->ad_obj_name = region_strdup(region, str,
+							    str_len);
+			if (mp_err->ad_obj_name == NULL)
+				return -1;
+		} else if (strncmp(key, "AD_ACCESS_TYPE", key_len) == 0) {
+			if (mp_typeof(**data) != MP_STR)
+				return -1;
+			str = mp_decode_str(data, &str_len);
+			mp_err->ad_access_type = region_strdup(region, str,
+							       str_len);
+			if (mp_err->ad_access_type == NULL)
+				return -1;
+		} else if (strncmp(key, "custom", key_len) == 0) {
+			if (mp_typeof(**data) != MP_STR)
+				return -1;
+			str = mp_decode_str(data, &str_len);
+			mp_err->custom_type = region_strdup(region, str,
+							    str_len);
+			if (mp_err->custom_type == NULL)
+				return -1;
+		} else {
+			mp_next(data);
+		}
+	}
+	return 0;
+}
+
+static struct error *
+decode_error(const char **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_create(&mp_err);
+	struct region *region = &fiber()->gc;
+	uint32_t region_svp = region_used(region);
+	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");
+			goto finish;
+		}
+
+		uint64_t key = mp_decode_uint(data);
+		const char *str;
+		uint32_t str_len;
+		switch(key) {
+		case MP_ERROR_TYPE:
+			if (mp_typeof(**data) != MP_STR)
+				goto error;
+			str = mp_decode_str(data, &str_len);
+			mp_err.error_type = region_strdup(region, str, str_len);
+			if (mp_err.error_type == NULL)
+				goto finish;
+			break;
+		case MP_ERROR_FILE:
+			if (mp_typeof(**data) != MP_STR)
+				goto error;
+			str = mp_decode_str(data, &str_len);
+			mp_err.file = region_strdup(region, str, str_len);
+			if (mp_err.file == NULL)
+				goto finish;
+			break;
+		case MP_ERROR_LINE:
+			if (mp_typeof(**data) != MP_UINT)
+				goto error;
+			mp_err.line = mp_decode_uint(data);
+			break;
+		case MP_ERROR_REASON:
+			if (mp_typeof(**data) != MP_STR)
+				goto error;
+			str = mp_decode_str(data, &str_len);
+			mp_err.reason = region_strdup(region, str, str_len);
+			if (mp_err.reason == NULL)
+				goto finish;
+			break;
+		case MP_ERROR_ERRNO:
+			if (mp_typeof(**data) != MP_UINT)
+				goto error;
+			mp_err.saved_errno = mp_decode_uint(data);
+			break;
+		case MP_ERROR_FIELDS:
+			if (decode_additional_fields(data, region, &mp_err) != 0) {
+				goto error;
+			}
+			break;
+		default:
+			mp_next(data);
+		}
+	}
+
+	try {
+		err = build_error_xc(&mp_err);
+	} catch (OutOfMemory *e) {
+		diag_set_error(&fiber()->diag, e);
+	}
+finish:
+	region_truncate(region, region_svp);
+	return err;
+
+error:
+	diag_set(ClientError, ER_INVALID_MSGPACK,
+		 "Invalid MP_ERROR MsgPack format");
+	goto finish;
+}
+
+void
+error_to_mpstream(const struct error *error, struct mpstream *stream)
+{
+	uint32_t err_cnt = 0;
+	uint32_t data_size = mp_sizeof_map(1);
+	data_size += mp_sizeof_uint(MP_ERROR_STACK);
+	for (const struct error *it = error; it != NULL; it = it->cause) {
+		err_cnt++;
+		data_size += encode_error_size(it);
+	}
+
+	data_size += mp_sizeof_array(err_cnt);
+	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, 1);
+	data = mp_encode_uint(data, MP_ERROR_STACK);
+	data = mp_encode_array(data, err_cnt);
+	for (const struct error *it = error; it != NULL; it = it->cause) {
+		encode_error(it, &data);
+	}
+
+	assert(data == ptr + data_size_ext);
+	mpstream_advance(stream, data_size_ext);
+}
+
+struct error *
+error_unpack(const char **data, uint32_t len)
+{
+	const char *end = *data + len;
+	struct error *err = NULL;
+
+	uint32_t map_size = mp_decode_map(data);
+	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");
+			break;
+		}
+		uint64_t key = mp_decode_uint(data);
+		switch(key) {
+		case MP_ERROR_STACK:
+		{
+			uint32_t stack_sz = mp_decode_array(data);
+			struct error *effect = NULL;
+			for (uint32_t i = 0; i < stack_sz; i++) {
+				struct error *cur = decode_error(data);
+				if (cur == NULL)
+					return NULL;
+				if (err == NULL) {
+					err = cur;
+					effect = cur;
+					continue;
+				}
+
+				error_set_prev(effect, cur);
+				effect = cur;
+			}
+			break;
+		}
+		default:
+			mp_next(data);
+		}
+	}
+
+	(void)end;
+	assert(*data == end);
+	return err;
+}
diff --git a/src/box/mp_error.h b/src/box/mp_error.h
new file mode 100644
index 0000000..5c746d9
--- /dev/null
+++ b/src/box/mp_error.h
@@ -0,0 +1,60 @@
+#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 <COPYRIGHT HOLDER> ``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
+ * <COPYRIGHT HOLDER> 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;
+
+/**
+ * @brief Encode error to mpstream as MP_ERROR.
+ * @param error pointer to struct error for encoding.
+ * @param stream pointer to output stream.
+ */
+void
+error_to_mpstream(const struct error *error, struct mpstream *stream);
+
+/**
+ * @brief Unpack MP_ERROR to error.
+ * @param data pointer to MP_ERROR.
+ * @param len data size.
+ * @return struct error * or NULL if failed.
+ */
+struct error *
+error_unpack(const char **data, uint32_t len);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
diff --git a/src/lib/core/mp_extension_types.h b/src/lib/core/mp_extension_types.h
index 7d42f21..e3ff9f5 100644
--- a/src/lib/core/mp_extension_types.h
+++ b/src/lib/core/mp_extension_types.h
@@ -43,6 +43,7 @@ enum mp_extension_type {
     MP_UNKNOWN_EXTENSION = 0,
     MP_DECIMAL = 1,
     MP_UUID = 2,
+    MP_ERROR = 3,
     mp_extension_type_MAX,
 };
 
diff --git a/src/lua/error.c b/src/lua/error.c
index 18a990a..4a9ffc8 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -34,8 +34,6 @@
 #include <fiber.h>
 #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 b14361f..91560ef 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -195,6 +195,8 @@ restart: /* used by MP_EXT of unidentified subtype */
 		case MP_UUID:
 			mpstream_encode_uuid(stream, field->uuidval);
 			break;
+		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);
@@ -237,6 +239,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)
diff --git a/src/lua/utils.c b/src/lua/utils.c
index bd320f3..7d62c2d 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -37,6 +37,8 @@
 #include <diag.h>
 #include <fiber.h>
 
+#include "serializer_opts.h"
+
 int luaL_nil_ref = LUA_REFNIL;
 int luaL_map_metatable_ref = LUA_REFNIL;
 int luaL_array_metatable_ref = LUA_REFNIL;
@@ -47,6 +49,7 @@ static uint32_t CTID_CHAR_PTR;
 static uint32_t CTID_CONST_CHAR_PTR;
 static uint32_t CTID_UUID;
 uint32_t CTID_DECIMAL;
+uint32_t CTID_CONST_STRUCT_ERROR_REF;
 
 
 void *
@@ -650,8 +653,6 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 	     struct serializer_opts *opts, int index,
 	     struct luaL_field *field)
 {
-	/* opts will be used for encode MP_ERROR in the future */
-	(void)opts;
 	if (index < 0)
 		index = lua_gettop(L) + index + 1;
 
@@ -759,6 +760,9 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 			} else if (cd->ctypeid == CTID_UUID) {
 				field->ext_type = MP_UUID;
 				field->uuidval = (struct tt_uuid *) cdata;
+			} else if (cd->ctypeid == CTID_CONST_STRUCT_ERROR_REF &&
+				   opts && opts->error_marshaling_enabled) {
+				field->ext_type = MP_ERROR;
 			} else {
 				field->ext_type = MP_UNKNOWN_EXTENSION;
 			}
diff --git a/test/box-tap/extended_error.test.lua b/test/box-tap/extended_error.test.lua
new file mode 100755
index 0000000..bd60a02
--- /dev/null
+++ b/test/box-tap/extended_error.test.lua
@@ -0,0 +1,136 @@
+#! /usr/bin/env tarantool
+
+local netbox = require('net.box')
+local os = require('os')
+local tap = require('tap')
+
+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 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()
+    grant_func('guest', 'error_func')
+    grant_func('guest', 'custom_error_func_2')
+
+    local connection = netbox.connect(box.cfg.listen)
+    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()
+    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(box.cfg.listen)
+    connection.space._session_settings:update('error_marshaling_enabled',
+                                              {{'=', 2, 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 19$',
+        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')
+}
+
+test_extended_transmission()
+test_old_transmission()
+
+os.exit(test:check() and 0 or 1)
-- 
2.7.4



More information about the Tarantool-patches mailing list