From: Stephan Bosch Date: Wed, 7 Aug 2019 19:48:01 +0000 (+0200) Subject: lib-json: json-istream - Add support for reading JSON tree values X-Git-Tag: 2.4.0~2381 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8743d99e9def36f640dc5c3ddcad36dd41281ea6;p=thirdparty%2Fdovecot%2Fcore.git lib-json: json-istream - Add support for reading JSON tree values --- diff --git a/src/lib-json/json-istream.c b/src/lib-json/json-istream.c index f4017775a4..a3515946be 100644 --- a/src/lib-json/json-istream.c +++ b/src/lib-json/json-istream.c @@ -20,6 +20,10 @@ struct json_istream { struct istream *value_stream, *seekable_stream; + struct json_tree *tree; + struct json_tree_node *tree_node; + unsigned int tree_node_level; + char *error; bool opened:1; @@ -223,11 +227,12 @@ static inline bool json_istream_parse_skip(struct json_istream *stream) } static void -json_istream_parse_list_open(void *context, void *parent_context ATTR_UNUSED, +json_istream_parse_list_open(void *context, void *parent_context, const char *name, bool object, - void **list_context_r ATTR_UNUSED) + void **list_context_r) { struct json_istream *stream = context; + struct json_tree_node *parent = parent_context; unsigned int node_level = stream->node_level; i_assert(!stream->node_parsed); @@ -259,6 +264,19 @@ json_istream_parse_list_open(void *context, void *parent_context ATTR_UNUSED, stream->node_level++; + if (stream->tree != NULL) { + if (parent == NULL) + parent = stream->tree_node; + if (object) { + *list_context_r = (void *) + json_tree_node_add_object(parent, name); + } else { + *list_context_r = (void *) + json_tree_node_add_array(parent, name); + } + return; + } + if (node_level == stream->read_node_level) { i_zero(&stream->node); stream->node.name = name; @@ -307,6 +325,20 @@ json_istream_parse_list_close(void *context, void *list_context ATTR_UNUSED, } } + if (stream->tree != NULL) { + if (stream->node_level < stream->tree_node_level) { + stream->end_of_list = TRUE; + stream->node_parsed = TRUE; + json_parser_interrupt(stream->parser); + } else if (stream->node_level == stream->tree_node_level) { + if (!json_istream_parse_skip(stream)) { + stream->node_parsed = TRUE; + json_parser_interrupt(stream->parser); + } + } + return; + } + if (stream->node_level < stream->read_node_level) { stream->end_of_list = TRUE; if (!json_istream_parse_skip(stream)) { @@ -334,6 +366,7 @@ json_istream_parse_object_member(void *context, if (stream->skip_to_end || stream->skip_nodes > 0) return; + i_assert(stream->tree == NULL); i_assert(stream->node_level >= stream->read_node_level); if (stream->node_level != stream->read_node_level) @@ -346,11 +379,11 @@ json_istream_parse_object_member(void *context, } static void -json_istream_parse_value(void *context, void *parent_context ATTR_UNUSED, - const char *name, enum json_type type, - const struct json_value *value) +json_istream_parse_value(void *context, void *parent_context, const char *name, + enum json_type type, const struct json_value *value) { struct json_istream *stream = context; + struct json_tree_node *parent = parent_context; i_assert(!stream->node_parsed); i_assert(stream->node_level >= stream->read_node_level); @@ -374,6 +407,20 @@ json_istream_parse_value(void *context, void *parent_context ATTR_UNUSED, } } + if (stream->tree != NULL) { + if (parent == NULL) { + /* just starting; parent is not in the syntax tree */ + parent = stream->tree_node; + } + (void)json_tree_node_add_value(parent, name, type, value); + if (stream->node_level == stream->tree_node_level) { + stream->node_parsed = TRUE; + json_parser_interrupt(stream->parser); + } + return; + } + + /* not parsing a full tree */ if (stream->node_level != stream->read_node_level) return; if (json_istream_parse_skip(stream)) @@ -409,6 +456,8 @@ static void json_istream_dereference_value(struct json_istream *stream) } json_parser_disable_string_stream(stream->parser); } + if (stream->tree != NULL) + json_tree_unref(&stream->tree); } int json_istream_read(struct json_istream *stream, struct json_node *node_r) @@ -581,6 +630,8 @@ int json_istream_descend(struct json_istream *stream, static void json_istream_ascend_common(struct json_istream *stream) { + if (stream->tree != NULL) + json_tree_unref(&stream->tree); stream->skip_nodes = 0; stream->node_parsed = FALSE; stream->member_parsed = FALSE; @@ -819,3 +870,158 @@ int json_istream_walk_stream(struct json_istream *stream, node_r); return 1; } + +/* + * Tree values + */ + +static int json_istream_read_tree_common(struct json_istream *stream) +{ + const char *error; + int ret; + + ret = json_istream_consume_value_stream(stream); + if (ret <= 0) + return ret; + ret = json_parse_more(stream->parser, &error); + if (ret < 0) { + json_istream_set_error(stream, error); + return ret; + } + if (stream->error != NULL) + return -1; + if (ret == 0 && !stream->node_parsed) { + return 0; + } + if (ret > 0) { + stream->end_of_input = TRUE; + if (!stream->node_parsed) + return -1; + } + return 1; +} + +int json_istream_read_tree(struct json_istream *stream, + struct json_tree **tree_r) +{ + int ret; + + i_assert(tree_r != NULL); + + if (stream->closed) { + *tree_r = NULL; + return -1; + } + if (stream->end_of_input) { + *tree_r = NULL; + return -1; + } + if (stream->end_of_list) { + *tree_r = NULL; + return 1; + } + + stream->member_parsed = FALSE; + if (stream->node_parsed) { + struct json_node root_node = stream->node; + + if (stream->tree != NULL) { + *tree_r = stream->tree; + return 1; + } + + i_assert(stream->node.type != JSON_TYPE_NONE); + i_assert(!json_node_is_end(&stream->node)); + + /* start tree with parsed node */ + root_node.name = NULL; + stream->tree = json_tree_create(); + stream->tree_node = json_tree_node_add( + json_tree_get_root(stream->tree), &root_node); + + stream->node_parsed = FALSE; + + if (json_node_is_singular(&root_node)) { + /* return tree with non-list item immediately */ + *tree_r = stream->tree; + stream->tree = NULL; + return 1; + } + + stream->tree_node_level = stream->read_node_level; + + } else if (stream->tree == NULL) { + /* start blank tree */ + stream->tree = json_tree_create(); + stream->tree_node = json_tree_get_root(stream->tree); + stream->tree_node_level = stream->read_node_level; + } + + ret = json_istream_read_tree_common(stream); + if (ret <= 0) { + *tree_r = NULL; + return ret; + } + + if (stream->end_of_list) { + *tree_r = NULL; + return 1; + } + + *tree_r = stream->tree; + stream->tree = NULL; + json_istream_skip(stream); + return 1; +} + +int json_istream_read_into_tree_node(struct json_istream *stream, + struct json_tree_node *tree_node) +{ + int ret; + + if (stream->tree != NULL) { + if (stream->node_parsed) + return 1; + } else { + if (!stream->node_parsed) { + ret = json_istream_read(stream, NULL); + if (ret <= 0 ) + return ret; + } + + struct json_node new_node = stream->node; + + i_assert(new_node.type != JSON_TYPE_NONE); + i_assert(!json_node_is_end(&new_node)); + + /* start tree branch with parsed node */ + stream->tree_node = json_tree_node_add(tree_node, &new_node); + + stream->node_parsed = FALSE; + + if (json_node_is_singular(&new_node)) { + stream->tree_node = NULL; + json_istream_skip(stream); + return 1; + } + + stream->tree = json_tree_node_get_tree(tree_node); + json_tree_ref(stream->tree); + + stream->tree_node_level = stream->read_node_level; + } + + ret = json_istream_read_tree_common(stream); + if (ret <= 0) + return ret; + + json_istream_skip(stream); + return 1; +} + +int json_istream_read_into_tree(struct json_istream *stream, + struct json_tree *tree) +{ + return json_istream_read_into_tree_node( + stream, json_tree_get_root(tree)); +} diff --git a/src/lib-json/json-istream.h b/src/lib-json/json-istream.h index 0bbfd3fded..cf260f6818 100644 --- a/src/lib-json/json-istream.h +++ b/src/lib-json/json-istream.h @@ -1,6 +1,7 @@ #ifndef JSON_ISTREAM_H #define JSON_ISTREAM_H +#include "json-tree.new.h" #include "json-parser.new.h" // FIXME: don't bother recording values if we're only validating/skipping @@ -172,4 +173,25 @@ int json_istream_walk_stream(struct json_istream *stream, const char *temp_path_prefix, struct json_node *node_r); +/* Read a full JSON tree starting at the current position. If a node was + already read using json_istream_read(), is used as the tree root. Returns + 1 on success, 0 if more data is needed from the input stream, or -1 upon + error or EOF. The last node in an array/object reads as *tree_r == NULL. The + next json_istream_read*() will read the node right after the tree, so + calling json_istream_skip() afterwards is not needed. + */ +int json_istream_read_tree(struct json_istream *stream, + struct json_tree **tree_r); + +/* Same as json_istream_read_tree(), but read current node from stream and all + its children into the provided existing tree node as a new child. */ +int json_istream_read_into_tree_node(struct json_istream *stream, + struct json_tree_node *tree_node); +/* Same as json_istream_read_tree(), but read current node from stream and all + children into the provided existing tree at the root. If there is no root, + the read node becomes the tree root. Otherwise, it is added as a new child of + the tree root. */ +int json_istream_read_into_tree(struct json_istream *stream, + struct json_tree *tree); + #endif diff --git a/src/lib-json/test-json-istream.c b/src/lib-json/test-json-istream.c index f80d9411a3..ba32014b70 100644 --- a/src/lib-json/test-json-istream.c +++ b/src/lib-json/test-json-istream.c @@ -2353,6 +2353,503 @@ static void test_json_istream_finish(void) i_stream_unref(&input); } +/* + * Test: read tree + */ + +static void test_json_istream_read_tree(void) +{ + struct istream *input; + struct json_istream *jinput; + const char *text; + struct json_tree *jtree; + struct json_tree_node *jtnode; + struct json_node jnode; + unsigned int pos, text_len, state; + int ret = 0; + + /* number */ + text = "2234234"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read tree - number"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + + /* string */ + text = "\"frop\""; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read tree - string"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + + /* array */ + text = "[\"frop\"]"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read tree - array"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + + /* array */ + text = "[\"frop\", {\"a\":1234, \"b\":[1, 2, 3, 4], " + "\"c\":1234}, \"frop\"]"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read tree - sequence"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 4) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + switch (state) { + case 0: + ret = json_istream_descend(jinput, &jnode); + if (ret == 0) + continue; + if (ret < 0) + break; + test_assert(json_node_is_array(&jnode)); + state++; + break; + case 1: + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + state++; + break; + case 2: + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_OBJECT); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_OBJECT); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + state++; + break; + case 3: + ret = json_istream_read_tree(jinput, &jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + json_tree_unref(&jtree); + json_istream_ascend(jinput); + state++; + break; + case 4: + ret = json_istream_read(jinput, &jnode); + if (ret == 0) + continue; + if (ret < 0) + break; + state++; + break; + } + } + test_assert(state == 4); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); +} + +/* + * Test: read into tree + */ + +static void test_json_istream_read_into_tree(void) +{ + struct istream *input; + struct json_istream *jinput; + const char *text; + struct json_tree *jtree; + struct json_node jnode; + struct json_tree_node *jtnode; + unsigned int pos, text_len, state; + int ret = 0; + + /* number */ + jtree = json_tree_create(); + text = "2234234"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read into tree - number"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert(json_tree_node_get_next(jtnode) == NULL); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + json_tree_unref(&jtree); + + /* string */ + jtree = json_tree_create(); + text = "\"frop\""; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read into tree - string"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + json_tree_unref(&jtree); + + /* array */ + jtree = json_tree_create(); + text = "[\"frop\"]"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read into tree - array"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 1) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + test_assert(json_tree_node_get_next(jtnode) == NULL); + state++; + } + test_assert(state == 1); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + json_tree_unref(&jtree); + + /* sequence */ + jtree = json_tree_create(); + (void)json_tree_node_add_array(json_tree_get_root(jtree), NULL); + text = "[\"frop\", {\"a\":1234, \"b\":[1, 2, 3, 4], " + "\"c\":1234}, \"frop\"]"; + text_len = strlen(text); + + input = test_istream_create_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream read into tree - sequence"); + + pos = 0; state = 0; ret = 0; + while (ret >= 0 && state <= 4) { + if (pos <= text_len) + pos++; + test_istream_set_size(input, pos); + switch (state) { + case 0: + ret = json_istream_descend(jinput, &jnode); + if (ret == 0) + continue; + if (ret < 0) + break; + test_assert(json_node_is_array(&jnode)); + state++; + break; + case 1: + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + state++; + break; + case 2: + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + state++; + break; + case 3: + ret = json_istream_read_into_tree(jinput, jtree); + if (ret == 0) + continue; + if (ret < 0) + break; + state++; + break; + case 4: + ret = json_istream_read(jinput, &jnode); + if (ret == 0) + continue; + if (ret < 0) + break; + + jtnode = json_tree_get_root(jtree); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_OBJECT); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert_strcmp(json_tree_node_get_name(jtnode), + "a"); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + test_assert_strcmp(json_tree_node_get_name(jtnode), + "b"); + jtnode = json_tree_node_get_child(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_NUMBER); + test_assert_strcmp(json_tree_node_get_name(jtnode), + "c"); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_OBJECT); + jtnode = json_tree_node_get_next(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_STRING); + test_assert(json_tree_node_get_next(jtnode) == NULL); + jtnode = json_tree_node_get_parent(jtnode); + test_assert(json_tree_node_get_type(jtnode) == + JSON_TYPE_ARRAY); + test_assert(json_tree_node_get_next(jtnode) == NULL); + + json_istream_ascend(jinput); + state++; + break; + } + } + test_assert(state == 5); + test_istream_set_size(input, text_len); + test_json_read_success(&jinput); + + test_end(); + + i_stream_unref(&input); + json_tree_unref(&jtree); +} + /* * Test: read stream */ @@ -3130,6 +3627,7 @@ static void test_json_istream_error(void) struct json_istream *jinput; const char *text, *error; struct json_node jnode; + struct json_tree *jtree; unsigned int text_len; int ret = 0; @@ -3193,6 +3691,24 @@ static void test_json_istream_error(void) json_istream_unref(&jinput); i_stream_unref(&input); + /* tree parse error */ + text = "{\"a\":[0],\"b\":[1],\"c\":[2],\"d\":[\"unclosed array\"}"; + text_len = strlen(text); + + input = i_stream_create_from_data(text, text_len); + jinput = json_istream_create(input, 0, NULL, 0); + + test_begin("json istream error - tree parse error"); + + ret = json_istream_read_tree(jinput, &jtree); + error = json_istream_get_error(jinput); + test_out_reason("read failure", (ret < 0 && error != NULL), error); + + test_end(); + + json_istream_unref(&jinput); + i_stream_unref(&input); + /* spurious data at end of input */ text = "[\"data\"][\"junk\"]"; text_len = strlen(text); @@ -3347,6 +3863,8 @@ int main(int argc, char *argv[]) test_json_istream_read_buffer, test_json_istream_read_trickle, test_json_istream_finish, + test_json_istream_read_tree, + test_json_istream_read_into_tree, test_json_istream_read_stream, test_json_istream_tokens_buffer, test_json_istream_tokens_trickle,