]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-json: json-generator - Add support for formatted JSON output
authorStephan Bosch <stephan.bosch@open-xchange.com>
Sat, 22 Feb 2020 13:49:31 +0000 (14:49 +0100)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Sat, 18 Nov 2023 18:58:04 +0000 (18:58 +0000)
src/lib-json/json-generator.c
src/lib-json/json-generator.h
src/lib-json/json-ostream.c
src/lib-json/json-ostream.h
src/lib-json/json-tree-io.c
src/lib-json/json-tree-io.h
src/lib-json/json-types.h
src/lib-json/test-json-generator.c
src/lib-json/test-json-io.c
src/lib-json/test-json-tree-io.c

index 225574ddd62cf69286b016c02e6c07aedb1a2fa7..93c8c8480fa23cb9c9a8d8c9fe4626125a02aec9 100644 (file)
@@ -28,6 +28,15 @@ enum json_generator_state {
        JSON_GENERATOR_STATE_END,
 };
 
+enum json_format_state {
+       JSON_FORMAT_STATE_NONE = 0,
+       JSON_FORMAT_STATE_INDENT,
+       JSON_FORMAT_STATE_SPACE,
+       JSON_FORMAT_STATE_CR,
+       JSON_FORMAT_STATE_LF,
+       JSON_FORMAT_STATE_DONE,
+};
+
 struct json_generator_level {
        bool object:1;
 };
@@ -55,6 +64,12 @@ struct json_generator {
        /* Write state: stack position of written syntax levels */
        unsigned int level_stack_written;
 
+       /* Formatting state */
+       struct json_format format;
+       char *format_indent;
+       enum json_format_state format_state;
+       unsigned int indent_pos, indent_count;
+
        /* Currently opened string output stream */
        struct json_string_ostream *str_stream;
        /* Currently pending string input stream */
@@ -72,6 +87,8 @@ struct json_generator {
        bool text_stream:1;           /* API state */
        /* A json_string_ostream is running the generator */
        bool streaming:1;
+       /* Finish writing formatting whitespace element */
+       bool format_finish:1;
 };
 
 static int json_generator_flush_string_input(struct json_generator *generator);
@@ -129,9 +146,26 @@ void json_generator_deinit(struct json_generator **_generator)
                str_free(&generator->buf);
        }
        array_free(&generator->level_stack);
+       i_free(generator->format_indent);
        i_free(generator);
 }
 
+void json_generator_set_format(struct json_generator *generator,
+                               const struct json_format *format)
+{
+       i_assert(generator->state == JSON_GENERATOR_STATE_VALUE);
+       i_assert(generator->write_state == JSON_GENERATOR_STATE_VALUE);
+       generator->format = *format;
+
+       i_free(generator->format_indent);
+       if (format->indent_chars > 0) {
+               generator->format_indent = i_malloc(format->indent_chars);
+               memset(generator->format_indent,
+                      (format->indent_tab ? '\t' : ' '),
+                      format->indent_chars);
+       }
+}
+
 static inline size_t
 json_generator_bytes_available(struct json_generator *generator)
 {
@@ -252,6 +286,94 @@ static int json_generator_flush_buffer(struct json_generator *generator)
        return 1;
 }
 
+static int json_generator_flush_format(struct json_generator *generator)
+{
+       int ret;
+
+       for (;;) switch (generator->format_state) {
+       case JSON_FORMAT_STATE_NONE:
+               return 1;
+       case JSON_FORMAT_STATE_CR:
+               ret = json_generator_write_all(generator, "\r", 1);
+               if (ret <= 0)
+                       return ret;
+               generator->format_state = JSON_FORMAT_STATE_LF;
+               /* fall through */
+       case JSON_FORMAT_STATE_LF:
+               ret = json_generator_write_all(generator, "\n", 1);
+               if (ret <= 0)
+                       return ret;
+               if (generator->format.indent_chars == 0) {
+                       generator->format_state = JSON_FORMAT_STATE_DONE;
+                       break;
+               }
+               generator->format_state = JSON_FORMAT_STATE_INDENT;
+               /* fall through */
+       case JSON_FORMAT_STATE_INDENT:
+               i_assert(generator->format.indent_chars != 0);
+               while (generator->indent_pos < generator->indent_count) {
+                       ret = json_generator_write_buffered(
+                               generator, generator->format_indent,
+                               generator->format.indent_chars, FALSE);
+                       if (ret <= 0)
+                               return -1;
+                       generator->indent_pos++;
+               }
+               generator->format_state = JSON_FORMAT_STATE_DONE;
+               break;
+       case JSON_FORMAT_STATE_SPACE:
+               ret = json_generator_write_all(generator, " ", 1);
+               if (ret <= 0)
+                       return ret;
+               generator->format_state = JSON_FORMAT_STATE_DONE;
+               break;
+       case JSON_FORMAT_STATE_DONE:
+               if (!generator->format_finish)
+                       return 1;
+               generator->format_state = JSON_FORMAT_STATE_NONE;
+               break;
+       }
+       i_unreached();
+}
+
+static int
+json_generator_write_newline(struct json_generator *generator,
+                             unsigned int indent_count, bool finish)
+{
+       if (generator->format_state == JSON_FORMAT_STATE_DONE)
+               return 1;
+       i_assert(generator->format_state == JSON_FORMAT_STATE_NONE);
+       if (!generator->format.new_line)
+               return 1;
+       if (generator->format.crlf)
+               generator->format_state = JSON_FORMAT_STATE_CR;
+       else
+               generator->format_state = JSON_FORMAT_STATE_LF;
+       generator->indent_pos = 0;
+       generator->indent_count = indent_count;
+       generator->format_finish = finish;
+       return json_generator_flush_format(generator);
+}
+
+static int
+json_generator_write_space(struct json_generator *generator,
+                           bool finish)
+{
+       if (generator->format_state == JSON_FORMAT_STATE_DONE)
+               return 1;
+       i_assert(generator->format_state == JSON_FORMAT_STATE_NONE);
+       if (!generator->format.whitespace)
+               return 1;
+       generator->format_state = JSON_FORMAT_STATE_SPACE;
+       generator->format_finish = finish;
+       return json_generator_flush_format(generator);
+}
+
+static void json_generator_finish_format(struct json_generator *generator)
+{
+       generator->format_state = JSON_FORMAT_STATE_NONE;
+}
+
 int json_generator_flush(struct json_generator *generator)
 {
        bool hide_root = HAS_ALL_BITS(generator->flags,
@@ -260,6 +382,10 @@ int json_generator_flush(struct json_generator *generator)
 
        /* Flush buffer */
        ret = json_generator_flush_buffer(generator);
+       if (ret <= 0)
+               return ret;
+       /* Flush formatting whitespace */
+       ret = json_generator_flush_format(generator);
        if (ret <= 0)
                return ret;
        /* Flush closing string */
@@ -276,6 +402,9 @@ int json_generator_flush(struct json_generator *generator)
                if (ret <= 0)
                        return ret;
                generator->write_state = JSON_GENERATOR_STATE_VALUE;
+               ret = json_generator_write_space(generator, TRUE);
+               if (ret <= 0)
+                       return ret;
        }
        /* Flush opening objects/arrays */
        for (;;) {
@@ -296,6 +425,11 @@ int json_generator_flush(struct json_generator *generator)
                        if (ret <= 0)
                                return ret;
                        generator->write_state = JSON_GENERATOR_STATE_VALUE;
+                       ret = json_generator_write_newline(
+                               generator, generator->level_stack_written,
+                               TRUE);
+                       if (ret <= 0)
+                               return ret;
                }
 
                // FIXME: add indent
@@ -321,6 +455,10 @@ int json_generator_flush(struct json_generator *generator)
                        generator->object_level_written = FALSE;
                        generator->write_state = JSON_GENERATOR_STATE_VALUE;
                }
+               ret = json_generator_write_newline(
+                       generator, generator->level_stack_written, TRUE);
+               if (ret <= 0)
+                       return ret;
        }
        /* Flush separator */
        switch (generator->write_state) {
@@ -328,6 +466,11 @@ int json_generator_flush(struct json_generator *generator)
        case JSON_GENERATOR_STATE_VALUE_END:
                if (generator->level_stack_pos == 0) {
                        generator->write_state = JSON_GENERATOR_STATE_END;
+                       ret = json_generator_write_newline(
+                               generator, generator->level_stack_written,
+                               TRUE);
+                       if (ret <= 0)
+                               return ret;
                        break;
                }
                if (generator->state != JSON_GENERATOR_STATE_STRING &&
@@ -345,6 +488,10 @@ int json_generator_flush(struct json_generator *generator)
                } else {
                        generator->write_state = JSON_GENERATOR_STATE_VALUE;
                }
+               ret = json_generator_write_newline(
+                       generator, generator->level_stack_written, TRUE);
+               if (ret <= 0)
+                       return ret;
                break;
        /* Flush colon */
        case JSON_GENERATOR_STATE_OBJECT_VALUE:
@@ -352,6 +499,9 @@ int json_generator_flush(struct json_generator *generator)
                if (ret <= 0)
                        return ret;
                generator->write_state = JSON_GENERATOR_STATE_VALUE;
+               ret = json_generator_write_space(generator, TRUE);
+               if (ret <= 0)
+                       return ret;
                break;
        default:
                break;
@@ -858,6 +1008,10 @@ int json_generate_array_close(struct json_generator *generator)
                 generator->write_state == JSON_GENERATOR_STATE_VALUE_END);
 
        i_assert(generator->level_stack_written > 0);
+       ret = json_generator_write_newline(
+               generator, generator->level_stack_written - 1, FALSE);
+       if (ret <= 0)
+               return ret;
        if (!hide_root || generator->level_stack_written > 1) {
                ret = json_generator_write_all(generator, "]", 1);
                if (ret <= 0)
@@ -865,6 +1019,7 @@ int json_generate_array_close(struct json_generator *generator)
        }
        json_generator_level_close(generator, FALSE);
        json_generator_value_end(generator);
+       json_generator_finish_format(generator);
        return 1;
 }
 
@@ -920,6 +1075,10 @@ int json_generate_object_close(struct json_generator *generator)
        i_assert(generator->write_state == JSON_GENERATOR_STATE_OBJECT_MEMBER ||
                 generator->write_state == JSON_GENERATOR_STATE_VALUE_END);
        i_assert(generator->level_stack_written > 0);
+       ret = json_generator_write_newline(
+               generator, generator->level_stack_written - 1, FALSE);
+       if (ret <= 0)
+               return ret;
        if (!hide_root || generator->level_stack_written > 1) {
                ret = json_generator_write_all(generator, "}", 1);
                if (ret <= 0)
@@ -927,6 +1086,7 @@ int json_generate_object_close(struct json_generator *generator)
        }
        json_generator_level_close(generator, TRUE);
        json_generator_value_end(generator);
+       json_generator_finish_format(generator);
        return 1;
 }
 
index d8a70864a2096faf6fa4338f72bcea404aed1384..7a833d99d6ca1df060013085956e441cae35a0fd 100644 (file)
@@ -25,6 +25,9 @@ json_generator_init_str(string_t *buf, enum json_generator_flags flags);
 
 void json_generator_deinit(struct json_generator **_generator);
 
+void json_generator_set_format(struct json_generator *generator,
+                               const struct json_format *format);
+
 int json_generator_flush(struct json_generator *generator);
 
 /* number */
index 777073c39be2591d76d5c01702d868caddbf3518..5658e05b9ddd946c1d50d5a903eb3674306d1883 100644 (file)
@@ -166,6 +166,12 @@ bool json_ostream_is_closed(struct json_ostream *stream)
        return stream->closed;
 }
 
+void json_ostream_set_format(struct json_ostream *stream,
+                            const struct json_format *format)
+{
+       json_generator_set_format(stream->generator, format);
+}
+
 void json_ostream_cork(struct json_ostream *stream)
 {
        if (stream->output != NULL)
index bc0d5baf4be492ea49c58446dab7b65da0aa5695..da9d48df180bedc93dc2af4379ae0a88a38f4a85 100644 (file)
@@ -27,6 +27,9 @@ void json_ostream_destroy(struct json_ostream **_stream);
 void json_ostream_close(struct json_ostream *stream);
 bool json_ostream_is_closed(struct json_ostream *stream) ATTR_PURE;
 
+void json_ostream_set_format(struct json_ostream *stream,
+                            const struct json_format *format);
+
 /*
  * Position
  */
index 00a3f356e6ddf276e2233f61dcf247195498028f..3c80f7fd7cd4ec5ab96a6f6a8b72e262856bc516 100644 (file)
@@ -59,22 +59,27 @@ int json_tree_read_cstr(const char *str, enum json_parser_flags parser_flags,
  */
 
 void json_tree_write_buffer(const struct json_tree *jtree, buffer_t *buf,
-                           enum json_generator_flags gen_flags)
+                           enum json_generator_flags gen_flags,
+                           const struct json_format *format)
+
 {
        struct json_ostream *joutput;
 
        joutput = json_ostream_create_str(buf, gen_flags);
+       if (format != NULL)
+               json_ostream_set_format(joutput, format);
        json_ostream_nwrite_tree(joutput, NULL, jtree);
        json_ostream_nfinish_destroy(&joutput);
 }
 
 const char *
 json_tree_to_text(const struct json_tree *jtree,
-                 enum json_generator_flags gen_flags)
+                 enum json_generator_flags gen_flags,
+                 const struct json_format *format)
 {
        string_t *str = t_str_new(1024);
 
-       json_tree_write_buffer(jtree, str, gen_flags);
+       json_tree_write_buffer(jtree, str, gen_flags, format);
        return str_c(str);
 }
 
@@ -82,6 +87,6 @@ const char *json_tree_to_text_line(const struct json_tree *jtree)
 {
        string_t *str = t_str_new(1024);
 
-       json_tree_write_buffer(jtree, str, 0);
+       json_tree_write_buffer(jtree, str, 0, NULL);
        return str_c(str);
 }
index a51577adde7da744181b7d4c1a5527cc03bdb2e6..dad89915f57fe01662653eaf9bfb7309036d3dab 100644 (file)
@@ -24,10 +24,13 @@ int json_tree_read_cstr(const char *str, enum json_parser_flags parser_flags,
  */
 
 void json_tree_write_buffer(const struct json_tree *jtree, buffer_t *buf,
-                           enum json_generator_flags gen_flags);
+                           enum json_generator_flags gen_flags,
+                           const struct json_format *format);
+
 const char *
 json_tree_to_text(const struct json_tree *jtree,
-                 enum json_generator_flags gen_flags);
+                 enum json_generator_flags gen_flags,
+                 const struct json_format *format);
 const char *json_tree_to_text_line(const struct json_tree *jtree);
 
 #endif
index 454eb871c95ad0e20c3976720091acffdbd418e0..e4c4fd2ce391c383dd2f955ae03c11bd6c277ffb 100644 (file)
@@ -571,4 +571,22 @@ struct json_limits {
        unsigned int max_list_items;
 };
 
+/*
+ * Format
+ */
+
+struct json_format {
+       /* Number of indent characters (either TAB or SPACE) */
+       unsigned int indent_chars;
+
+       /* Indent character is TAB instead of SPACE */
+       bool indent_tab:1;
+       /* Insert whitespace at appropriate places around separators */
+       bool whitespace:1;
+       /* Insert newlines */
+       bool new_line:1;
+       /* Newlines are CRLF rather than the default LF */
+       bool crlf:1;
+};
+
 #endif
index fa2418df545591eaf01793638e08c6a5063a6cdb..83f901140f2e488de0b18c62fb8ed3245b0d42fd 100644 (file)
@@ -2157,6 +2157,389 @@ static void test_json_generate_stream(void)
        str_free(&buffer);
 }
 
+static void test_json_generate_formatted(void)
+{
+       string_t *buffer;
+       struct ostream *output;
+       struct json_format format;
+       struct json_generator *generator;
+       unsigned int state, pos;
+       int ret;
+
+       i_zero(&format);
+       format.indent_chars = 2;
+       format.indent_tab = FALSE;
+       format.whitespace = TRUE;
+       format.new_line = TRUE;
+
+       buffer = str_new(default_pool, 256);
+       output = o_stream_create_buffer(buffer);
+       o_stream_set_no_error_handling(output, TRUE);
+
+       /* value */
+       test_begin("json format value");
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       ret = json_generate_number(generator, 23423);
+       test_assert(ret > 0);
+       json_generator_flush(generator);
+       test_assert(ret > 0);
+       json_generator_deinit(&generator);
+       test_assert(strcmp("23423\n", str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       /* [ value ] */
+       test_begin("json format array - [ string ]");
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       json_generate_array_open(generator);
+       ret = json_generate_string(generator, "frop");
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       json_generator_flush(generator);
+       test_assert(ret > 0);
+       json_generator_deinit(&generator);
+       test_assert(strcmp("[\n"
+                          "  \"frop\"\n"
+                          "]\n", str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       /* [ true, true, true ] */
+       test_begin("json format array - [ true, true, true ]");
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       json_generate_array_open(generator);
+       ret = json_generate_true(generator);
+       test_assert(ret > 0);
+       ret = json_generate_true(generator);
+       test_assert(ret > 0);
+       ret = json_generate_true(generator);
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       json_generator_flush(generator);
+       test_assert(ret > 0);
+       json_generator_deinit(&generator);
+       test_assert(strcmp("[\n"
+                          "  true,\n"
+                          "  true,\n"
+                          "  true\n"
+                          "]\n",
+                          str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       /* [ "frop", "friep", "frml" ] */
+       test_begin("json format array - [ \"frop\", \"friep\", \"frml\" ]");
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       json_generate_array_open(generator);
+       ret = json_generate_string(generator, "frop");
+       test_assert(ret > 0);
+       ret = json_generate_string(generator, "friep");
+       test_assert(ret > 0);
+       ret = json_generate_string(generator, "frml");
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       json_generator_flush(generator);
+       test_assert(ret > 0);
+       json_generator_deinit(&generator);
+       test_assert(strcmp("[\n"
+                          "  \"frop\",\n"
+                          "  \"friep\",\n"
+                          "  \"frml\"\n"
+                          "]\n",
+                          str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */
+       test_begin("json format object - "
+                  "{ \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], "
+                    "\"c\": [{\"f\": 3}] }");
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       json_generate_object_open(generator);
+       ret = json_generate_object_member(generator, "a");
+       test_assert(ret > 0);
+       json_generate_array_open(generator);
+       json_generate_object_open(generator);
+       ret = json_generate_object_member(generator, "d");
+       test_assert(ret > 0);
+       ret = json_generate_number(generator, 1);
+       ret = json_generate_object_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_object_member(generator, "b");
+       test_assert(ret > 0);
+       json_generate_array_open(generator);
+       json_generate_object_open(generator);
+       ret = json_generate_object_member(generator, "e");
+       test_assert(ret > 0);
+       ret = json_generate_number(generator, 2);
+       ret = json_generate_object_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_object_member(generator, "c");
+       test_assert(ret > 0);
+       json_generate_array_open(generator);
+       json_generate_object_open(generator);
+       ret = json_generate_object_member(generator, "f");
+       test_assert(ret > 0);
+       ret = json_generate_number(generator, 3);
+       ret = json_generate_object_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_array_close(generator);
+       test_assert(ret > 0);
+       ret = json_generate_object_close(generator);
+       test_assert(ret > 0);
+       json_generator_flush(generator);
+       test_assert(ret > 0);
+       json_generator_deinit(&generator);
+       test_assert(strcmp("{\n"
+                          "  \"a\": [\n"
+                          "    {\n"
+                          "      \"d\": 1\n"
+                          "    }\n"
+                          "  ],\n"
+                          "  \"b\": [\n"
+                          "    {\n"
+                          "      \"e\": 2\n"
+                          "    }\n"
+                          "  ],\n"
+                          "  \"c\": [\n"
+                          "    {\n"
+                          "      \"f\": 3\n"
+                          "    }\n"
+                          "  ]\n"
+                          "}\n", str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       /* trickle */
+       test_begin("json format object - trickle");
+       o_stream_set_max_buffer_size(output, 0);
+       generator = json_generator_init(output, 0);
+       json_generator_set_format(generator, &format);
+       json_generate_object_open(generator);
+       state = 0;
+       for (pos = 0; pos < 65535 && state <= 25; pos++) {
+               o_stream_set_max_buffer_size(output, pos);
+               switch (state) {
+               case 0:
+                       ret = json_generate_object_member(generator, "aaaaaa");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_array_open(generator);
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 1:
+                       ret = json_generate_object_member(generator, "dddddd");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 2:
+                       ret = json_generate_number(generator, 1);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 3:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 4:
+                       ret = json_generate_object_member(generator, "gggggg");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 5:
+                       ret = json_generate_number(generator, 4);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 6:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 7:
+                       ret = json_generate_array_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 8:
+                       ret = json_generate_object_member(generator, "bbbbbb");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_array_open(generator);
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 9:
+                       ret = json_generate_object_member(generator, "eeeeee");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 10:
+                       ret = json_generate_number(generator, 2);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 11:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 12:
+                       ret = json_generate_object_member(generator, "hhhhhh");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 13:
+                       ret = json_generate_number(generator, 5);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 14:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 15:
+                       ret = json_generate_array_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 16:
+                       ret = json_generate_object_member(generator, "cccccc");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_array_open(generator);
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 17:
+                       ret = json_generate_object_member(generator, "ffffff");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 18:
+                       ret = json_generate_number(generator, 3);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 19:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       json_generate_object_open(generator);
+                       state++;
+                       continue;
+               case 20:
+                       ret = json_generate_object_member(generator, "iiiiii");
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 21:
+                       ret = json_generate_number(generator, 6);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 22:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 23:
+                       ret = json_generate_array_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 24:
+                       ret = json_generate_object_close(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               case 25:
+                       ret = json_generator_flush(generator);
+                       test_assert(ret >= 0);
+                       if (ret == 0) break;
+                       state++;
+                       continue;
+               }
+       }
+       json_generator_deinit(&generator);
+       test_assert(state == 26);
+       test_assert(strcmp("{\n"
+                          "  \"aaaaaa\": [\n"
+                          "    {\n"
+                          "      \"dddddd\": 1\n"
+                          "    },\n"
+                          "    {\n"
+                          "      \"gggggg\": 4\n"
+                          "    }\n"
+                          "  ],\n"
+                          "  \"bbbbbb\": [\n"
+                          "    {\n"
+                          "      \"eeeeee\": 2\n"
+                          "    },\n"
+                          "    {\n"
+                          "      \"hhhhhh\": 5\n"
+                          "    }\n"
+                          "  ],\n"
+                          "  \"cccccc\": [\n"
+                          "    {\n"
+                          "      \"ffffff\": 3\n"
+                          "    },\n"
+                          "    {\n"
+                          "      \"iiiiii\": 6\n"
+                          "    }\n"
+                          "  ]\n"
+                          "}\n", str_c(buffer)) == 0);
+       test_end();
+       str_truncate(buffer, 0);
+       output->offset = 0;
+
+       o_stream_destroy(&output);
+       str_free(&buffer);
+}
+
 static void test_json_append_escaped(void)
 {
        string_t *str = t_str_new(32);
@@ -2194,6 +2577,7 @@ int main(int argc, char *argv[])
        static void (*test_functions[])(void) = {
                test_json_generate_buffer,
                test_json_generate_stream,
+               test_json_generate_formatted,
                test_json_append_escaped,
                test_json_append_escaped_data,
                NULL
index 0813e4caea34bc63a8f3b142d83340a3f65234f5..ccbefc94e9c32deedb77cca463828e162cc2b739 100644 (file)
@@ -1145,12 +1145,12 @@ test_json_async_io_run(const struct json_io_test *test, unsigned int scenario)
        output = o_stream_create_buffer(outbuf);
 
        switch (scenario) {
-       case 0: case 2:
+       case 0: case 2: case 4: case 6:
                pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 16);
                pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 32);
                pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 64);
                break;
-       case 1: case 3:
+       case 1: case 3: case 5: case 7:
                pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 128);
                pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 64);
                pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 32);
@@ -1160,12 +1160,12 @@ test_json_async_io_run(const struct json_io_test *test, unsigned int scenario)
        }
 
        switch (scenario) {
-       case 0: case 1:
+       case 0: case 1: case 4: case 5:
                pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 32);
                pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 64);
                pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 128);
                break;
-       case 2: case 3:
+       case 2: case 3: case 6: case 7:
                pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 64);
                pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 32);
                pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 16);
@@ -1204,6 +1204,22 @@ test_json_async_io_run(const struct json_io_test *test, unsigned int scenario)
        tproc2.io = io_add_istream(tproc2.input,
                                  test_json_async_io_input_callback, &tproc2);
 
+       struct json_format json_format;
+
+       switch (scenario) {
+       case 0: case 1: case 2: case 3:
+               break;
+       case 4: case 5: case 6: case 7:
+               i_zero(&json_format);
+               json_format.indent_chars = 2;
+               json_format.new_line = TRUE;
+               json_format.whitespace = TRUE;
+               json_generator_set_format(tproc1.generator, &json_format);
+               break;
+       default:
+               i_unreached();
+       }
+
        struct timeout *to = timeout_add(5000, io_loop_stop, ioloop);
 
        iostream_pump_start(tctx.pump_in);
@@ -1244,7 +1260,7 @@ static void test_json_io_async(void)
        for (i = 0; i < tests_count; i++) T_BEGIN {
                test_begin(t_strdup_printf("json io async [%d]", i));
 
-               for (sc = 0; sc < 4; sc++)
+               for (sc = 0; sc < 8; sc++)
                        test_json_async_io_run(&tests[i], sc);
 
                test_end();
@@ -1567,12 +1583,12 @@ test_json_stream_io_async_run(const struct json_io_test *test,
        output = o_stream_create_buffer(outbuf);
 
        switch (scenario) {
-       case 0: case 2:
+       case 0: case 2: case 4: case 6:
                pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 16);
                pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 32);
                pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 64);
                break;
-       case 1: case 3:
+       case 1: case 3: case 5: case 7:
                pipe1_input = i_stream_create_fd_autoclose(&fd_pipe1[0], 128);
                pipe2_input = i_stream_create_fd_autoclose(&fd_pipe2[0], 64);
                pipe3_input = i_stream_create_fd_autoclose(&fd_pipe3[0], 32);
@@ -1582,12 +1598,12 @@ test_json_stream_io_async_run(const struct json_io_test *test,
        }
 
        switch (scenario) {
-       case 0: case 1:
+       case 0: case 1: case 4: case 5:
                pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 32);
                pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 64);
                pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 128);
                break;
-       case 2: case 3:
+       case 2: case 3: case 6: case 7:
                pipe1_output = o_stream_create_fd_autoclose(&fd_pipe1[1], 64);
                pipe2_output = o_stream_create_fd_autoclose(&fd_pipe2[1], 32);
                pipe3_output = o_stream_create_fd_autoclose(&fd_pipe3[1], 16);
@@ -1614,6 +1630,22 @@ test_json_stream_io_async_run(const struct json_io_test *test,
        tproc2.tctx = &tctx;
        tproc2.name = "proc_b";
 
+       struct json_format json_format;
+
+       switch (scenario) {
+       case 0: case 1: case 2: case 3:
+               break;
+       case 4: case 5: case 6: case 7:
+               i_zero(&json_format);
+               json_format.indent_chars = 2;
+               json_format.new_line = TRUE;
+               json_format.whitespace = TRUE;
+               json_ostream_set_format(tproc1.joutput, &json_format);
+               break;
+       default:
+               i_unreached();
+       }
+
        struct timeout *to = timeout_add(5000, io_loop_stop, ioloop);
 
        iostream_pump_start(tctx.pump_in);
@@ -1654,7 +1686,7 @@ static void test_json_stream_io_async(void)
        for (i = 0; i < tests_count; i++) T_BEGIN {
                test_begin(t_strdup_printf("json stream io async [%d]", i));
 
-               for (sc = 0; sc < 4; sc++)
+               for (sc = 0; sc < 8; sc++)
                        test_json_stream_io_async_run(&tests[i], sc);
                test_end();
        } T_END;
index 0b988a8d55f1f5a527e478396e3617c4bbee4c41..28fa1c166937b4abfb7627d2e986bf7e842be544 100644 (file)
@@ -276,7 +276,7 @@ static void test_json_tree_io(void)
                test_out_reason_quiet("input ok", ret >= 0, error);
 
                if (jtree != NULL)
-                       json_tree_write_buffer(jtree, outbuf, 0);
+                       json_tree_write_buffer(jtree, outbuf, 0, NULL);
 
                test_out_quiet("io match",
                               strcmp(text_out, str_c(outbuf)) == 0);