[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