From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v1 1/4] lib: introduce json_tree_snprint_path Date: Thu, 27 Dec 2018 14:15:52 +0300 Message-Id: In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: kostja@tarantool.org, Kirill Shcherbatov List-ID: Implemented a helper function for getting a path to a json_token in a json_tree. When routine is called with size=0, return value (as always) as the number of characters that would have been written in case the output string has been large enough. We will use this function for reporting tuple format violations in further patches. Needed for #1012 --- src/lib/json/json.c | 68 ++++++++++++++++++++++++++++++++++++++++ src/lib/json/json.h | 10 ++++++ test/unit/json.c | 73 ++++++++++++++++++++++++++++++++++++++++++- test/unit/json.result | 24 +++++++++++++- 4 files changed, 173 insertions(+), 2 deletions(-) diff --git a/src/lib/json/json.c b/src/lib/json/json.c index c7909fde2..6bee024a9 100644 --- a/src/lib/json/json.c +++ b/src/lib/json/json.c @@ -317,6 +317,74 @@ json_path_validate(const char *path, int path_len, int index_base) return rc; } +/** + * An snprint-style helper to print an individual token key. + */ +static int +json_token_snprint(char *buf, int size, const struct json_token *token, + int index_base) +{ + enum json_token_type type = token->type; + assert(type == JSON_TOKEN_NUM || type == JSON_TOKEN_STR); + int len = 0; + if (type == JSON_TOKEN_NUM) + len = snprintf(buf, size, "[%d]", token->num + index_base); + else if (type == JSON_TOKEN_STR) + len = snprintf(buf, size, "[\"%.*s\"]", token->len, token->str); + return len; +} + +int +json_tree_snprint_path(char *buf, int size, const struct json_token *root, + const struct json_token *token, int index_base) +{ + /* + * Calculate JSON path string length passing 0-buffer in + * json_token_snprint routine. + */ + int len = 0; + const struct json_token *ptr = token; + while (ptr != root && ptr->type != JSON_TOKEN_END) { + len += json_token_snprint(NULL, 0, ptr, index_base); + ptr = ptr->parent; + } + if (size == 0) + return len; + + /* + * Write to the memory buffer buf as many tokens, + * starting with the root, as it can fit. If the fragment + * of the token does not fit into the buffer, it would be + * truncated. + */ + int pos = len; + char last = '\0'; + ptr = token; + while (ptr != root && ptr->type != JSON_TOKEN_END) { + pos -= json_token_snprint(NULL, 0, ptr, index_base); + assert(pos >= 0); + if (pos < size) { + int rc = json_token_snprint(buf + pos, size - pos, ptr, + index_base); + rc = MIN(rc, size - pos); + /* + * The json_token_snprint writes a + * '\0'-terminated string in memory + * buffer. The first character in + * token string representation would be + * overwritten on next cycle iteration. + * Let's keep it in 'last' variable to + * restore next time. + */ + buf[pos + rc] = last; + last = buf[pos]; + } + ptr = ptr->parent; + } + assert(buf[MIN(len, size - 1)] == '\0'); + return len; +} + #define MH_SOURCE 1 #define mh_name _json #define mh_key_t struct json_token * diff --git a/src/lib/json/json.h b/src/lib/json/json.h index 7ce10ce2c..c0adca450 100644 --- a/src/lib/json/json.h +++ b/src/lib/json/json.h @@ -253,6 +253,16 @@ json_path_cmp(const char *a, int a_len, const char *b, int b_len, int json_path_validate(const char *path, int path_len, int index_base); +/** + * An snprint-style function to print path to a token in a JSON + * tree. The root node doesn't necessarily have to be the tree + * root - it may be any ascendant of the given token, in which + * case a path from the given ascendant to the token will be + * printed. + */ +int +json_tree_snprint_path(char *buf, int size, const struct json_token *root, + const struct json_token *token, int index_base); /** * Initialize a new empty JSON tree. * diff --git a/test/unit/json.c b/test/unit/json.c index e553ff23c..26cc5088b 100644 --- a/test/unit/json.c +++ b/test/unit/json.c @@ -438,16 +438,87 @@ test_path_cmp() footer(); } + +struct snprintf_test_case { + struct json_token *root; + struct json_token *token; + int buf_size; + int retval; + const char *str; + int str_len; + const char *descr; +}; + +void +test_path_snprintf() +{ + struct json_tree tree; + int rc = json_tree_create(&tree); + fail_if(rc != 0); + struct test_struct records[5]; + const char *path = "[1][20][\"file\"][8]"; + int path_len = strlen(path); // 18 + + int records_idx = 0; + struct test_struct *node, *node_tmp; + node = test_add_path(&tree, path, path_len, records, &records_idx); + fail_if(&node->node != &records[3].node); + + /* Test size estimation. */ + char buff[64]; + const struct snprintf_test_case tests[] = { + {&tree.root, &node->node, path_len + 1, path_len, path, + path_len, "Full path"}, + {&records[0].node, &node->node, path_len + 1, path_len - 3, + path + 3, path_len - 3, "Non-tree-root root token"}, + {&tree.root, &node->node, path_len - 5, path_len, path, + path_len - 6, "Not enough space in buffer, all tokens fit in"}, + {&tree.root, &node->node, path_len - 5, path_len, path, + path_len - 6, + "Not enough space in buffer, part of token doesn't fit in"}, + {&tree.root, &node->node, 2, path_len, path, 1, + "2-byte size buffer"}, + {&tree.root, &node->node, 1, path_len, path, 0, + "1-byte size buffer"}, + }; + header(); + plan(3 * lengthof(tests)); + + for (size_t i = 0; i < lengthof(tests); ++i) { + rc = json_tree_snprint_path(buff, tests[i].buf_size, + tests[i].root, tests[i].token, + INDEX_BASE); + is(memcmp(buff, tests[i].str, tests[i].str_len), 0, + "\"%s\" - correct partial JSON path: have \"%.*s\" expected \"%.*s\"", + tests[i].descr, tests[i].buf_size, buff, tests[i].str_len, + tests[i].str); + is(buff[tests[i].str_len], '\0', + "\"%s\" - terminating '\\0' at appropriate place", + tests[i].descr); + is(rc, tests[i].retval, "\"%s\" - correct rc: have %d expected %d", + tests[i].descr, rc, tests[i].retval); + } + + json_tree_foreach_entry_safe(node, &tree.root, struct test_struct, + node, node_tmp) + json_tree_del(&tree, &node->node); + json_tree_destroy(&tree); + + check_plan(); + footer(); +} + int main() { header(); - plan(4); + plan(5); test_basic(); test_errors(); test_tree(); test_path_cmp(); + test_path_snprintf(); int rc = check_plan(); footer(); diff --git a/test/unit/json.result b/test/unit/json.result index a17451099..a73203154 100644 --- a/test/unit/json.result +++ b/test/unit/json.result @@ -1,5 +1,5 @@ *** main *** -1..4 +1..5 *** test_basic *** 1..71 ok 1 - parse <[1]> @@ -169,4 +169,26 @@ ok 3 - subtests ok 7 - path Data[[1]["FIO"].fname error pos 6 expected 6 ok 4 - subtests *** test_path_cmp: done *** + *** test_path_snprintf *** + 1..18 + ok 1 - "Full path" - correct partial JSON path: have "[1][20]["file"][8]" expected "[1][20]["file"][8]" + ok 2 - "Full path" - terminating '\0' at appropriate place + ok 3 - "Full path" - correct rc: have 18 expected 18 + ok 4 - "Non-tree-root root token" - correct partial JSON path: have "[20]["file"][8]" expected "[20]["file"][8]" + ok 5 - "Non-tree-root root token" - terminating '\0' at appropriate place + ok 6 - "Non-tree-root root token" - correct rc: have 15 expected 15 + ok 7 - "Not enough space in buffer, all tokens fit in" - correct partial JSON path: have "[1][20]["fil" expected "[1][20]["fil" + ok 8 - "Not enough space in buffer, all tokens fit in" - terminating '\0' at appropriate place + ok 9 - "Not enough space in buffer, all tokens fit in" - correct rc: have 18 expected 18 + ok 10 - "Not enough space in buffer, part of token doesn't fit in" - correct partial JSON path: have "[1][20]["fil" expected "[1][20]["fil" + ok 11 - "Not enough space in buffer, part of token doesn't fit in" - terminating '\0' at appropriate place + ok 12 - "Not enough space in buffer, part of token doesn't fit in" - correct rc: have 18 expected 18 + ok 13 - "2-byte size buffer" - correct partial JSON path: have "[" expected "[" + ok 14 - "2-byte size buffer" - terminating '\0' at appropriate place + ok 15 - "2-byte size buffer" - correct rc: have 18 expected 18 + ok 16 - "1-byte size buffer" - correct partial JSON path: have "" expected "" + ok 17 - "1-byte size buffer" - terminating '\0' at appropriate place + ok 18 - "1-byte size buffer" - correct rc: have 18 expected 18 +ok 5 - subtests + *** test_path_snprintf: done *** *** main: done *** -- 2.19.2