[Tarantool-patches] [PATCH 4/5] error: provide MP_ERROR extension serializer

Vladislav Shpilevoy v.shpilevoy at tarantool.org
Tue May 12 02:45:51 MSK 2020


Msgpuck functions mp_snprint() and mp_fprint() now support
customizable MP_EXT serializer. This patch adds such for MP_ERROR.
All extension serializers will be activated in a separate commit.

Part of #4719
---
 src/box/mp_error.cc       | 161 ++++++++++++++++++++++-
 src/box/mp_error.h        |  29 ++++
 test/unit/mp_error.cc     | 270 +++++++++++++++++++++++++++++++++++++-
 test/unit/mp_error.result |  72 +++++++++-
 4 files changed, 529 insertions(+), 3 deletions(-)

diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc
index 0491a7a50..fed2ce288 100644
--- a/src/box/mp_error.cc
+++ b/src/box/mp_error.cc
@@ -28,6 +28,8 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#ifndef MP_ERROR_PRINT_DEFINITION
+
 #include "box/mp_error.h"
 #include "box/error.h"
 #include "mpstream/mpstream.h"
@@ -86,7 +88,8 @@ enum {
 	 * Type-specific fields stored as a map
 	 * {string key = value}.
 	 */
-	MP_ERROR_FIELDS = 0x06
+	MP_ERROR_FIELDS = 0x06,
+	MP_ERROR_MAX,
 };
 
 /**
@@ -552,3 +555,159 @@ error_unpack_unsafe(const char **data)
 	}
 	return err;
 }
+
+#define MP_ERROR_PRINT_DEFINITION
+#define MP_PRINT_FUNC snprintf
+#define MP_PRINT_SUFFIX snprint
+#define MP_PRINT_2(total, func, ...)						\
+	SNPRINT(total, func, buf, size, __VA_ARGS__)
+#define MP_PRINT_ARGS_DECL char *buf, int size
+#include __FILE__
+
+#define MP_ERROR_PRINT_DEFINITION
+#define MP_PRINT_FUNC fprintf
+#define MP_PRINT_SUFFIX fprint
+#define MP_PRINT_2(total, func, ...) do {							\
+	int bytes = func(file, __VA_ARGS__);					\
+	if (bytes < 0)								\
+		return -1;							\
+	total += bytes;								\
+} while (0)
+#define MP_PRINT_ARGS_DECL FILE *file
+#include __FILE__
+
+/* !defined(MP_ERROR_PRINT_DEFINITION) */
+#else
+/* defined(MP_ERROR_PRINT_DEFINITION) */
+
+/**
+ * MP_ERROR extension string serializer.
+ * There are two applications for string serialization - into a
+ * buffer, and into a file. Structure of both is exactly the same
+ * except for the copying/writing itself. To avoid code
+ * duplication the code is templated and expects some macros to do
+ * the actual output.
+ */
+
+#define MP_CONCAT4_R(a, b, c, d) a##b##c##d
+#define MP_CONCAT4(a, b, c, d) MP_CONCAT4_R(a, b, c, d)
+#define MP_PRINT(total, ...) MP_PRINT_2(total, MP_PRINT_FUNC, __VA_ARGS__)
+
+#define mp_func_name(name) MP_CONCAT4(mp_, MP_PRINT_SUFFIX, _, name)
+#define mp_print_error_one mp_func_name(error_one)
+#define mp_print_error_stack mp_func_name(error_stack)
+#define mp_print_error mp_func_name(error)
+#define mp_print_common mp_func_name(recursion)
+
+static int
+mp_print_error_one(MP_PRINT_ARGS_DECL, const char **data, int depth)
+{
+	int total = 0;
+	MP_PRINT(total, "{");
+	if (depth <= 0) {
+		MP_PRINT(total, "...}");
+		return total;
+	}
+	const char *field_to_key[MP_ERROR_MAX] = {
+		/* MP_ERROR_TYPE = */ "\"type\": ",
+		/* MP_ERROR_FILE = */ "\"file\": ",
+		/* MP_ERROR_LINE = */ "\"line\": ",
+		/* MP_ERROR_MESSAGE = */ "\"message\": ",
+		/* MP_ERROR_ERRNO = */ "\"errno\": ",
+		/* MP_ERROR_CODE = */ "\"code\": ",
+		/* MP_ERROR_FIELDS = */ "\"fields\": ",
+	};
+	--depth;
+	if (mp_typeof(**data) != MP_MAP)
+		return -1;
+	uint32_t map_size = mp_decode_map(data);
+	for (uint32_t i = 0; i < map_size; ++i) {
+		if (i != 0)
+			MP_PRINT(total, ", ");
+		if (mp_typeof(**data) != MP_UINT)
+			return -1;
+		uint64_t key = mp_decode_uint(data);
+		if (key < MP_ERROR_MAX)
+			MP_PRINT(total, "%s", field_to_key[key]);
+		else
+			MP_PRINT(total, "%llu: ", key);
+		MP_PRINT_2(total, mp_print_common, data, depth);
+	}
+	MP_PRINT(total, "}");
+	return total;
+}
+
+static int
+mp_print_error_stack(MP_PRINT_ARGS_DECL, const char **data, int depth)
+{
+	int total = 0;
+	MP_PRINT(total, "[");
+	if (depth <= 0) {
+		MP_PRINT(total, "...]");
+		return total;
+	}
+	--depth;
+	if (mp_typeof(**data) != MP_ARRAY)
+		return -1;
+	uint32_t arr_size = mp_decode_array(data);
+	for (uint32_t i = 0; i < arr_size; ++i) {
+		if (i != 0)
+			MP_PRINT(total, ", ");
+		MP_PRINT_2(total, mp_print_error_one, data, depth);
+	}
+	MP_PRINT(total, "]");
+	return total;
+}
+
+int
+mp_print_error(MP_PRINT_ARGS_DECL, const char **data, int depth)
+{
+	int total = 0;
+	MP_PRINT(total, "{");
+	if (depth <= 0) {
+		MP_PRINT(total, "...}");
+		return total;
+	}
+	--depth;
+	if (mp_typeof(**data) != MP_MAP)
+		return -1;
+	uint32_t map_size = mp_decode_map(data);
+	for (uint32_t i = 0; i < map_size; ++i) {
+		if (i != 0)
+			MP_PRINT(total, ", ");
+		if (mp_typeof(**data) != MP_UINT)
+			return -1;
+		uint64_t key = mp_decode_uint(data);
+		switch(key) {
+		case MP_ERROR_STACK: {
+			MP_PRINT(total, "\"stack\": ");
+			MP_PRINT_2(total, mp_print_error_stack, data, depth);
+			break;
+		}
+		default:
+			MP_PRINT(total, "%llu: ", key);
+			MP_PRINT_2(total, mp_print_common, data, depth);
+			break;
+		}
+	}
+	MP_PRINT(total, "}");
+	return total;
+}
+
+#undef MP_PRINT
+#undef MP_CONCAT4_R
+#undef MP_CONCAT4
+
+#undef mp_func_name
+#undef mp_print_error_one
+#undef mp_print_error_stack
+#undef mp_print_error
+#undef mp_print_common
+
+#undef MP_ERROR_PRINT_DEFINITION
+#undef MP_PRINT_FUNC
+#undef MP_PRINT_SUFFIX
+#undef MP_PRINT_2
+#undef MP_PRINT_ARGS_DECL
+
+#endif /* defined(MP_ERROR_PRINT_DEFINITION) */
diff --git a/src/box/mp_error.h b/src/box/mp_error.h
index f040e0fc7..a4a82ecb7 100644
--- a/src/box/mp_error.h
+++ b/src/box/mp_error.h
@@ -35,9 +35,11 @@ extern "C" {
 #endif /* defined(__cplusplus) */
 
 #include <stdint.h>
+#include <stdio.h>
 #include <assert.h>
 
 struct mpstream;
+struct error;
 
 /**
  * @brief Encode error to mpstream as MP_ERROR.
@@ -69,6 +71,33 @@ error_unpack(const char **data, uint32_t len)
 	return e;
 }
 
+/**
+ * Print error's string representation into a given buffer.
+ * @param buf Target buffer to write string to.
+ * @param size Buffer size.
+ * @param data MessagePack encoded error, without MP_EXT header.
+ * @param len Length of @a data. If not all data is used, it is
+ *        an error.
+ * @retval <0 Error. Couldn't decode error.
+ * @retval >=0 How many bytes were written, or would have been
+ *        written, if there was enough buffer space.
+ */
+int
+mp_snprint_error(char *buf, int size, const char **data, int depth);
+
+/**
+ * Print error's string representation into a stream.
+ * @param file Target stream to write string to.
+ * @param data MessagePack encoded error, without MP_EXT header.
+ * @param len Length of @a data. If not all data is used, it is
+ *        an error.
+ * @retval <0 Error. Couldn't decode error, or couldn't write to
+ *        the stream.
+ * @retval >=0 How many bytes were written.
+ */
+int
+mp_fprint_error(FILE *file, const char **data, int depth);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 #endif /* defined(__cplusplus) */
diff --git a/test/unit/mp_error.cc b/test/unit/mp_error.cc
index becabb370..e85d282e7 100644
--- a/test/unit/mp_error.cc
+++ b/test/unit/mp_error.cc
@@ -33,7 +33,10 @@
 #include "fiber.h"
 #include "memory.h"
 #include "msgpuck.h"
+#include "mp_extension_types.h"
 #include "tt_static.h"
+#include "small/ibuf.h"
+#include "mpstream/mpstream.h"
 
 #include "box/error.h"
 #include "box/mp_error.h"
@@ -452,11 +455,275 @@ test_unknown_additional_fields()
 	footer();
 }
 
+static int
+mp_fprint_ext_test(FILE *file, const char **data, int depth)
+{
+	int8_t type;
+	mp_decode_extl(data, &type);
+	if (type != MP_ERROR)
+		return fprintf(file, "undefined");
+	return mp_fprint_error(file, data, depth);
+}
+
+static int
+mp_snprint_ext_test(char *buf, int size, const char **data, int depth)
+{
+	int8_t type;
+	mp_decode_extl(data, &type);
+	if (type != MP_ERROR)
+		return snprintf(buf, size, "undefined");
+	return mp_snprint_error(buf, size, data, depth);
+}
+
+static void
+test_mp_print_check_str(int depth, const char *str, int len,
+			const char *expected, const char *method)
+{
+	is(len, (int) strlen(str), "%s depth %d correct returned value", method,
+	   depth);
+	int expected_len = strlen(expected);
+	is(len, depth * 2 + expected_len, "%s depth %d correct length", method,
+	   depth);
+	bool is_error = false;
+	/*
+	 * Deep encoding is simulated with a number of nested
+	 * arrays. In string representation they look like:
+	 *
+	 *   [[[[[[[ ... object ... ]]]]]]]
+	 *
+	 */
+	for (int i = 0; i < depth && !is_error; ++i)
+		is_error = str[i] != '[' || str[len - 1 - i] != ']';
+	ok(!is_error, "%s depth %d correct prefix and suffix", method, depth);
+	str += depth;
+	is(strncmp(str, expected, expected_len), 0, "%s depth %d correct "
+	   "object in the middle", method, depth);
+}
+
+static void
+mpstream_error_test(void *ctx)
+{
+	*((bool *)ctx) = true;
+}
+
+static void
+test_mp_print_encode_error(struct ibuf *buf, struct error *e, int depth)
+{
+	struct mpstream stream;
+	bool is_error = false;
+	mpstream_init(&stream, buf, ibuf_reserve_cb, ibuf_alloc_cb,
+		      mpstream_error_test, &is_error);
+	for (int i = 0; i < depth; ++i)
+		mpstream_encode_array(&stream, 1);
+	error_to_mpstream(e, &stream);
+	mpstream_flush(&stream);
+	fail_if(is_error);
+}
+
+static void
+test_mp_print_check(const char *data, int depth, const char *expected)
+{
+	char str[2048];
+	int rc = mp_snprint(str, sizeof(str), data);
+	test_mp_print_check_str(depth, str, rc, expected, "mp_snprint");
+	int rc2 = mp_snprint(NULL, 0, data);
+	is(rc, rc2, "mp_snprint depth %d correct with NULL buffer", depth);
+
+	FILE *f = tmpfile();
+	rc = mp_fprint(f, data);
+	rewind(f);
+	rc2 = fread(str, 1, TT_STATIC_BUF_LEN, f);
+	is(rc, rc2, "mp_fprint depth %d result and the actual file size "
+	   "are equal", depth);
+	str[rc] = 0;
+	fclose(f);
+	test_mp_print_check_str(depth, str, rc, expected, "mp_fprint");
+}
+
+static void
+test_mp_print(void)
+{
+	header();
+	plan(60);
+
+	mp_snprint_ext = mp_snprint_ext_test;
+	mp_fprint_ext = mp_fprint_ext_test;
+
+	struct error *e1 = BuildClientError("file1", 1, 0);
+	error_ref(e1);
+	struct error *e2 = BuildCustomError("file2", 4, "type", 5);
+	struct error *e3 = BuildAccessDeniedError("file3", 6, "field1",
+						  "field2", "field3", "field4");
+	error_set_prev(e1, e2);
+	error_set_prev(e2, e3);
+
+	struct ibuf buf;
+	ibuf_create(&buf, &cord()->slabc, 1024);
+
+	note("zero depth, normal error");
+	int depth = 0;
+	const char *expected = "{"
+		"\"stack\": ["
+			"{"
+				"\"type\": \"ClientError\", "
+				"\"line\": 1, "
+				"\"file\": \"file1\", "
+				"\"message\": \"Unknown error\", "
+				"\"errno\": 0, "
+				"\"code\": 0"
+			"}, "
+			"{"
+				"\"type\": \"CustomError\", "
+				"\"line\": 4, "
+				"\"file\": \"file2\", "
+				"\"message\": \"\", "
+				"\"errno\": 0, "
+				"\"code\": 5, "
+				"\"fields\": {"
+					"\"custom_type\": \"type\""
+				"}"
+			"}, "
+			"{"
+				"\"type\": \"AccessDeniedError\", "
+				"\"line\": 6, "
+				"\"file\": \"file3\", "
+				"\"message\": \"field1 access to field2 "
+					"'field3' is denied for user "
+					"'field4'\", "
+				"\"errno\": 0, "
+				"\"code\": 42, "
+				"\"fields\": {"
+					"\"object_type\": \"field2\", "
+					"\"object_name\": \"field3\", "
+					"\"access_type\": \"field1\""
+				"}"
+			"}"
+		"]"
+	"}";
+	test_mp_print_encode_error(&buf, e1, depth);
+	test_mp_print_check(buf.rpos, depth, expected);
+	ibuf_reset(&buf);
+
+	note("max depth, all is truncated");
+	depth = MP_PRINT_MAX_DEPTH;
+	expected = "{...}";
+	test_mp_print_encode_error(&buf, e1, depth);
+	test_mp_print_check(buf.rpos, depth, expected);
+	ibuf_reset(&buf);
+
+	note("max depth - 1, top level of keys is visible");
+	depth = MP_PRINT_MAX_DEPTH - 1;
+	expected = "{\"stack\": [...]}";
+	test_mp_print_encode_error(&buf, e1, depth);
+	test_mp_print_check(buf.rpos, depth, expected);
+	ibuf_reset(&buf);
+
+	note("max depth - 2, top level of keys and error count are visible");
+	depth = MP_PRINT_MAX_DEPTH - 2;
+	expected = "{\"stack\": [{...}, {...}, {...}]}";
+	test_mp_print_encode_error(&buf, e1, depth);
+	test_mp_print_check(buf.rpos, depth, expected);
+	ibuf_reset(&buf);
+
+	note("max depth - 3, all except additional fields is visible");
+	depth = MP_PRINT_MAX_DEPTH - 3;
+	expected = "{"
+		"\"stack\": ["
+			"{"
+				"\"type\": \"ClientError\", "
+				"\"line\": 1, "
+				"\"file\": \"file1\", "
+				"\"message\": \"Unknown error\", "
+				"\"errno\": 0, "
+				"\"code\": 0"
+			"}, "
+			"{"
+				"\"type\": \"CustomError\", "
+				"\"line\": 4, "
+				"\"file\": \"file2\", "
+				"\"message\": \"\", "
+				"\"errno\": 0, "
+				"\"code\": 5, "
+				"\"fields\": {...}"
+			"}, "
+			"{"
+				"\"type\": \"AccessDeniedError\", "
+				"\"line\": 6, "
+				"\"file\": \"file3\", "
+				"\"message\": \"field1 access to field2 "
+					"'field3' is denied for user "
+					"'field4'\", "
+				"\"errno\": 0, "
+				"\"code\": 42, "
+				"\"fields\": {...}"
+			"}"
+		"]"
+	"}";
+	test_mp_print_encode_error(&buf, e1, depth);
+	test_mp_print_check(buf.rpos, depth, expected);
+	ibuf_reset(&buf);
+
+	note("zero depth, error with unknown fields");
+	ibuf_reserve(&buf, 2048);
+	char *begin = buf.rpos + 10;
+	char *data = mp_encode_map(begin, 2);
+	data = mp_encode_uint(data, 4096);
+	data = mp_encode_double(data, 1.234);
+	data = mp_encode_uint(data, MP_ERROR_STACK);
+	data = mp_encode_array(data, 1);
+	struct mp_test_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_uint_field = 300;
+	err.unknown_str_field = "unknown_field";
+	data = mp_encode_mp_error(&err, data);
+	uint32_t size = data - begin;
+	begin -= mp_sizeof_extl(size);
+	mp_encode_extl(begin, MP_ERROR, size);
+	expected = "{"
+		"4096: 1.234, "
+		"\"stack\": ["
+			"{"
+				"\"type\": \"AccessDeniedError\", "
+				"\"file\": \"File\", "
+				"\"line\": 3, "
+				"\"message\": \"Message\", "
+				"\"errno\": 4, "
+				"\"code\": 42, "
+				"18446744073709551615: 300, "
+				"\"fields\": {"
+					"\"object_type\": \"ObjectType\", "
+					"\"object_name\": \"ObjectName\", "
+					"\"access_type\": \"AccessType\", "
+					"\"unknown_field\": \"unknown_field\""
+				"}"
+			"}"
+		"]"
+	"}";
+	test_mp_print_check(begin, 0, expected);
+
+	error_unref(e1);
+	ibuf_destroy(&buf);
+	mp_snprint_ext = mp_snprint_ext_default;
+	mp_fprint_ext = mp_fprint_ext_default;
+
+	check_plan();
+	footer();
+}
+
 int
 main(void)
 {
 	header();
-	plan(5);
+	plan(6);
 	memory_init();
 	fiber_init(fiber_c_invoke);
 
@@ -465,6 +732,7 @@ main(void)
 	test_fail_not_enough_fields();
 	test_unknown_fields();
 	test_unknown_additional_fields();
+	test_mp_print();
 
 	fiber_free();
 	memory_free();
diff --git a/test/unit/mp_error.result b/test/unit/mp_error.result
index 6ef37b4d5..0582458d3 100644
--- a/test/unit/mp_error.result
+++ b/test/unit/mp_error.result
@@ -1,5 +1,5 @@
 	*** main ***
-1..5
+1..6
 	*** test_stack_error_decode ***
     1..17
     ok 1 - check CustomError
@@ -41,4 +41,74 @@ ok 4 - subtests
     ok 1 - check unknown additional field
 ok 5 - subtests
 	*** test_unknown_additional_fields: done ***
+	*** test_mp_print ***
+    1..60
+    # zero depth, normal error
+    ok 1 - mp_snprint depth 0 correct returned value
+    ok 2 - mp_snprint depth 0 correct length
+    ok 3 - mp_snprint depth 0 correct prefix and suffix
+    ok 4 - mp_snprint depth 0 correct object in the middle
+    ok 5 - mp_snprint depth 0 correct with NULL buffer
+    ok 6 - mp_fprint depth 0 result and the actual file size are equal
+    ok 7 - mp_fprint depth 0 correct returned value
+    ok 8 - mp_fprint depth 0 correct length
+    ok 9 - mp_fprint depth 0 correct prefix and suffix
+    ok 10 - mp_fprint depth 0 correct object in the middle
+    # max depth, all is truncated
+    ok 11 - mp_snprint depth 32 correct returned value
+    ok 12 - mp_snprint depth 32 correct length
+    ok 13 - mp_snprint depth 32 correct prefix and suffix
+    ok 14 - mp_snprint depth 32 correct object in the middle
+    ok 15 - mp_snprint depth 32 correct with NULL buffer
+    ok 16 - mp_fprint depth 32 result and the actual file size are equal
+    ok 17 - mp_fprint depth 32 correct returned value
+    ok 18 - mp_fprint depth 32 correct length
+    ok 19 - mp_fprint depth 32 correct prefix and suffix
+    ok 20 - mp_fprint depth 32 correct object in the middle
+    # max depth - 1, top level of keys is visible
+    ok 21 - mp_snprint depth 31 correct returned value
+    ok 22 - mp_snprint depth 31 correct length
+    ok 23 - mp_snprint depth 31 correct prefix and suffix
+    ok 24 - mp_snprint depth 31 correct object in the middle
+    ok 25 - mp_snprint depth 31 correct with NULL buffer
+    ok 26 - mp_fprint depth 31 result and the actual file size are equal
+    ok 27 - mp_fprint depth 31 correct returned value
+    ok 28 - mp_fprint depth 31 correct length
+    ok 29 - mp_fprint depth 31 correct prefix and suffix
+    ok 30 - mp_fprint depth 31 correct object in the middle
+    # max depth - 2, top level of keys and error count are visible
+    ok 31 - mp_snprint depth 30 correct returned value
+    ok 32 - mp_snprint depth 30 correct length
+    ok 33 - mp_snprint depth 30 correct prefix and suffix
+    ok 34 - mp_snprint depth 30 correct object in the middle
+    ok 35 - mp_snprint depth 30 correct with NULL buffer
+    ok 36 - mp_fprint depth 30 result and the actual file size are equal
+    ok 37 - mp_fprint depth 30 correct returned value
+    ok 38 - mp_fprint depth 30 correct length
+    ok 39 - mp_fprint depth 30 correct prefix and suffix
+    ok 40 - mp_fprint depth 30 correct object in the middle
+    # max depth - 3, all except additional fields is visible
+    ok 41 - mp_snprint depth 29 correct returned value
+    ok 42 - mp_snprint depth 29 correct length
+    ok 43 - mp_snprint depth 29 correct prefix and suffix
+    ok 44 - mp_snprint depth 29 correct object in the middle
+    ok 45 - mp_snprint depth 29 correct with NULL buffer
+    ok 46 - mp_fprint depth 29 result and the actual file size are equal
+    ok 47 - mp_fprint depth 29 correct returned value
+    ok 48 - mp_fprint depth 29 correct length
+    ok 49 - mp_fprint depth 29 correct prefix and suffix
+    ok 50 - mp_fprint depth 29 correct object in the middle
+    # zero depth, error with unknown fields
+    ok 51 - mp_snprint depth 0 correct returned value
+    ok 52 - mp_snprint depth 0 correct length
+    ok 53 - mp_snprint depth 0 correct prefix and suffix
+    ok 54 - mp_snprint depth 0 correct object in the middle
+    ok 55 - mp_snprint depth 0 correct with NULL buffer
+    ok 56 - mp_fprint depth 0 result and the actual file size are equal
+    ok 57 - mp_fprint depth 0 correct returned value
+    ok 58 - mp_fprint depth 0 correct length
+    ok 59 - mp_fprint depth 0 correct prefix and suffix
+    ok 60 - mp_fprint depth 0 correct object in the middle
+ok 6 - subtests
+	*** test_mp_print: done ***
 	*** main: done ***
-- 
2.21.1 (Apple Git-122.3)



More information about the Tarantool-patches mailing list