From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp46.i.mail.ru (smtp46.i.mail.ru [94.100.177.106]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id E3EA245C310 for ; Tue, 12 May 2020 02:45:58 +0300 (MSK) From: Vladislav Shpilevoy Date: Tue, 12 May 2020 01:45:51 +0200 Message-Id: <22a830fb9d2dbc3883b9710d36ab88c638101002.1589240704.git.v.shpilevoy@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH 4/5] error: provide MP_ERROR extension serializer List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: tarantool-patches@dev.tarantool.org, gorcunov@gmail.com, sergepetrenko@tarantool.org, korablev@tarantool.org 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 +#include #include 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)