From: Stephan Bosch Date: Wed, 7 Aug 2019 19:49:25 +0000 (+0200) Subject: lib-json: json-ostream - Add support for writing JSON tree values X-Git-Tag: 2.4.0~2380 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1bc7fdfeaa976b160cbff1ee4dca9a4d06984043;p=thirdparty%2Fdovecot%2Fcore.git lib-json: json-ostream - Add support for writing JSON tree values --- diff --git a/src/lib-json/Makefile.am b/src/lib-json/Makefile.am index bcc815ae23..f694b4a706 100644 --- a/src/lib-json/Makefile.am +++ b/src/lib-json/Makefile.am @@ -29,7 +29,8 @@ test_programs = \ test-json-io \ test-json-istream \ test-json-ostream \ - test-json-tree + test-json-tree \ + test-json-tree-io noinst_PROGRAMS = $(test_programs) @@ -87,6 +88,13 @@ test_json_tree_LDADD = \ test_json_tree_DEPENDENCIES = \ $(test_deps) +test_json_tree_io_SOURCE = \ + test-json-tree-io.c +test_json_tree_io_LDADD = \ + $(test_libs) +test_json_tree_io_DEPENDENCIES = \ + $(test_deps) + pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) diff --git a/src/lib-json/json-ostream.c b/src/lib-json/json-ostream.c index c9a7844a33..9f7176fb98 100644 --- a/src/lib-json/json-ostream.c +++ b/src/lib-json/json-ostream.c @@ -25,6 +25,10 @@ struct json_ostream { enum json_ostream_node_state node_state; unsigned int write_node_level; + struct json_tree_walker *tree_walker; + struct json_tree *tree; + struct json_node tree_node; + struct json_data node_data; string_t *buffer; @@ -40,7 +44,12 @@ struct json_ostream { bool closed:1; }; +static int json_ostream_write_tree_more(struct json_ostream *stream); static int json_ostream_write_node_more(struct json_ostream *stream); +static int +json_ostream_do_write_node(struct json_ostream *stream, + const struct json_node *node, bool flush, + bool persist); struct json_ostream * json_ostream_create(struct ostream *output, @@ -100,6 +109,9 @@ void json_ostream_unref(struct json_ostream **_stream) o_stream_unref(&stream->output); str_free(&stream->buffer); + json_tree_walker_free(&stream->tree_walker); + json_tree_unref(&stream->tree); + i_free(stream->error); i_free(stream); } @@ -350,6 +362,16 @@ int json_ostream_flush(struct json_ostream *stream) if (ret <= 0) return ret; } + if (stream->tree_walker != NULL) { + ret = json_ostream_write_tree_more(stream); + if (ret <= 0) + return ret; + if (stream->node_state != JSON_OSTREAM_NODE_STATE_NONE) { + ret = json_ostream_write_node_more(stream); + if (ret <= 0) + return ret; + } + } if (json_node_is_none(&stream->node)) return json_generator_flush(stream->generator); ret = json_ostream_write_more(stream); @@ -985,6 +1007,88 @@ void json_ostream_nwrite_text_stream(struct json_ostream *stream, stream->nfailed = TRUE; } +static int json_ostream_write_tree_more(struct json_ostream *stream) +{ + int ret; + + i_assert(stream->tree_walker != NULL); + + for (;;) { + /* Walk the tree to the next node */ + if (json_node_is_none(&stream->tree_node) && + !json_tree_walk(stream->tree_walker, + &stream->tree_node)) { + json_tree_walker_free(&stream->tree_walker); + json_tree_unref(&stream->tree); + i_zero(&stream->tree_node); + return 1; + } + + ret = json_ostream_do_write_node(stream, &stream->tree_node, + FALSE, FALSE); + if (ret < 0) { + json_tree_walker_free(&stream->tree_walker); + json_tree_unref(&stream->tree); + i_zero(&stream->tree_node); + return -1; + } + if (ret == 0) + return ret; + i_zero(&stream->tree_node); + } + i_unreached(); +} + +static int +json_ostream_write_tree_init(struct json_ostream *stream, const char *name, + const struct json_tree *jtree) +{ + int ret; + + i_assert(jtree != NULL); + + ret = json_ostream_write_init(stream, name, JSON_TYPE_TEXT); + if (ret <= 0) + return ret; + + i_assert(stream->tree_walker == NULL); + stream->tree_walker = json_tree_walker_create(jtree); + i_zero(&stream->tree_node); + + return 1; +} + +int json_ostream_write_tree(struct json_ostream *stream, const char *name, + struct json_tree *jtree) +{ + int ret; + + ret = json_ostream_write_tree_init(stream, name, jtree); + if (ret <= 0) + return ret; + + ret = json_ostream_write_tree_more(stream); + if (stream->tree_walker != NULL) { + stream->tree = jtree; + json_tree_ref(jtree); + } + return 1; +} + +void json_ostream_nwrite_tree(struct json_ostream *stream, const char *name, + const struct json_tree *jtree) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_write_tree_init(stream, name, jtree); + if (ret > 0) + ret = json_ostream_write_tree_more(stream); + i_assert(ret <= 0 || stream->tree_walker == NULL); + json_ostream_nwrite_post(stream, ret); +} + /* * Nodes */ diff --git a/src/lib-json/json-ostream.h b/src/lib-json/json-ostream.h index fc865afe69..3d0356f63a 100644 --- a/src/lib-json/json-ostream.h +++ b/src/lib-json/json-ostream.h @@ -4,6 +4,7 @@ #include "lib.h" #include "json-types.h" +#include "json-tree.new.h" #include "json-generator.h" struct json_ostream; @@ -266,6 +267,18 @@ int json_ostream_write_text_stream(struct json_ostream *stream, void json_ostream_nwrite_text_stream(struct json_ostream *stream, const char *name, struct istream *input); +/* Write a JSON-text tree object to the stream. Returns 1 on success, + 0 if the stream buffer is full, or -1 upon error. Success does not mean + that the tree is already (fully) sent. It just means that the stream was + able to accept/buffer the tree object immediately. While the tree is still + being sent, the stream holds a reference to it. Sending an incompletely sent + tree continues once one of the write(), descend(), acsend(), or flush() + functions is called. */ +int json_ostream_write_tree(struct json_ostream *stream, const char *name, + struct json_tree *jtree); +void json_ostream_nwrite_tree(struct json_ostream *stream, const char *name, + const struct json_tree *jtree); + /* * String output stream */ diff --git a/src/lib-json/test-json-ostream.c b/src/lib-json/test-json-ostream.c index 8df070396a..14e95f3553 100644 --- a/src/lib-json/test-json-ostream.c +++ b/src/lib-json/test-json-ostream.c @@ -1243,6 +1243,1178 @@ static void test_json_ostream_nwrite(void) str_free(&buffer); } +static void test_json_ostream_write_tree(void) +{ + string_t *buffer; + struct istream *input; + struct ostream *output; + struct json_ostream *joutput; + struct json_tree *jtree, *jtree2; + struct json_tree_node *jtnode; + const char *data; + unsigned int state, pos; + int ret; + + buffer = str_new(default_pool, 256); + output = o_stream_create_buffer(buffer); + o_stream_set_no_error_handling(output, TRUE); + + /* number */ + test_begin("json ostream write tree - number"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_number_int( + json_tree_get_root(jtree), NULL, 23423); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("23423", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* false */ + test_begin("json ostream write tree - false"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_false( + json_tree_get_root(jtree), NULL); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("false", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* null */ + test_begin("json ostream write tree - null"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_null( + json_tree_get_root(jtree), NULL); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("null", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* true */ + test_begin("json ostream write tree - true"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_true( + json_tree_get_root(jtree), NULL); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("true", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string */ + test_begin("json ostream write tree - string"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_string( + json_tree_get_root(jtree), NULL, "frop"); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("\"frop\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string stream */ + test_begin("json ostream write tree - string stream"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream( + json_tree_get_root(jtree), NULL, input); + i_stream_unref(&input); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* array */ + test_begin("json ostream write tree - array"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("[]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string ] */ + test_begin("json ostream write tree - array [ string ]"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + (void)json_tree_node_add_string(jtnode, NULL, "frop"); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("[\"frop\"]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string stream ] */ + test_begin("json ostream write tree - array [ string stream ]"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, NULL, input); + i_stream_unref(&input); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "[\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"]", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* object */ + test_begin("json ostream write tree - object"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("{}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { member: string } */ + test_begin("json ostream write tree - object { member: string }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + (void)json_tree_node_add_string(jtnode, "frop", "friep"); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("{\"frop\":\"friep\"}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { member: string stream } */ + test_begin("json ostream write tree - object { member: string stream }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, "frop", input); + i_stream_unref(&input); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "{\"frop\":" + "\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream write tree - object " + "{ \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], " + "\"c\": [{\"f\": 3}] }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + jtnode = json_tree_node_add_array(jtnode, "a"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "b"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "c"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream write tree - descended"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + ret = json_ostream_write_tree(joutput, "a", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + ret = json_ostream_write_tree(joutput, "b", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + ret = json_ostream_write_tree(joutput, "c", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_ascend_object(joutput); + i_assert(ret > 0); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* descended, strings */ + test_begin("json ostream write tree - descended, strings"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "d", "gggggggggg"); + ret = json_ostream_write_tree(joutput, "a", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "e", "hhhhhhhhhh"); + ret = json_ostream_write_tree(joutput, "b", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "f", "iiiiiiiiii"); + ret = json_ostream_write_tree(joutput, "c", jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_ascend_object(joutput); + i_assert(ret > 0); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("{\"a\":[{\"d\":\"gggggggggg\"}]," + "\"b\":[{\"e\":\"hhhhhhhhhh\"}]," + "\"c\":[{\"f\":\"iiiiiiiiii\"}]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream write tree - nested trees"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_object(jtnode, NULL); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "a", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "b", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "c", jtree2); + json_tree_unref(&jtree2); + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret > 0); + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [1] */ + test_begin("json ostream write tree - object, trickle[1]"); + o_stream_set_max_buffer_size(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + jtnode = json_tree_node_add_array(jtnode, "aaaaaa"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "dddddd", 1); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "bbbbbb"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "eeeeee", 2); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "cccccc"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "ffffff", 3); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 1; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 1: + ret = json_ostream_flush(joutput); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 2); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1}]," + "\"bbbbbb\":[{\"eeeeee\":2}]," + "\"cccccc\":[{\"ffffff\":3}]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [2] */ + test_begin("json ostream write tree - object, trickle[2]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 7; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + case 1: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int( + jtnode, "dddddd", 1); + state++; + /* fall through */ + case 2: + ret = json_ostream_write_tree(joutput, "aaaaaa", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 3: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int( + jtnode, "eeeeee", 2); + state++; + /* fall through */ + case 4: + ret = json_ostream_write_tree(joutput, "bbbbbb", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 5: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int( + jtnode, "ffffff", 3); + state++; + /* fall through */ + case 6: + ret = json_ostream_write_tree(joutput, "cccccc", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 7: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 8); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1}]," + "\"bbbbbb\":[{\"eeeeee\":2}]," + "\"cccccc\":[{\"ffffff\":3}]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle[3] */ + test_begin("json ostream write tree - object, trickle[3]"); + o_stream_set_max_buffer_size(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_object(jtnode, NULL); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "dddddd", 1); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "aaaaaa", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "eeeeee", 2); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "bbbbbb", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "ffffff", 3); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "cccccc", jtree2); + json_tree_unref(&jtree2); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 1; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 1: + ret = json_ostream_flush(joutput); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 2); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1}]," + "\"bbbbbb\":[{\"eeeeee\":2}]," + "\"cccccc\":[{\"ffffff\":3}]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [4] */ + test_begin("json ostream write tree - object, trickle[4]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 7; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + case 1: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string( + jtnode, "dddddd", "gggggggggg"); + state++; + /* fall through */ + case 2: + ret = json_ostream_write_tree(joutput, "aaaaaa", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 3: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string( + jtnode, "eeeeee", "hhhhhhhhhh"); + state++; + /* fall through */ + case 4: + ret = json_ostream_write_tree(joutput, "bbbbbb", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 5: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string( + jtnode, "ffffff", "iiiiiiiiii"); + state++; + /* fall through */ + case 6: + ret = json_ostream_write_tree(joutput, "cccccc", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 7: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 8); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":\"gggggggggg\"}]," + "\"bbbbbb\":[{\"eeeeee\":\"hhhhhhhhhh\"}]," + "\"cccccc\":[{\"ffffff\":\"iiiiiiiiii\"}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [5] */ + test_begin("json ostream write tree - object, trickle[5]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 7; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + case 1: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_text( + jtnode, "dddddd", "1234567"); + state++; + /* fall through */ + case 2: + ret = json_ostream_write_tree(joutput, "aaaaaa", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 3: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_text( + jtnode, "eeeeee", "[1,2,3,4,5,6,7]"); + state++; + /* fall through */ + case 4: + ret = json_ostream_write_tree(joutput, "bbbbbb", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 5: + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_text( + jtnode, "ffffff", "\"1234567\""); + state++; + /* fall through */ + case 6: + ret = json_ostream_write_tree(joutput, "cccccc", jtree); + i_assert(ret >= 0); + if (ret == 0) + break; + json_tree_unref(&jtree); + state++; + continue; + case 7: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) + break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 8); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1234567}]," + "\"bbbbbb\":[{\"eeeeee\":[1,2,3,4,5,6,7]}]," + "\"cccccc\":[{\"ffffff\":\"1234567\"}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle [6] */ + test_begin("json ostream write tree - object, trickle[6]"); + o_stream_set_max_buffer_size(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + jtnode = json_tree_node_add_array(jtnode, "aaaaaa"); + jtnode = json_tree_node_add_object(jtnode, NULL); + data = "AAAAA"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, "dddddd", input); + i_stream_unref(&input); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "bbbbbb"); + jtnode = json_tree_node_add_object(jtnode, NULL); + data = "BBBBB"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, "eeeeee", input); + i_stream_unref(&input); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "cccccc"); + jtnode = json_tree_node_add_object(jtnode, NULL); + data = "CCCCC"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, "ffffff", input); + i_stream_unref(&input); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 1; pos++) { + o_stream_set_max_buffer_size(output, pos); + switch (state) { + case 0: + ret = json_ostream_write_tree(joutput, NULL, jtree); + i_assert(ret >= 0); + if (ret == 0) break; + json_tree_unref(&jtree); + state++; + continue; + case 1: + ret = json_ostream_flush(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert(state == 2); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":\"AAAAA\"}]," + "\"bbbbbb\":[{\"eeeeee\":\"BBBBB\"}]," + "\"cccccc\":[{\"ffffff\":\"CCCCC\"}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + o_stream_destroy(&output); + str_free(&buffer); +} + +static void test_json_ostream_nwrite_tree(void) +{ + string_t *buffer; + struct istream *input; + struct ostream *output; + struct json_ostream *joutput; + struct json_tree *jtree, *jtree2; + struct json_tree_node *jtnode; + const char *data; + + buffer = str_new(default_pool, 256); + output = o_stream_create_buffer(buffer); + o_stream_set_no_error_handling(output, TRUE); + + /* number */ + test_begin("json ostream nwrite tree - number"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_number_int( + json_tree_get_root(jtree), NULL, 23423); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("23423", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* false */ + test_begin("json ostream nwrite tree - false"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_false( + json_tree_get_root(jtree), NULL); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("false", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* null */ + test_begin("json ostream nwrite tree - null"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_null( + json_tree_get_root(jtree), NULL); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("null", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* true */ + test_begin("json ostream nwrite tree - true"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_true( + json_tree_get_root(jtree), NULL); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("true", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string */ + test_begin("json ostream nwrite tree - string"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_string( + json_tree_get_root(jtree), NULL, "frop"); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("\"frop\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* string stream */ + test_begin("json ostream nwrite tree - string stream"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream( + json_tree_get_root(jtree), NULL, input); + i_stream_unref(&input); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* array */ + test_begin("json ostream nwrite tree - array"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string ] */ + test_begin("json ostream nwrite tree - array [ string ]"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + (void)json_tree_node_add_string(jtnode, NULL, "frop"); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[\"frop\"]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ string stream ] */ + test_begin("json ostream nwrite tree - array [ string stream ]"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_array( + json_tree_get_root(jtree), NULL); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, NULL, input); + i_stream_unref(&input); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "[\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"]", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* object */ + test_begin("json ostream nwrite tree - object"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + (void)json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { member: string } */ + test_begin("json ostream nwrite tree - object { member: string }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + (void)json_tree_node_add_string(jtnode, "frop", "friep"); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{\"frop\":\"friep\"}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { member: string stream } */ + test_begin("json ostream nwrite tree - " + "object { member: string stream }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ"; + input = i_stream_create_from_data(data, strlen(data)); + (void)json_tree_node_add_string_stream(jtnode, "frop", input); + i_stream_unref(&input); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "{\"frop\":" + "\"AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\"}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream nwrite tree - object " + "{ \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], " + "\"c\": [{\"f\": 3}] }"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_node_add_object( + json_tree_get_root(jtree), NULL); + jtnode = json_tree_node_add_array(jtnode, "a"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "b"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_get_parent(jtnode); + jtnode = json_tree_node_add_array(jtnode, "c"); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream nwrite tree - descended"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + json_ostream_nwrite_tree(joutput, "a", jtree); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + json_ostream_nwrite_tree(joutput, "b", jtree); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + json_ostream_nwrite_tree(joutput, "c", jtree); + json_tree_unref(&jtree); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* descended, strings */ + test_begin("json ostream nwrite tree - descended, strings"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "d", "gggggggggg"); + json_ostream_nwrite_tree(joutput, "a", jtree); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "e", "hhhhhhhhhh"); + json_ostream_nwrite_tree(joutput, "b", jtree); + json_tree_unref(&jtree); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_string(jtnode, "f", "iiiiiiiiii"); + json_ostream_nwrite_tree(joutput, "c", jtree); + json_tree_unref(&jtree); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{\"a\":[{\"d\":\"gggggggggg\"}]," + "\"b\":[{\"e\":\"hhhhhhhhhh\"}]," + "\"c\":[{\"f\":\"iiiiiiiiii\"}]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */ + test_begin("json ostream nwrite tree - nested trees"); + joutput = json_ostream_create(output, 0); + jtree = json_tree_create(); + jtnode = json_tree_get_root(jtree); + jtnode = json_tree_node_add_object(jtnode, NULL); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "d", 1); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "a", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "e", 2); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "b", jtree2); + json_tree_unref(&jtree2); + jtree2 = json_tree_create(); + jtnode = json_tree_get_root(jtree2); + jtnode = json_tree_node_add_array(jtnode, NULL); + jtnode = json_tree_node_add_object(jtnode, NULL); + (void)json_tree_node_add_number_int(jtnode, "f", 3); + jtnode = json_tree_get_root(jtree); + json_tree_node_add_subtree(jtnode, "c", jtree2); + json_tree_unref(&jtree2); + json_ostream_nwrite_tree(joutput, NULL, jtree); + json_tree_unref(&jtree); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "{\"a\":[{\"d\":1}],\"b\":[{\"e\":2}],\"c\":[{\"f\":3}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + o_stream_destroy(&output); + str_free(&buffer); +} + int main(int argc, char *argv[]) { int c; @@ -1250,6 +2422,8 @@ int main(int argc, char *argv[]) static void (*test_functions[])(void) = { test_json_ostream_write, test_json_ostream_nwrite, + test_json_ostream_write_tree, + test_json_ostream_nwrite_tree, NULL }; diff --git a/src/lib-json/test-json-tree-io.c b/src/lib-json/test-json-tree-io.c new file mode 100644 index 0000000000..2bb8a5cd79 --- /dev/null +++ b/src/lib-json/test-json-tree-io.c @@ -0,0 +1,446 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "test-common.h" + +#include "json-istream.h" +#include "json-ostream.h" + +#include + +static bool debug = FALSE; + +struct json_io_test { + const char *input; + const char *output; + struct json_limits limits; + enum json_parser_flags flags; +}; + +static const struct json_io_test +tests[] = { + { + .input = "{\"kty\":\"EC\"," + "\"crv\":\"P-256\"," + "\"x\":\"Kp0Y4-Wpt-D9t_2XenFIj0LmvaZByLG69yOisek4aMI\"," + "\"y\":\"wjEPB5BhH5SRPw1cCN5grWrLCphrW19fCFR8p7c9O5o\"," + "\"use\":\"sig\"," + "\"kid\":\"123\"," + "\"d\":\"Po2z9rs86J2Qb_xWprr4idsWNPlgKf3G8-mftnE2ync\"" + "}", + }, + { + .input = + "{\r\n" + " \"$schema\": \"http://json-schema.org/draft-06/schema#\",\r\n" + " \"$id\": \"http://json-schema.org/draft-06/schema#\",\r\n" + " \"title\": \"Core schema meta-schema\",\r\n" + " \"definitions\": {\r\n" + " \"schemaArray\": {\r\n" + " \"type\": \"array\",\r\n" + " \"minItems\": 1,\r\n" + " \"items\": { \"$ref\": \"#\" }\r\n" + " },\r\n" + " \"nonNegativeInteger\": {\r\n" + " \"type\": \"integer\",\r\n" + " \"minimum\": 0\r\n" + " },\r\n" + " \"nonNegativeIntegerDefault0\": {\r\n" + " \"allOf\": [\r\n" + " { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " { \"default\": 0 }\r\n" + " ]\r\n" + " },\r\n" + " \"simpleTypes\": {\r\n" + " \"enum\": [\r\n" + " \"array\",\r\n" + " \"boolean\",\r\n" + " \"integer\",\r\n" + " \"null\",\r\n" + " \"number\",\r\n" + " \"object\",\r\n" + " \"string\"\r\n" + " ]\r\n" + " },\r\n" + " \"stringArray\": {\r\n" + " \"type\": \"array\",\r\n" + " \"items\": { \"type\": \"string\" },\r\n" + " \"uniqueItems\": true,\r\n" + " \"default\": []\r\n" + " }\r\n" + " },\r\n" + " \"type\": [\"object\", \"boolean\"],\r\n" + " \"properties\": {\r\n" + " \"$id\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri-reference\"\r\n" + " },\r\n" + " \"$schema\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri\"\r\n" + " },\r\n" + " \"$ref\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"uri-reference\"\r\n" + " },\r\n" + " \"title\": {\r\n" + " \"type\": \"string\"\r\n" + " },\r\n" + " \"description\": {\r\n" + " \"type\": \"string\"\r\n" + " },\r\n" + " \"default\": {},\r\n" + " \"multipleOf\": {\r\n" + " \"type\": \"number\",\r\n" + " \"exclusiveMinimum\": 0\r\n" + " },\r\n" + " \"maximum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"exclusiveMaximum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"minimum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"exclusiveMinimum\": {\r\n" + " \"type\": \"number\"\r\n" + " },\r\n" + " \"maxLength\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minLength\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"pattern\": {\r\n" + " \"type\": \"string\",\r\n" + " \"format\": \"regex\"\r\n" + " },\r\n" + " \"additionalItems\": { \"$ref\": \"#\" },\r\n" + " \"items\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#\" },\r\n" + " { \"$ref\": \"#/definitions/schemaArray\" }\r\n" + " ],\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"maxItems\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minItems\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"uniqueItems\": {\r\n" + " \"type\": \"boolean\",\r\n" + " \"default\": false\r\n" + " },\r\n" + " \"contains\": { \"$ref\": \"#\" },\r\n" + " \"maxProperties\": { \"$ref\": \"#/definitions/nonNegativeInteger\" },\r\n" + " \"minProperties\": { \"$ref\": \"#/definitions/nonNegativeIntegerDefault0\" },\r\n" + " \"required\": { \"$ref\": \"#/definitions/stringArray\" },\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"definitions\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"properties\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"patternProperties\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": { \"$ref\": \"#\" },\r\n" + " \"default\": {}\r\n" + " },\r\n" + " \"dependencies\": {\r\n" + " \"type\": \"object\",\r\n" + " \"additionalProperties\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#\" },\r\n" + " { \"$ref\": \"#/definitions/stringArray\" }\r\n" + " ]\r\n" + " }\r\n" + " },\r\n" + " \"propertyNames\": { \"$ref\": \"#\" },\r\n" + " \"const\": {},\r\n" + " \"enum\": {\r\n" + " \"type\": \"array\",\r\n" + " \"minItems\": 1,\r\n" + " \"uniqueItems\": true\r\n" + " },\r\n" + " \"type\": {\r\n" + " \"anyOf\": [\r\n" + " { \"$ref\": \"#/definitions/simpleTypes\" },\r\n" + " {\r\n" + " \"type\": \"array\",\r\n" + " \"items\": { \"$ref\": \"#/definitions/simpleTypes\" },\r\n" + " \"minItems\": 1,\r\n" + " \"uniqueItems\": true\r\n" + " }\r\n" + " ]\r\n" + " },\r\n" + " \"format\": { \"type\": \"string\" },\r\n" + " \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" },\r\n" + " \"not\": { \"$ref\": \"#\" }\r\n" + " },\r\n" + " \"default\": {}\r\n" + "}\r\n", + .output = + "{\"$schema\":\"http://json-schema.org/draft-06/schema#\"," + "\"$id\":\"http://json-schema.org/draft-06/schema#\"," + "\"title\":\"Core schema meta-schema\",\"definitions\":{" + "\"schemaArray\":{\"type\":\"array\",\"minItems\":1," + "\"items\":{\"$ref\":\"#\"}},\"nonNegativeInteger\":{" + "\"type\":\"integer\",\"minimum\":0}," + "\"nonNegativeIntegerDefault0\":{\"allOf\":[" + "{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "{\"default\":0}]},\"simpleTypes\":{\"enum\":[" + "\"array\",\"boolean\",\"integer\",\"null\"," + "\"number\",\"object\",\"string\"]},\"stringArray\":{" + "\"type\":\"array\",\"items\":{\"type\":\"string\"}," + "\"uniqueItems\":true,\"default\":[]}}," + "\"type\":[\"object\",\"boolean\"]," + "\"properties\":{\"$id\":{\"type\":\"string\"," + "\"format\":\"uri-reference\"},\"$schema\":{" + "\"type\":\"string\",\"format\":\"uri\"}," + "\"$ref\":{\"type\":\"string\",\"format\":\"uri-reference\"" + "},\"title\":{\"type\":\"string\"},\"description\":{" + "\"type\":\"string\"},\"default\":{},\"multipleOf\":{" + "\"type\":\"number\",\"exclusiveMinimum\":0}," + "\"maximum\":{\"type\":\"number\"},\"exclusiveMaximum\":{" + "\"type\":\"number\"},\"minimum\":{\"type\":\"number\"" + "},\"exclusiveMinimum\":{\"type\":\"number\"}," + "\"maxLength\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minLength\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"pattern\":{\"type\":\"string\",\"format\":\"regex\"" + "},\"additionalItems\":{\"$ref\":\"#\"},\"items\":{" + "\"anyOf\":[{\"$ref\":\"#\"},{\"$ref\":\"#/definitions/schemaArray\"}" + "],\"default\":{}}," + "\"maxItems\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minItems\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"uniqueItems\":{\"type\":\"boolean\",\"default\":false}," + "\"contains\":{\"$ref\":\"#\"}," + "\"maxProperties\":{\"$ref\":\"#/definitions/nonNegativeInteger\"}," + "\"minProperties\":{\"$ref\":\"#/definitions/nonNegativeIntegerDefault0\"}," + "\"required\":{\"$ref\":\"#/definitions/stringArray\"}," + "\"additionalProperties\":{\"$ref\":\"#\"},\"definitions\":{" + "\"type\":\"object\",\"additionalProperties\":{\"$ref\":\"#\"}," + "\"default\":{}},\"properties\":{\"type\":\"object\"," + "\"additionalProperties\":{\"$ref\":\"#\"},\"default\":{}" + "},\"patternProperties\":{\"type\":\"object\"," + "\"additionalProperties\":{\"$ref\":\"#\"}," + "\"default\":{}},\"dependencies\":{\"type\":\"object\"," + "\"additionalProperties\":{\"anyOf\":[{\"$ref\":\"#\"}," + "{\"$ref\":\"#/definitions/stringArray\"}" + "]}},\"propertyNames\":{\"$ref\":\"#\"},\"const\":{}," + "\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true" + "},\"type\":{\"anyOf\":[{\"$ref\":\"#/definitions/simpleTypes\"}," + "{\"type\":\"array\",\"items\":{\"$ref\":\"#/definitions/simpleTypes\"}," + "\"minItems\":1,\"uniqueItems\":true}]},\"format\":{\"type\":\"string\"}," + "\"allOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"anyOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"oneOf\":{\"$ref\":\"#/definitions/schemaArray\"}," + "\"not\":{\"$ref\":\"#\"}},\"default\":{}}" + } +}; + +static const unsigned tests_count = N_ELEMENTS(tests); + +static void test_json_tree_stream_io(void) +{ + string_t *outbuf; + unsigned int i; + + outbuf = str_new(default_pool, 1024); + + for (i = 0; i < tests_count; i++) T_BEGIN { + const struct json_io_test *test; + const char *text, *text_out; + unsigned int pos, text_len; + struct istream *input; + struct ostream *output; + struct json_istream *jinput; + struct json_ostream *joutput; + struct json_tree *jtree; + const char *error = NULL; + int ret = 0; + + test = &tests[i]; + text = test->input; + text_out = test->output; + if (text_out == NULL) + text_out = test->input; + text_len = strlen(text); + + test_begin(t_strdup_printf("json tree stream io [%d]", i)); + + buffer_set_used_size(outbuf, 0); + + input = test_istream_create_data(text, text_len); + output = o_stream_create_buffer(outbuf); + o_stream_set_no_error_handling(output, TRUE); + + jinput = json_istream_create(input, 0, NULL, 0); + joutput = json_ostream_create(output, 0); + + o_stream_set_max_buffer_size(output, 0); + ret = 0; + for (pos = 0; pos <= text_len && ret == 0; pos++) { + test_istream_set_size(input, pos); + ret = json_istream_read_tree(jinput, &jtree); + } + test_assert(ret > 0); + + test_istream_set_size(input, text_len); + ret = json_istream_finish(&jinput, &error); + test_out_reason_quiet("input stream ok (trickle)", + ret > 0, error); + + ret = 0; + for (pos = 0; pos <= 65535 && ret == 0; pos++) { + o_stream_set_max_buffer_size(output, pos); + if (jtree != NULL) { + ret = json_ostream_write_tree(joutput, NULL, jtree); + if (ret > 0) + json_tree_unref(&jtree); + } + if (jtree == NULL) + ret = json_ostream_flush(joutput); + } + json_ostream_unref(&joutput); + test_out_quiet("output stream ok (trickle)", ret > 0); + + test_out_quiet("io match (trickle)", + strcmp(text_out, str_c(outbuf)) == 0); + + if (debug) { + i_debug("OUT: >%s<", text_out); + i_debug("OUT: >%s<", str_c(outbuf)); + } + + json_tree_unref(&jtree); + i_stream_unref(&input); + o_stream_unref(&output); + + buffer_set_used_size(outbuf, 0); + + input = test_istream_create_data(text, text_len); + output = o_stream_create_buffer(outbuf); + o_stream_set_no_error_handling(output, TRUE); + + jinput = json_istream_create(input, 0, NULL, 0); + joutput = json_ostream_create(output, 0); + + ret = json_istream_read_tree(jinput, &jtree); + test_assert(ret > 0); + + ret = json_istream_finish(&jinput, &error); + test_out_reason_quiet("input stream ok (buffer)", + ret > 0, error); + + if (jtree != NULL) { + ret = json_ostream_write_tree(joutput, NULL, jtree); + if (ret > 0) { + json_tree_unref(&jtree); + ret = json_ostream_flush(joutput); + } + } + json_ostream_unref(&joutput); + test_out_quiet("output stream ok (buffer)", ret > 0); + + test_out_quiet("io match (buffer)", + strcmp(text_out, str_c(outbuf)) == 0); + + if (debug) { + i_debug("OUT: >%s<", text_out); + i_debug("OUT: >%s<", str_c(outbuf)); + } + + json_tree_unref(&jtree); + i_stream_unref(&input); + o_stream_unref(&output); + + test_end(); + + } T_END; + + buffer_free(&outbuf); +} + +static void test_json_tree_file(const char *file) +{ + struct istream *input; + struct ostream *output; + struct json_istream *jinput; + struct json_ostream *joutput; + struct json_tree *jtree; + int ret = 0; + + input = i_stream_create_file(file, 1024); + output = o_stream_create_fd(1, 1024); + o_stream_set_no_error_handling(output, TRUE); + + jinput = json_istream_create(input, 0, NULL, + JSON_PARSER_FLAG_NUMBERS_AS_STRING); + joutput = json_ostream_create(output, 0); + + ret = 0; + while (ret == 0) + ret = json_istream_read_tree(jinput, &jtree); + + if (ret < 0) { + i_fatal("Failed to read JSON: %s", + json_istream_get_error(jinput)); + } + + ret = 0; + while (ret == 0) { + if (jtree != NULL) { + ret = json_ostream_write_tree(joutput, NULL, jtree); + if (ret > 0) + json_tree_unref(&jtree); + } + if (jtree == NULL) + ret = json_ostream_flush(joutput); + } + + if (ret < 0) { + i_fatal("Failed to write JSON: %s", + o_stream_get_error(output)); + } + + json_istream_unref(&jinput); + json_ostream_unref(&joutput); + + o_stream_nsend_str(output, "\n"); + i_stream_unref(&input); + o_stream_unref(&output); +} + +int main(int argc, char *argv[]) +{ + int c; + + static void (*test_functions[])(void) = { + test_json_tree_stream_io, + NULL + }; + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + test_json_tree_file(argv[0]); + return 0; + } + + return test_run(test_functions); +}