[Tarantool-patches] [PATCH V6 06/10] error: add error MsgPack encoding

Leonid Vasiliev lvasiliev at tarantool.org
Mon Apr 20 01:25:08 MSK 2020


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

Closes #4398

@TarantoolBot document
Title: Error objects encoding in MessagePack

Until now an error sent over IProto, or serialized into
MessagePack was turned into a string consisting of the error
message. As a result, all other error object attributes were lost,
including type of the object. On client side seeing a string it
was not possible to tell whether the string is a real string, or
it is a serialized error.

To deal with that the error objects encoding is reworked from the
scratch. Now, when session setting `error_marshaling_enabled` is
true, all fibers of that session will encode error objects as a
new MP_EXT type - MP_ERROR (0x03).

```
    +--------+----------+========+
    | 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_MESSAGE: <MP_STR>,
                MP_ERROR_ERRNO: <MP_UINT>,
                MP_ERROR_CODE: <MP_UINT>,
                MP_ERROR_FIELDS: <MP_MAP> {
                    <MP_STR>: ...,
                    <MP_STR>: ...,
                    ...
                },
                ...
            },
            ...
        ]
    }
```

On the top level there is a single key: `MP_ERROR_STACK = 0x00`.
More keys can be added in future, and a client should ignore all
unknown keys to keep compatibility with new versions.

Every error in the stack is a map with the following keys:
* `MP_ERROR_TYPE = 0x00` - error type. This is what is visible in
  `<error_object>.base_type` field;
* `MP_ERROR_FILE = 0x01` - file name from `<error_object>.trace`;
* `MP_ERROR_LINE = 0x02` - line from `<error_object>.trace`;
* `MP_ERROR_MESSAGE = 0x03` - error message from
  `<error_object>.message`;
* `MP_ERROR_ERRNO = 0x04` - errno saved at the moment of the error
  creation. Visible in `<error_object>.errno`;
* `MP_ERROR_CODE = 0x05` - error code. Visible in
  `<error_object>.code` and in C function `box_error_code()`.
* `MP_ERROR_FIELDS = 0x06` - additional fields depending on error
  type. For example, AccessDenied error type stores here fields
  `access_type`, `object_type`, `object_name`. Connector's code
  should ignore unknown keys met here, and be ready, that for some
  existing errors new fields can be added, old can be dropped.
---
 src/box/CMakeLists.txt               |   6 +-
 src/box/lua/init.c                   |  36 +++
 src/box/mp_error.cc                  | 533 +++++++++++++++++++++++++++++++++++
 src/box/mp_error.h                   |  60 ++++
 src/lib/core/mp_extension_types.h    |   1 +
 src/lua/error.c                      |   2 +-
 src/lua/error.h                      |   1 +
 src/lua/msgpack.c                    |   2 +
 src/lua/utils.c                      |   8 +-
 test/box-tap/extended_error.test.lua | 124 ++++++++
 test/unit/CMakeLists.txt             |   2 +
 test/unit/mp_error.cc                | 473 +++++++++++++++++++++++++++++++
 test/unit/mp_error.result            |  44 +++
 13 files changed, 1286 insertions(+), 6 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
 create mode 100644 test/unit/mp_error.cc
 create mode 100644 test/unit/mp_error.result

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 5ed7eae..6ca540d 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -34,15 +34,15 @@ set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${lua_sources})
 include_directories(${ZSTD_INCLUDE_DIRS})
 include_directories(${CMAKE_BINARY_DIR}/src/box/sql)
 
-add_library(box_error STATIC error.cc errcode.c vclock.c)
-target_link_libraries(box_error core stat)
+add_library(box_error STATIC error.cc errcode.c vclock.c mp_error.cc)
+target_link_libraries(box_error core stat mpstream)
 
 add_library(vclock STATIC vclock.c)
 target_link_libraries(vclock core)
 
 add_library(xrow STATIC xrow.c iproto_constants.c)
 target_link_libraries(xrow server core small vclock misc box_error
-                      scramble mpstream ${MSGPUCK_LIBRARIES})
+                      scramble ${MSGPUCK_LIBRARIES})
 
 add_library(tuple STATIC
     tuple.c
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 7899c16..cac62b9 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -42,6 +42,8 @@
 #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"
@@ -64,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[],
@@ -408,9 +412,40 @@ luamp_encode_extension_box(struct lua_State *L, int idx,
 		tuple_to_mpstream(tuple, stream);
 		return MP_ARRAY;
 	}
+	struct error *err = luaL_iserror(L, idx);
+	struct serializer_opts *opts = &current_session()->meta.serializer_opts;
+	if (err != NULL && opts->error_marshaling_enabled)
+		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
@@ -452,6 +487,7 @@ box_lua_init(struct lua_State *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) {
diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc
new file mode 100644
index 0000000..e7d2808
--- /dev/null
+++ b/src/box/mp_error.cc
@@ -0,0 +1,533 @@
+/*
+ * 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_MESSAGE: <MP_STR>,
+ *             MP_ERROR_ERRNO: <MP_UINT>,
+ *             MP_ERROR_CODE: <MP_UINT>,
+ *             MP_ERROR_FIELDS: <MP_MAP> {
+ *                 <MP_STR>: ...,
+ *                 <MP_STR>: ...,
+ *                 ...
+ *             },
+ *             ...
+ *         },
+ *         ...
+ *     ]
+ * }
+ */
+
+/**
+ * MP_ERROR keys
+ */
+enum {
+	MP_ERROR_STACK = 0x00
+};
+
+/**
+ * Keys of individual error in the stack.
+ */
+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_MESSAGE = 0x03,
+	/** Errno at the moment of error creation. */
+	MP_ERROR_ERRNO = 0x04,
+	/** Error code. */
+	MP_ERROR_CODE = 0x05,
+	/*
+	 * Type-specific fields stored as a map
+	 * {string key = value}.
+	 */
+	MP_ERROR_FIELDS = 0x06
+};
+
+/**
+ * The structure is used for storing parameters
+ * during decoding MP_ERROR.
+ */
+struct mp_error {
+	uint32_t code;
+	uint32_t line;
+	uint32_t saved_errno;
+	const char *type;
+	const char *file;
+	const char *message;
+	const char *custom_type;
+	const char *ad_object_type;
+	const char *ad_object_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
+mp_sizeof_error(const struct error *error)
+{
+	uint32_t errcode = box_error_code(error);
+
+	bool is_custom = false;
+	bool is_access_denied = false;
+
+	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 = 6;
+	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_MESSAGE);
+	data_size += mp_sizeof_str(strlen(error->errmsg));
+	data_size += mp_sizeof_uint(MP_ERROR_ERRNO);
+	data_size += mp_sizeof_uint(error->saved_errno);
+	data_size += mp_sizeof_uint(MP_ERROR_CODE);
+	data_size += mp_sizeof_uint(errcode);
+
+	if (is_access_denied) {
+		++details_num;
+		data_size += mp_sizeof_uint(MP_ERROR_FIELDS);
+		data_size += mp_sizeof_map(3);
+		AccessDeniedError *ad_err = type_cast(AccessDeniedError, error);
+		data_size += mp_sizeof_str(strlen("object_type"));
+		data_size += mp_sizeof_str(strlen(ad_err->object_type()));
+		data_size += mp_sizeof_str(strlen("object_name"));
+		data_size += mp_sizeof_str(strlen(ad_err->object_name()));
+		data_size += mp_sizeof_str(strlen("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);
+		data_size += mp_sizeof_map(1);
+		data_size += mp_sizeof_str(strlen("custom_type"));
+		data_size +=
+			mp_sizeof_str(strlen(box_error_custom_type(error)));
+	}
+
+	data_size += mp_sizeof_map(details_num);
+
+	return data_size;
+}
+
+static inline char *
+mp_encode_str0(char *data, const char *str)
+{
+	return mp_encode_str(data, str, strlen(str));
+}
+
+static void
+mp_encode_error_one(const struct error *error, char **data)
+{
+	uint32_t errcode = box_error_code(error);
+
+	bool is_custom = false;
+	bool is_access_denied = false;
+
+	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 = 6;
+	if (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_str0(*data, 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_str0(*data, error->file);
+	*data = mp_encode_uint(*data, MP_ERROR_MESSAGE);
+	*data = mp_encode_str0(*data, error->errmsg);
+	*data = mp_encode_uint(*data, MP_ERROR_ERRNO);
+	*data = mp_encode_uint(*data, error->saved_errno);
+	*data = mp_encode_uint(*data, MP_ERROR_CODE);
+	*data = mp_encode_uint(*data, errcode);
+
+	if (is_access_denied) {
+		*data = mp_encode_uint(*data, MP_ERROR_FIELDS);
+		*data = mp_encode_map(*data, 3);
+		AccessDeniedError *ad_err = type_cast(AccessDeniedError, error);
+		*data = mp_encode_str0(*data, "object_type");
+		*data = mp_encode_str0(*data, ad_err->object_type());
+		*data = mp_encode_str0(*data, "object_name");
+		*data = mp_encode_str0(*data, ad_err->object_name());
+		*data = mp_encode_str0(*data, "access_type");
+		*data = mp_encode_str0(*data, ad_err->access_type());
+	} else if (is_custom) {
+		*data = mp_encode_uint(*data, MP_ERROR_FIELDS);
+		*data = mp_encode_map(*data, 1);
+		*data = mp_encode_str0(*data, "custom_type");
+		*data = mp_encode_str0(*data, box_error_custom_type(error));
+	}
+}
+
+static struct error *
+error_build_xc(struct mp_error *mp_error)
+{
+	/*
+	 * To create an error the "raw" constructor is used
+	 * 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 (mp_error->type == NULL || mp_error->message == NULL ||
+	    mp_error->file == NULL) {
+missing_fields:
+		diag_set(ClientError, ER_INVALID_MSGPACK,
+			 "Missing mandatory error fields");
+		return NULL;
+	}
+
+	if (strcmp(mp_error->type, "ClientError") == 0) {
+		ClientError *e = new ClientError(mp_error->file, mp_error->line,
+						 ER_UNKNOWN);
+		e->m_errcode = mp_error->code;
+		err = e;
+	} else if (strcmp(mp_error->type, "CustomError") == 0) {
+		if (mp_error->custom_type == NULL)
+			goto missing_fields;
+		err = new CustomError(mp_error->file, mp_error->line,
+				      mp_error->custom_type, mp_error->code);
+	} else if (strcmp(mp_error->type, "AccessDeniedError") == 0) {
+		if (mp_error->ad_access_type == NULL ||
+		    mp_error->ad_object_type == NULL ||
+		    mp_error->ad_object_name == NULL)
+			goto missing_fields;
+		err = new AccessDeniedError(mp_error->file, mp_error->line,
+					    mp_error->ad_access_type,
+					    mp_error->ad_object_type,
+					    mp_error->ad_object_name, "",
+					    false);
+	} else if (strcmp(mp_error->type, "XlogError") == 0) {
+		err = new XlogError(&type_XlogError, mp_error->file,
+				    mp_error->line);
+	} else if (strcmp(mp_error->type, "XlogGapError") == 0) {
+		err = new XlogGapError(mp_error->file, mp_error->line,
+				       mp_error->message);
+	} else if (strcmp(mp_error->type, "SystemError") == 0) {
+		err = new SystemError(mp_error->file, mp_error->line,
+				      "%s", mp_error->message);
+	} else if (strcmp(mp_error->type, "SocketError") == 0) {
+		err = new SocketError(mp_error->file, mp_error->line, "", "");
+		error_format_msg(err, "%s", mp_error->message);
+	} else if (strcmp(mp_error->type, "OutOfMemory") == 0) {
+		err = new OutOfMemory(mp_error->file, mp_error->line,
+				      0, "", "");
+	} else if (strcmp(mp_error->type, "TimedOut") == 0) {
+		err = new TimedOut(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->type, "ChannelIsClosed") == 0) {
+		err = new ChannelIsClosed(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->type, "FiberIsCancelled") == 0) {
+		err = new FiberIsCancelled(mp_error->file, mp_error->line);
+	} else if (strcmp(mp_error->type, "LuajitError") == 0) {
+		err = new LuajitError(mp_error->file, mp_error->line,
+				      mp_error->message);
+	} else if (strcmp(mp_error->type, "IllegalParams") == 0) {
+		err = new IllegalParams(mp_error->file, mp_error->line,
+					"%s", mp_error->message);
+	} else if (strcmp(mp_error->type, "CollationError") == 0) {
+		err = new CollationError(mp_error->file, mp_error->line,
+					 "%s", mp_error->message);
+	} else if (strcmp(mp_error->type, "SwimError") == 0) {
+		err = new SwimError(mp_error->file, mp_error->line,
+				    "%s", mp_error->message);
+	} else if (strcmp(mp_error->type, "CryptoError") == 0) {
+		err = new CryptoError(mp_error->file, mp_error->line,
+				      "%s", mp_error->message);
+	} else {
+		err = new ClientError(mp_error->file, mp_error->line,
+				      ER_UNKNOWN);
+	}
+	err->saved_errno = mp_error->saved_errno;
+	error_format_msg(err, "%s", mp_error->message);
+	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 inline const char *
+mp_decode_and_copy_str(const char **data, struct region *region)
+{
+	if (mp_typeof(**data) != MP_STR) {
+		diag_set(ClientError, ER_INVALID_MSGPACK,
+			 "Invalid MP_ERROR MsgPack format");
+		return NULL;
+	}
+	uint32_t str_len;
+	const char *str = mp_decode_str(data, &str_len);
+	return region_strdup(region, str, str_len);;
+}
+
+static inline bool
+str_nonterm_is_eq(const char *l, const char *r, uint32_t r_len)
+{
+	return r_len == strlen(l) && memcmp(l, r, r_len) == 0;
+}
+
+static int
+mp_decode_error_fields(const char **data, struct mp_error *mp_err,
+		       struct region *region)
+{
+	if (mp_typeof(**data) != MP_MAP)
+		return -1;
+	uint32_t map_sz = mp_decode_map(data);
+	const char *key;
+	uint32_t key_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 (str_nonterm_is_eq("object_type", key, key_len)) {
+			mp_err->ad_object_type =
+				mp_decode_and_copy_str(data, region);
+			if (mp_err->ad_object_type == NULL)
+				return -1;
+		} else if (str_nonterm_is_eq("object_name", key, key_len)) {
+			mp_err->ad_object_name =
+				mp_decode_and_copy_str(data, region);
+			if (mp_err->ad_object_name == NULL)
+				return -1;
+		} else if (str_nonterm_is_eq("access_type", key, key_len)) {
+			mp_err->ad_access_type =
+				mp_decode_and_copy_str(data, region);
+			if (mp_err->ad_access_type == NULL)
+				return -1;
+		} else if (str_nonterm_is_eq("custom_type", key, key_len)) {
+			mp_err->custom_type =
+				mp_decode_and_copy_str(data, region);
+			if (mp_err->custom_type == NULL)
+				return -1;
+		} else {
+			mp_next(data);
+		}
+	}
+	return 0;
+}
+
+static struct error *
+mp_decode_error_one(const char **data)
+{
+	struct mp_error mp_err;
+	mp_error_create(&mp_err);
+	struct region *region = &fiber()->gc;
+	uint32_t region_svp = region_used(region);
+	struct error *err = NULL;
+	uint32_t map_size;
+
+	if (mp_typeof(**data) != MP_MAP)
+		goto error;
+
+	map_size = mp_decode_map(data);
+	for (uint32_t i = 0; i < map_size; ++i) {
+		if (mp_typeof(**data) != MP_UINT)
+			goto error;
+
+		uint64_t key = mp_decode_uint(data);
+		switch(key) {
+		case MP_ERROR_TYPE:
+			mp_err.type = mp_decode_and_copy_str(data, region);
+			if (mp_err.type == NULL)
+				goto finish;
+			break;
+		case MP_ERROR_FILE:
+			mp_err.file = mp_decode_and_copy_str(data, region);
+			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_MESSAGE:
+			mp_err.message = mp_decode_and_copy_str(data, region);
+			if (mp_err.message == 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_CODE:
+			if (mp_typeof(**data) != MP_UINT)
+				goto error;
+			mp_err.code = mp_decode_uint(data);
+			break;
+		case MP_ERROR_FIELDS:
+			if (mp_decode_error_fields(data, &mp_err, region) != 0)
+				goto finish;
+			break;
+		default:
+			mp_next(data);
+		}
+	}
+
+	try {
+		err = error_build_xc(&mp_err);
+	} catch (OutOfMemory *e) {
+		assert(err == NULL && !diag_is_empty(diag_get()));
+	}
+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 += mp_sizeof_error(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) {
+		mp_encode_error_one(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;
+
+	if (mp_typeof(**data) != MP_MAP) {
+		diag_set(ClientError, ER_INVALID_MSGPACK,
+			 "Invalid MP_ERROR MsgPack format");
+		return 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");
+			return NULL;
+		}
+		uint64_t key = mp_decode_uint(data);
+		switch(key) {
+		case MP_ERROR_STACK: {
+			if (mp_typeof(**data) != MP_ARRAY) {
+				diag_set(ClientError, ER_INVALID_MSGPACK,
+					 "Invalid MP_ERROR MsgPack format");
+				return NULL;
+			}
+			uint32_t stack_sz = mp_decode_array(data);
+			struct error *effect = NULL;
+			for (uint32_t i = 0; i < stack_sz; i++) {
+				struct error *cur = mp_decode_error_one(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..616aa7f 100644
--- a/src/lua/error.c
+++ b/src/lua/error.c
@@ -34,7 +34,7 @@
 #include <fiber.h>
 #include "utils.h"
 
-static int CTID_CONST_STRUCT_ERROR_REF = 0;
+uint32_t 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..54635bd 100644
--- a/src/lua/error.h
+++ b/src/lua/error.h
@@ -37,6 +37,7 @@
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+extern uint32_t CTID_CONST_STRUCT_ERROR_REF;
 
 /** \cond public */
 struct error;
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index 5c1bf8e..d482c9b 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);
diff --git a/src/lua/utils.c b/src/lua/utils.c
index 42a226c..d410a3d 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;
@@ -650,8 +652,6 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 	     const 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 +759,10 @@ 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 != NULL &&
+				   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..1681419
--- /dev/null
+++ b/test/box-tap/extended_error.test.lua
@@ -0,0 +1,124 @@
+#! /usr/bin/env tarantool
+
+local netbox = require('net.box')
+local os = require('os')
+local tap = require('tap')
+
+box.cfg{
+    listen = os.getenv('LISTEN')
+}
+
+--
+-- gh-4398: error objects after transmission through network
+-- should not loose their fields and type.
+--
+
+-- Create AccessDeniedError. It is going to be used in the tests
+-- below.
+function forbidden_function()
+    return nil
+end
+local user = box.session.user()
+box.session.su('admin')
+box.schema.func.create('forbidden_function')
+box.session.su('guest')
+local tmp = box.func.forbidden_function
+local access_denied_error
+tmp, access_denied_error = pcall(tmp.call, tmp)
+box.session.su('admin')
+box.schema.func.drop('forbidden_function')
+box.session.su(user)
+
+local test = tap.test('Error marshaling')
+test:plan(6)
+
+function error_new(...)
+    return box.error.new(...)
+end
+
+function error_new_stacked(args1, args2)
+    local e1 = box.error.new(args1)
+    local e2 = box.error.new(args2)
+    e1:set_prev(e2)
+    return e1
+end
+
+function error_access_denied()
+    return access_denied_error
+end
+
+local function check_error(err, check_list)
+    assert(type(check_list) == 'table')
+    if type(err.trace) ~= 'table' or err.trace[1] == nil or
+       err.trace[1].file == nil or err.trace[1].line == nil then
+        return false
+    end
+    for k, v in pairs(check_list) do
+        if err[k] ~= v then
+            return false
+        end
+    end
+    return true
+end
+
+box.schema.user.grant('guest', 'super')
+local c = netbox.connect(box.cfg.listen)
+c:eval('box.session.settings.error_marshaling_enabled = true')
+local err = c:call('error_new', {{code = 1000, reason = 'Reason'}})
+local checks = {
+    code = 1000,
+    message = 'Reason',
+    base_type = 'ClientError',
+    type = 'ClientError',
+}
+test:ok(check_error(err, checks), "ClientError marshaling")
+
+err = c:call('error_new', {{
+    code = 1001, reason = 'Reason2', type = 'MyError'
+}})
+checks = {
+    code = 1001,
+    message = 'Reason2',
+    base_type = 'CustomError',
+    type = 'MyError',
+}
+test:ok(check_error(err, checks), "CustomError marshaling")
+
+err = c:call('error_access_denied')
+checks = {
+    code = 42,
+    type = 'AccessDeniedError',
+    base_type = 'AccessDeniedError',
+    message = "Execute access to function 'forbidden_function' is denied for user 'guest'",
+    object_type = 'function',
+    object_name = 'forbidden_function',
+    access_type = 'Execute',
+}
+test:ok(check_error(err, checks), "AccessDeniedError marshaling")
+
+err = c:call('error_new_stacked', {
+    {code = 1003, reason = 'Reason3', type = 'MyError2'},
+    {code = 1004, reason = 'Reason4'}
+})
+local err1 = err
+local err2 = err.prev
+test:isnt(err2, nil, 'Stack is received')
+checks = {
+    code = 1003,
+    message = 'Reason3',
+    base_type = 'CustomError',
+    type = 'MyError2'
+}
+test:ok(check_error(err1, checks), "First error in the stack")
+checks = {
+    code = 1004,
+    message = 'Reason4',
+    base_type = 'ClientError',
+    type = 'ClientError'
+}
+test:ok(check_error(err2, checks), "Second error in the stack")
+
+c:close()
+box.schema.user.revoke('guest', 'super')
+
+os.exit(test:check() and 0 or 1)
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index e1d506f..24586c2 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -69,6 +69,8 @@ add_executable(xrow.test xrow.cc)
 target_link_libraries(xrow.test xrow unit)
 add_executable(decimal.test decimal.c)
 target_link_libraries(decimal.test core unit)
+add_executable(mp_error.test mp_error.cc)
+target_link_libraries(mp_error.test box_error core unit)
 
 add_executable(fiber.test fiber.cc)
 set_source_files_properties(fiber.cc PROPERTIES COMPILE_FLAGS -O0)
diff --git a/test/unit/mp_error.cc b/test/unit/mp_error.cc
new file mode 100644
index 0000000..e02deac
--- /dev/null
+++ b/test/unit/mp_error.cc
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and iproto 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 iproto 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 "unit.h"
+#include "exception.h"
+#include "fiber.h"
+#include "memory.h"
+#include "msgpuck.h"
+#include "tt_static.h"
+
+#include "box/error.h"
+#include "box/mp_error.h"
+
+enum {
+	MP_ERROR_STACK = 0x00
+};
+
+enum {
+	MP_ERROR_TYPE = 0x00,
+	MP_ERROR_FILE = 0x01,
+	MP_ERROR_LINE = 0x02,
+	MP_ERROR_MESSAGE = 0x03,
+	MP_ERROR_ERRNO = 0x04,
+	MP_ERROR_CODE = 0x05,
+	MP_ERROR_FIELDS = 0x06
+};
+
+struct mp_error {
+	uint32_t code;
+	uint32_t line;
+	int32_t saved_errno;
+	uint32_t unknown_uint_field;
+	const char *type;
+	const char *file;
+	const char *message;
+	const char *custom_type;
+	const char *ad_object_type;
+	const char *ad_object_name;
+	const char *ad_access_type;
+	const char *unknown_str_field;
+};
+
+const char *standard_errors[] = {
+	"XlogError",
+	"XlogGapError",
+	"SystemError",
+	"SocketError",
+	"OutOfMemory",
+	"TimedOut",
+	"ChannelIsClosed",
+	"FiberIsCancelled",
+	"LuajitError",
+	"IllegalParams",
+	"CollationError",
+	"SwimError",
+	"CryptoError",
+};
+
+enum {
+	TEST_STANDARD_ERRORS_NUM =
+		sizeof(standard_errors) / sizeof(standard_errors[0]),
+};
+
+static inline char *
+mp_encode_str0(char *data, const char *str)
+{
+	return mp_encode_str(data, str, strlen(str));
+}
+
+/** Note, not the same as mp_encode_error(). */
+static char *
+mp_encode_mp_error(const struct mp_error *e, char *data)
+{
+	uint32_t fields_num = 6;
+	int field_count = (e->custom_type != NULL) +
+			  (e->ad_object_type != NULL) +
+			  (e->ad_object_name != NULL) +
+			  (e->ad_access_type != NULL) +
+			  (e->unknown_str_field != NULL);
+	fields_num += (field_count != 0);
+	fields_num += (e->unknown_uint_field != 0);
+
+	data = mp_encode_map(data, fields_num);
+	data = mp_encode_uint(data, MP_ERROR_TYPE);
+	data = mp_encode_str0(data, e->type);
+	data = mp_encode_uint(data, MP_ERROR_FILE);
+	data = mp_encode_str0(data, e->file);
+	data = mp_encode_uint(data, MP_ERROR_LINE);
+	data = mp_encode_uint(data, e->line);
+	data = mp_encode_uint(data, MP_ERROR_MESSAGE);
+	data = mp_encode_str0(data, e->message);
+	data = mp_encode_uint(data, MP_ERROR_ERRNO);
+	data = mp_encode_uint(data, e->saved_errno);
+	data = mp_encode_uint(data, MP_ERROR_CODE);
+	data = mp_encode_uint(data, e->code);
+	if (e->unknown_uint_field != 0) {
+		data = mp_encode_uint(data, UINT64_MAX);
+		data = mp_encode_uint(data, e->unknown_uint_field);
+	}
+	if (field_count != 0) {
+		data = mp_encode_uint(data, MP_ERROR_FIELDS);
+		data = mp_encode_map(data, field_count);
+		if (e->custom_type != NULL) {
+			data = mp_encode_str0(data, "custom_type");
+			data = mp_encode_str0(data, e->custom_type);
+		}
+		if (e->ad_object_type != NULL) {
+			data = mp_encode_str0(data, "object_type");
+			data = mp_encode_str0(data, e->ad_object_type);
+		}
+		if (e->ad_object_name != NULL) {
+			data = mp_encode_str0(data, "object_name");
+			data = mp_encode_str0(data, e->ad_object_name);
+		}
+		if (e->ad_access_type != NULL) {
+			data = mp_encode_str0(data, "access_type");
+			data = mp_encode_str0(data, e->ad_access_type);
+		}
+		if (e->unknown_str_field != NULL) {
+			data = mp_encode_str0(data, "unknown_field");
+			data = mp_encode_str0(data, e->unknown_str_field);
+		}
+	}
+	return data;
+}
+
+static char *
+mp_encode_error_header(char *data, int stack_size)
+{
+	data = mp_encode_map(data, 1);
+	data = mp_encode_uint(data, MP_ERROR_STACK);
+	data = mp_encode_array(data, stack_size);
+	return data;
+}
+
+static char *
+mp_encode_test_error_stack(char *data)
+{
+	data = mp_encode_error_header(data, TEST_STANDARD_ERRORS_NUM + 3);
+	/*
+	 * CustomError
+	 */
+	struct mp_error err;
+	memset(&err, 0, sizeof(err));
+	err.code = 123;
+	err.line = 1;
+	err.saved_errno = 2;
+	err.type = "CustomError";
+	err.file = "File1";
+	err.message = "Message1";
+	err.custom_type = "MyType";
+	data = mp_encode_mp_error(&err, data);
+	/*
+	 * AccessDeniedError
+	 */
+	memset(&err, 0, sizeof(err));
+	err.code = 42;
+	err.line = 3;
+	err.saved_errno = 4;
+	err.type = "AccessDeniedError";
+	err.file = "File2";
+	err.message = "Message2";
+	err.ad_object_type = "ObjectType";
+	err.ad_object_name = "ObjectName";
+	err.ad_access_type = "AccessType";
+	data = mp_encode_mp_error(&err, data);
+	/*
+	 * ClientError
+	 */
+	memset(&err, 0, sizeof(err));
+	err.code = 123;
+	err.line = 5;
+	err.saved_errno = 6;
+	err.type = "ClientError";
+	err.file = "File3";
+	err.message = "Message4";
+	data = mp_encode_mp_error(&err, data);
+
+	/*
+	 * All errors with standard fields only.
+	 */
+	for (uint8_t i = 0; i < TEST_STANDARD_ERRORS_NUM; ++i) {
+		memset(&err, 0, sizeof(err));
+		err.code = i;
+		err.line = i;
+		err.saved_errno = i;
+		err.type = standard_errors[i];
+		err.file = tt_sprintf("File%d", i);
+		err.message = tt_sprintf("Message%d", i);
+		data = mp_encode_mp_error(&err, data);
+	}
+
+	return data;
+}
+
+static bool
+error_is_eq_mp_error(struct error *err, struct mp_error *check)
+{
+	if (err->saved_errno != check->saved_errno)
+		return false;
+	if (strcmp(err->type->name, check->type) != 0)
+		return false;
+	if (strcmp(err->file, check->file) != 0)
+		return false;
+	if (err->line != check->line)
+		return false;
+	if (strcmp(err->errmsg, check->message) != 0)
+		return false;
+
+	if (strcmp(check->type, "ClientError") == 0) {
+		if (box_error_code(err) != check->code)
+			return false;
+	} else if (strcmp(check->type, "CustomError") == 0) {
+		CustomError *cust_err = type_cast(CustomError, err);
+		if (box_error_code(err) != check->code ||
+		    strcmp(cust_err->custom_type(), check->custom_type) != 0)
+			return false;
+	} else if (strcmp(check->type, "AccessDeniedError") == 0) {
+		AccessDeniedError *ad_err = type_cast(AccessDeniedError, err);
+		if (box_error_code(err) != check->code ||
+		    strcmp(ad_err->access_type(), check->ad_access_type) != 0 ||
+		    strcmp(ad_err->object_name(), check->ad_object_name) != 0 ||
+		    strcmp(ad_err->object_type(), check->ad_object_type) != 0)
+			return false;
+	}
+	return true;
+}
+
+void
+test_stack_error_decode()
+{
+	header();
+	plan(TEST_STANDARD_ERRORS_NUM + 4);
+
+	char buffer[2048];
+	memset(buffer, 0, sizeof(buffer));
+	char *end = mp_encode_test_error_stack(buffer);
+
+	uint32_t len = end - buffer;
+	const char *pos = buffer;
+	struct error *err1 = error_unpack(&pos, len);
+	error_ref(err1);
+	struct error *err2 = err1->cause;
+	struct error *err3 = err2->cause;
+
+	struct mp_error check;
+	memset(&check, 0, sizeof(check));
+	check.code = 123;
+	check.line = 1;
+	check.saved_errno = 2;
+	check.type = "CustomError";
+	check.file = "File1";
+	check.message = "Message1";
+	check.custom_type = "MyType";
+	ok(error_is_eq_mp_error(err1, &check), "check CustomError");
+
+	memset(&check, 0, sizeof(check));
+	check.code = 42;
+	check.line = 3;
+	check.saved_errno = 4;
+	check.type = "AccessDeniedError";
+	check.file = "File2";
+	check.message = "Message2";
+	check.ad_object_type = "ObjectType";
+	check.ad_object_name = "ObjectName";
+	check.ad_access_type = "AccessType";
+	ok(error_is_eq_mp_error(err2, &check), "check AccessDeniedError");
+
+	memset(&check, 0, sizeof(check));
+	check.code = 123;
+	check.line = 5;
+	check.saved_errno = 6;
+	check.type = "ClientError";
+	check.file = "File3";
+	check.message = "Message4";
+	ok(error_is_eq_mp_error(err3, &check), "check ClientError");
+
+	struct error *cur_err = err3;
+	int i = 0;
+	while(cur_err->cause) {
+		cur_err = cur_err->cause;
+		memset(&check, 0, sizeof(check));
+		check.code = i;
+		check.line = i;
+		check.saved_errno = i;
+		check.type = standard_errors[i];
+		check.file = tt_sprintf("File%d", i);
+		check.message = tt_sprintf("Message%d", i);
+		ok(error_is_eq_mp_error(cur_err, &check), "check %s",
+					standard_errors[i]);
+		++i;
+	}
+	is(i, TEST_STANDARD_ERRORS_NUM, "stack size");
+	error_unref(err1);
+	check_plan();
+	footer();
+}
+
+void
+test_decode_unknown_type()
+{
+	header();
+	plan(1);
+	char buffer[2048];
+	memset(buffer, 0, sizeof(buffer));
+
+	char *data = mp_encode_error_header(buffer, 1);
+	struct mp_error err;
+	memset(&err, 0, sizeof(err));
+	err.code = 1;
+	err.line = 2;
+	err.saved_errno = 3;
+	err.type = "SomeNewError";
+	err.file = "File1";
+	err.message = "Message1";
+	data = mp_encode_mp_error(&err, data);
+
+	uint32_t len = data - buffer;
+	const char *pos = buffer;
+	struct error *unpacked = error_unpack(&pos, len);
+	error_ref(unpacked);
+	err.code = 0;
+	err.type = "ClientError";
+	ok(error_is_eq_mp_error(unpacked, &err), "check SomeNewError");
+	error_unref(unpacked);
+
+	check_plan();
+	footer();
+}
+
+void
+test_fail_not_enough_fields()
+{
+	header();
+	plan(2);
+	char buffer[2048];
+	memset(buffer, 0, sizeof(buffer));
+
+	char *data = mp_encode_error_header(buffer, 1);
+	struct mp_error err;
+	memset(&err, 0, sizeof(err));
+	err.code = 42;
+	err.line = 3;
+	err.saved_errno = 4;
+	err.type = "AccessDeniedError";
+	err.file = "File1";
+	err.message = "Message1";
+	err.ad_object_type = "ObjectType";
+	err.ad_access_type = "AccessType";
+	data = mp_encode_mp_error(&err, data);
+
+	uint32_t len = data - buffer;
+	const char *pos = buffer;
+	struct error *unpacked = error_unpack(&pos, len);
+
+	is(unpacked, NULL, "check not enough additional fields");
+	ok(!diag_is_empty(diag_get()), "error about parsing problem is set");
+	check_plan();
+	footer();
+}
+
+void
+test_unknown_fields()
+{
+	header();
+	plan(1);
+	char buffer[2048];
+	memset(buffer, 0, sizeof(buffer));
+
+	char *data = mp_encode_error_header(buffer, 1);
+	struct mp_error err;
+	memset(&err, 0, sizeof(err));
+	err.code = 0;
+	err.line = 1;
+	err.saved_errno = 0;
+	err.type = "SystemError";
+	err.file = "File";
+	err.message = "Message";
+	err.unknown_uint_field = 55;
+	data = mp_encode_mp_error(&err, data);
+
+	uint32_t len = data - buffer;
+	const char *pos = buffer;
+	struct error *unpacked = error_unpack(&pos, len);
+	error_ref(unpacked);
+	data = mp_encode_mp_error(&err, data);
+
+	ok(error_is_eq_mp_error(unpacked, &err), "check unknown fields");
+	error_unref(unpacked);
+	check_plan();
+}
+
+void
+test_unknown_additional_fields()
+{
+	header();
+	plan(1);
+	char buffer[2048];
+	memset(buffer, 0, sizeof(buffer));
+
+	char *data = mp_encode_error_header(buffer, 1);
+	struct mp_error err;
+	memset(&err, 0, sizeof(err));
+	err.code = 42;
+	err.line = 3;
+	err.saved_errno = 4;
+	err.type = "AccessDeniedError";
+	err.file = "File";
+	err.message = "Message";
+	err.ad_object_type = "ObjectType";
+	err.ad_object_name = "ObjectName";
+	err.ad_access_type = "AccessType";
+	err.unknown_str_field = "unknown_field";
+	data = mp_encode_mp_error(&err, data);
+
+	uint32_t len = data - buffer;
+	const char *pos = buffer;
+	struct error *unpacked = error_unpack(&pos, len);
+	error_ref(unpacked);
+	ok(error_is_eq_mp_error(unpacked, &err),
+	   "check unknown additional field");
+	error_unref(unpacked);
+
+	check_plan();
+	footer();
+}
+
+int
+main(void)
+{
+	header();
+	plan(5);
+	memory_init();
+	fiber_init(fiber_c_invoke);
+
+	test_stack_error_decode();
+	test_decode_unknown_type();
+	test_fail_not_enough_fields();
+	test_unknown_fields();
+	test_unknown_additional_fields();
+
+	fiber_free();
+	memory_free();
+	footer();
+	return check_plan();
+}
diff --git a/test/unit/mp_error.result b/test/unit/mp_error.result
new file mode 100644
index 0000000..6ef37b4
--- /dev/null
+++ b/test/unit/mp_error.result
@@ -0,0 +1,44 @@
+	*** main ***
+1..5
+	*** test_stack_error_decode ***
+    1..17
+    ok 1 - check CustomError
+    ok 2 - check AccessDeniedError
+    ok 3 - check ClientError
+    ok 4 - check XlogError
+    ok 5 - check XlogGapError
+    ok 6 - check SystemError
+    ok 7 - check SocketError
+    ok 8 - check OutOfMemory
+    ok 9 - check TimedOut
+    ok 10 - check ChannelIsClosed
+    ok 11 - check FiberIsCancelled
+    ok 12 - check LuajitError
+    ok 13 - check IllegalParams
+    ok 14 - check CollationError
+    ok 15 - check SwimError
+    ok 16 - check CryptoError
+    ok 17 - stack size
+ok 1 - subtests
+	*** test_stack_error_decode: done ***
+	*** test_decode_unknown_type ***
+    1..1
+    ok 1 - check SomeNewError
+ok 2 - subtests
+	*** test_decode_unknown_type: done ***
+	*** test_fail_not_enough_fields ***
+    1..2
+    ok 1 - check not enough additional fields
+    ok 2 - error about parsing problem is set
+ok 3 - subtests
+	*** test_fail_not_enough_fields: done ***
+	*** test_unknown_fields ***
+    1..1
+    ok 1 - check unknown fields
+ok 4 - subtests
+	*** test_unknown_additional_fields ***
+    1..1
+    ok 1 - check unknown additional field
+ok 5 - subtests
+	*** test_unknown_additional_fields: done ***
+	*** main: done ***
-- 
2.7.4



More information about the Tarantool-patches mailing list