From: Stephan Bosch Date: Wed, 7 Aug 2019 19:24:50 +0000 (+0200) Subject: lib-json: Implement JSON value output stream X-Git-Tag: 2.4.0~2392 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ea9f7d6a3b1603e95883379e783efa087dcb7708;p=thirdparty%2Fdovecot%2Fcore.git lib-json: Implement JSON value output stream --- diff --git a/src/lib-json/Makefile.am b/src/lib-json/Makefile.am index 24934ae3c7..f67e94f9bb 100644 --- a/src/lib-json/Makefile.am +++ b/src/lib-json/Makefile.am @@ -9,7 +9,8 @@ libjson_la_SOURCES = \ json-types.c \ json-parser.new.c \ json-generator.c \ - json-istream.c + json-istream.c \ + json-ostream.c libjson_la_LIBADD = -lm headers = \ @@ -17,13 +18,15 @@ headers = \ json-types.h \ json-parser.new.h \ json-generator.h \ - json-istream.h + json-istream.h \ + json-ostream.h test_programs = \ test-json-parser \ test-json-generator \ test-json-io \ - test-json-istream + test-json-istream \ + test-json-ostream noinst_PROGRAMS = $(test_programs) @@ -67,6 +70,13 @@ test_json_istream_LDADD = \ test_json_istream_DEPENDENCIES = \ $(test_deps) +test_json_ostream_SOURCE = \ + test-json-ostream.c +test_json_ostream_LDADD = \ + $(test_libs) +test_json_ostream_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 new file mode 100644 index 0000000000..89c8193e17 --- /dev/null +++ b/src/lib-json/json-ostream.c @@ -0,0 +1,1073 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "array.h" +#include "ostream.h" + +#include "json-ostream.h" + +enum json_ostream_node_state { + JSON_OSTREAM_NODE_STATE_NONE = 0, + JSON_OSTREAM_NODE_STATE_VALUE, + JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE, + JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE, +}; + +struct json_ostream { + int refcount; + + struct ostream *output; + struct json_generator *generator; + + struct json_node node; + enum json_ostream_node_state node_state; + unsigned int write_node_level; + + struct json_data node_data; + string_t *buffer; + + char *error; + + bool member_name_written:1; + bool value_opened:1; + bool value_persists:1; + bool string_opened:1; + bool last_errors_not_checked:1; + bool error_handling_disabled:1; + bool nfailed:1; + bool closed:1; +}; + +static int json_ostream_write_node_more(struct json_ostream *stream); + +struct json_ostream * +json_ostream_create(struct ostream *output, + enum json_generator_flags gen_flags) +{ + struct json_ostream *stream; + + stream = i_new(struct json_ostream, 1); + stream->refcount = 1; + + stream->output = output; + o_stream_ref(output); + + stream->generator = json_generator_init(output, gen_flags); + + return stream; +} + +struct json_ostream * +json_ostream_create_str(string_t *buf, enum json_generator_flags gen_flags) +{ + struct json_ostream *stream; + + stream = i_new(struct json_ostream, 1); + stream->refcount = 1; + + stream->generator = json_generator_init_str(buf, gen_flags); + + return stream; +} + +void json_ostream_ref(struct json_ostream *stream) +{ + i_assert(stream->refcount > 0); + stream->refcount++; +} + +void json_ostream_unref(struct json_ostream **_stream) +{ + struct json_ostream *stream = *_stream; + + if (stream == NULL) + return; + *_stream = NULL; + + i_assert(stream->refcount > 0); + if (--stream->refcount != 0) + return; + + if (stream->output != NULL && stream->last_errors_not_checked && + !stream->error_handling_disabled) { + i_panic("JSON output stream %s is missing error handling", + o_stream_get_name(stream->output)); + } + + json_generator_deinit(&stream->generator); + o_stream_unref(&stream->output); + str_free(&stream->buffer); + + i_free(stream->error); + i_free(stream); +} + +void json_ostream_destroy(struct json_ostream **_stream) +{ + struct json_ostream *stream = *_stream; + + if (stream == NULL) + return; + + json_ostream_close(stream); + json_ostream_unref(_stream); +} + +unsigned int json_ostream_get_write_node_level(struct json_ostream *stream) +{ + return stream->write_node_level; +} + +static void ATTR_FORMAT(2, 3) +json_ostream_set_error(struct json_ostream *stream, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + i_free(stream->error); + stream->error = i_strdup_vprintf(fmt, args); + va_end(args); +} + +const char *json_ostream_get_error(struct json_ostream *stream) +{ + if (stream->error != NULL) + return stream->error; + if (stream->closed) + return ""; + if (stream->output != NULL) + return o_stream_get_error(stream->output); + return ""; +} + +void json_ostream_close(struct json_ostream *stream) +{ + stream->closed = TRUE; +} + +bool json_ostream_is_closed(struct json_ostream *stream) +{ + return stream->closed; +} + +void json_ostream_cork(struct json_ostream *stream) +{ + if (stream->output != NULL) + o_stream_cork(stream->output); +} + +void json_ostream_uncork(struct json_ostream *stream) +{ + if (stream->output != NULL) + o_stream_uncork(stream->output); +} + +bool json_ostream_is_corked(struct json_ostream *stream) +{ + return (stream->output != NULL && o_stream_is_corked(stream->output)); +} + +int json_ostream_nfinish(struct json_ostream *stream) +{ + if (stream->closed) + return -1; + if (stream->error != NULL) + return -1; + json_ostream_nflush(stream); + if (stream->output == NULL) + return 0; + json_ostream_ignore_last_errors(stream); + if (stream->output->stream_errno == 0 && stream->nfailed) { + json_ostream_set_error(stream, + "Output stream buffer was full (%zu bytes)", + o_stream_get_max_buffer_size(stream->output)); + return -1; + } + return (stream->output->stream_errno != 0 ? -1 : 0); +} + +void json_ostream_nfinish_destroy(struct json_ostream **_stream) +{ + struct json_ostream *stream = *_stream; + int ret; + + if (stream == NULL) + return; + + ret = json_ostream_nfinish(stream); + i_assert(ret >= 0); + json_ostream_destroy(_stream); +} + +void json_ostream_ignore_last_errors(struct json_ostream *stream) +{ + stream->last_errors_not_checked = FALSE; +} + +void json_ostream_set_no_error_handling(struct json_ostream *stream, bool set) +{ + stream->error_handling_disabled = set; +} + +/* + * Partial data + */ + +static void json_ostream_persist_value(struct json_ostream *stream) +{ + if (!json_node_is_singular(&stream->node)) + return; + if (stream->value_persists) + return; + stream->value_persists = TRUE; + + switch (stream->node.value.content_type) { + case JSON_CONTENT_TYPE_STRING: + i_unreached(); + case JSON_CONTENT_TYPE_DATA: + if (stream->buffer == NULL) + stream->buffer = str_new(default_pool, 128); + stream->node_data = *stream->node.value.content.data; + str_truncate(stream->buffer, 0); + str_append_data(stream->buffer, + stream->node_data.data, stream->node_data.size); + stream->node_data.data = str_data(stream->buffer); + stream->node.value.content.data = &stream->node_data; + break; + default: + break; + } +} + +static int json_ostream_do_write_more(struct json_ostream *stream) +{ + struct json_data *jdata; + ssize_t sret; + int ret; + + if (stream->closed) + return -1; + + switch (stream->node.value.content_type) { + case JSON_CONTENT_TYPE_STRING: + i_zero(&stream->node_data); + stream->node_data.data = (const unsigned char *) + stream->node.value.content.str; + stream->node_data.size = strlen(stream->node.value.content.str); + stream->node.value.content.data = &stream->node_data; + stream->node.value.content_type = + JSON_CONTENT_TYPE_DATA; + /* fall through */ + case JSON_CONTENT_TYPE_DATA: + jdata = stream->node.value.content.data; + switch (stream->node.type) { + /* string */ + case JSON_TYPE_STRING: + if (stream->string_opened) + stream->value_opened = TRUE; + if (!stream->value_opened) { + json_generate_string_open(stream->generator); + stream->value_opened = TRUE; + } + while (jdata->size > 0) { + sret = json_generate_string_more( + stream->generator, jdata->data, + jdata->size, TRUE); + if (sret <= 0) + return (int)sret; + i_assert((size_t)sret <= jdata->size); + jdata->data += sret; + jdata->size -= (size_t)sret; + } + if (!stream->string_opened) { + ret = json_generate_string_write_close( + stream->generator); + if (ret <= 0) + return ret; + } + stream->value_opened = FALSE; + return 1; + /* number, JSON-text */ + case JSON_TYPE_NUMBER: + case JSON_TYPE_TEXT: + i_assert(!stream->string_opened); + if (!stream->value_opened) { + json_generate_text_open(stream->generator); + stream->value_opened = TRUE; + } + while (jdata->size > 0) { + sret = json_generate_text_more( + stream->generator, jdata->data, + jdata->size); + if (sret <= 0) + return (int)sret; + i_assert((size_t)sret <= jdata->size); + jdata->data += sret; + jdata->size -= (size_t)sret; + } + ret = json_generate_text_close(stream->generator); + if (ret <= 0) + return ret; + stream->value_opened = FALSE; + return 1; + default: + i_unreached(); + } + break; + default: + break; + } + + return json_generate_value(stream->generator, stream->node.type, + &stream->node.value); +} + +static int json_ostream_write_more(struct json_ostream *stream) +{ + int ret; + + ret = json_ostream_do_write_more(stream); + if (ret <= 0) { + i_assert(stream->output != NULL); + return ret; + } + i_zero(&stream->node); + stream->value_persists = FALSE; + stream->value_opened = FALSE; + return 1; +} + +int json_ostream_flush(struct json_ostream *stream) +{ + int ret; + + if (stream->closed) + return -1; + 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); + if (ret <= 0) + return ret; + return 1; +} + +void json_ostream_nflush(struct json_ostream *stream) +{ + if (unlikely(stream->closed)) + return; + if (unlikely(stream->nfailed)) { + i_assert(stream->output != NULL); + return; + } + if (stream->output != NULL) { + if (unlikely(stream->output->closed || + stream->output->stream_errno != 0)) + return; + } + if (json_ostream_flush(stream) <= 0) { + i_assert(stream->output != NULL); + stream->nfailed = TRUE; + } + stream->last_errors_not_checked = TRUE; +} + +static int +json_ostream_write_init(struct json_ostream *stream, const char *name, + enum json_type type) +{ + int ret; + + i_assert(name == NULL || !stream->string_opened); + i_assert(!stream->string_opened || type == JSON_TYPE_STRING); + + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + + if (stream->string_opened) + return 1; + + if (name != NULL) { + i_assert(!stream->member_name_written); + ret = json_generate_object_member(stream->generator, name); + if (ret <= 0) + return ret; + } + stream->member_name_written = FALSE; + return 1; +} + +/* + * Values + */ + +static inline bool json_ostream_nwrite_pre(struct json_ostream *stream) +{ + if (unlikely(stream->closed)) + return FALSE; + if (unlikely(stream->nfailed)) { + i_assert(stream->output != NULL); + return FALSE; + } + if (stream->output != NULL) { + if (unlikely(stream->output->closed || + stream->output->stream_errno != 0)) + return FALSE; + } + return TRUE; +} + +static inline void +json_ostream_nwrite_post(struct json_ostream *stream, int ret) +{ + if (ret <= 0) { + i_assert(stream->output != NULL); + stream->nfailed = TRUE; + } + stream->last_errors_not_checked = TRUE; +} + +/* object member */ + +int json_ostream_write_object_member(struct json_ostream *stream, + const char *name) +{ + int ret; + + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + + i_assert(!stream->member_name_written); + ret = json_generate_object_member(stream->generator, name); + if (ret <= 0) + return ret; + stream->member_name_written = TRUE; + return 1; +} + +void json_ostream_nwrite_object_member(struct json_ostream *stream, + const char *name) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_write_object_member(stream, name); + json_ostream_nwrite_post(stream, ret); +} + +/* value */ + +static int +json_ostream_do_write_value(struct json_ostream *stream, + const char *name, enum json_type type, + const struct json_value *value, bool persist) +{ + int ret; + + ret = json_ostream_write_init(stream, name, type); + if (ret <= 0) + return ret; + + i_zero(&stream->node); + stream->node.type = type; + stream->node.value = *value; + + ret = json_ostream_write_more(stream); + if (ret < 0) + return ret; + if (ret == 0 && persist) + json_ostream_persist_value(stream); + return 1; +} + +int json_ostream_write_value(struct json_ostream *stream, + const char *name, enum json_type type, + const struct json_value *value) +{ + return json_ostream_do_write_value(stream, name, type, value, TRUE); +} + +void json_ostream_nwrite_value(struct json_ostream *stream, + const char *name, enum json_type type, + const struct json_value *value) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_do_write_value(stream, name, type, value, FALSE); + json_ostream_nwrite_post(stream, ret); +} + +/* number */ + +int json_ostream_write_number(struct json_ostream *stream, + const char *name, intmax_t number) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_INTEGER; + jvalue.content.intnum = number; + + return json_ostream_write_value(stream, name, + JSON_TYPE_NUMBER, &jvalue); +} + +void json_ostream_nwrite_number(struct json_ostream *stream, + const char *name, intmax_t number) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_INTEGER; + jvalue.content.intnum = number; + + json_ostream_nwrite_value(stream, name, + JSON_TYPE_NUMBER, &jvalue); +} + +int json_ostream_write_number_raw(struct json_ostream *stream, + const char *name, const char *number) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = number; + + return json_ostream_write_value(stream, name, + JSON_TYPE_NUMBER, &jvalue); +} + +void json_ostream_nwrite_number_raw(struct json_ostream *stream, + const char *name, const char *number) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = number; + + json_ostream_nwrite_value(stream, name, JSON_TYPE_NUMBER, &jvalue); +} + +/* string */ + +int json_ostream_write_string_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size) +{ + struct json_value jvalue; + struct json_data jdata; + + i_zero(&jdata); + jdata.data = data; + jdata.size = size; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_DATA; + jvalue.content.data = &jdata; + + return json_ostream_write_value(stream, name, JSON_TYPE_STRING, + &jvalue); +} + +void json_ostream_nwrite_string_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size) +{ + struct json_value jvalue; + struct json_data jdata; + + i_zero(&jdata); + jdata.data = data; + jdata.size = size; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_DATA; + jvalue.content.data = &jdata; + + json_ostream_nwrite_value(stream, name, JSON_TYPE_STRING, &jvalue); +} + +int json_ostream_write_string(struct json_ostream *stream, + const char *name, const char *str) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = str; + + return json_ostream_write_value(stream, name, JSON_TYPE_STRING, + &jvalue); +} + +void json_ostream_nwrite_string(struct json_ostream *stream, + const char *name, const char *str) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = str; + + json_ostream_nwrite_value(stream, name, JSON_TYPE_STRING, &jvalue); +} + +void json_ostream_nwritef_string(struct json_ostream *stream, + const char *name, const char *format, ...) +{ + va_list args; + + va_start(args, format); + json_ostream_nwrite_string(stream, name, + t_strdup_vprintf(format, args)); + va_end(args); +} + +int json_ostream_open_string(struct json_ostream *stream, const char *name) +{ + int ret; + + ret = json_ostream_write_init(stream, name, JSON_TYPE_STRING); + if (ret <= 0) + return ret; + + i_zero(&stream->node); + json_generate_string_open(stream->generator); + stream->string_opened = TRUE; + return 1; +} + +void json_ostream_nopen_string(struct json_ostream *stream, const char *name) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_open_string(stream, name); + json_ostream_nwrite_post(stream, ret); +} + +int json_ostream_close_string(struct json_ostream *stream) +{ + int ret; + + i_assert(stream->string_opened); + + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + i_zero(&stream->node); + ret = json_generate_string_write_close(stream->generator); + if (ret <= 0) + return ret; + stream->string_opened = FALSE; + return 1; +} + +void json_ostream_nclose_string(struct json_ostream *stream) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_close_string(stream); + json_ostream_nwrite_post(stream, ret); +} + +/* null */ + +int json_ostream_write_null(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + return json_ostream_write_value(stream, name, JSON_TYPE_NULL, &jvalue); +} + +void json_ostream_nwrite_null(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + json_ostream_nwrite_value(stream, name, JSON_TYPE_NULL, &jvalue); +} + + +/* false, true */ + +int json_ostream_write_false(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + return json_ostream_write_value(stream, name, JSON_TYPE_FALSE, + &jvalue); +} + +void json_ostream_nwrite_false(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + json_ostream_nwrite_value(stream, name, JSON_TYPE_FALSE, &jvalue); +} + +int json_ostream_write_true(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + return json_ostream_write_value(stream, name, JSON_TYPE_TRUE, &jvalue); +} + +void json_ostream_nwrite_true(struct json_ostream *stream, const char *name) +{ + struct json_value jvalue; + + i_zero(&jvalue); + json_ostream_nwrite_value(stream, name, JSON_TYPE_TRUE, &jvalue); +} + +int json_ostream_write_bool(struct json_ostream *stream, const char *name, + bool value) +{ + struct json_value jvalue; + + i_zero(&jvalue); + return json_ostream_write_value( + stream, name, (value ? JSON_TYPE_TRUE : JSON_TYPE_FALSE), + &jvalue); +} + +void json_ostream_nwrite_bool(struct json_ostream *stream, const char *name, + bool value) +{ + struct json_value jvalue; + + i_zero(&jvalue); + json_ostream_nwrite_value( + stream, name, (value ? JSON_TYPE_TRUE : JSON_TYPE_FALSE), + &jvalue); +} + +/* object */ + +int json_ostream_descend_object(struct json_ostream *stream, const char *name) +{ + int ret; + + ret = json_ostream_write_init(stream, name, JSON_TYPE_OBJECT); + if (ret <= 0) + return ret; + + i_zero(&stream->node); + json_generate_object_open(stream->generator); + stream->write_node_level++; + return 1; +} + +void json_ostream_ndescend_object(struct json_ostream *stream, + const char *name) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_descend_object(stream, name); + json_ostream_nwrite_post(stream, ret); +} + +int json_ostream_ascend_object(struct json_ostream *stream) +{ + int ret; + + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + ret = json_generate_object_close(stream->generator); + if (ret <= 0) + return ret; + i_assert(stream->write_node_level > 0); + stream->write_node_level--; + return 1; +} + +void json_ostream_nascend_object(struct json_ostream *stream) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_ascend_object(stream); + json_ostream_nwrite_post(stream, ret); +} + +/* array */ + +int json_ostream_descend_array(struct json_ostream *stream, const char *name) +{ + int ret; + + ret = json_ostream_write_init(stream, name, JSON_TYPE_ARRAY); + if (ret <= 0) + return ret; + + i_zero(&stream->node); + json_generate_array_open(stream->generator); + stream->write_node_level++; + return 1; +} + +void json_ostream_ndescend_array(struct json_ostream *stream, + const char *name) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_descend_array(stream, name); + json_ostream_nwrite_post(stream, ret); +} + +int json_ostream_ascend_array(struct json_ostream *stream) +{ + int ret; + + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + ret = json_generate_array_close(stream->generator); + if (ret <= 0) + return ret; + i_assert(stream->write_node_level > 0); + stream->write_node_level--; + return 1; +} + +void json_ostream_nascend_array(struct json_ostream *stream) +{ + int ret; + + if (!json_ostream_nwrite_pre(stream)) + return; + ret = json_ostream_ascend_array(stream); + json_ostream_nwrite_post(stream, ret); +} + +/* JSON-text */ + +/* string */ + +int json_ostream_write_text_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size) +{ + struct json_value jvalue; + struct json_data jdata; + + i_zero(&jdata); + jdata.data = data; + jdata.size = size; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_DATA; + jvalue.content.data = &jdata; + + return json_ostream_write_value(stream, name, JSON_TYPE_TEXT, &jvalue); +} + +void json_ostream_nwrite_text_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size) +{ + struct json_value jvalue; + struct json_data jdata; + + i_zero(&jdata); + jdata.data = data; + jdata.size = size; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_DATA; + jvalue.content.data = &jdata; + + json_ostream_nwrite_value(stream, name, JSON_TYPE_TEXT, &jvalue); +} + +int json_ostream_write_text(struct json_ostream *stream, + const char *name, const char *str) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = str; + + return json_ostream_write_value(stream, name, JSON_TYPE_TEXT, &jvalue); +} + +void json_ostream_nwrite_text(struct json_ostream *stream, + const char *name, const char *str) +{ + struct json_value jvalue; + + i_zero(&jvalue); + jvalue.content_type = JSON_CONTENT_TYPE_STRING; + jvalue.content.str = str; + + json_ostream_nwrite_value(stream, name, JSON_TYPE_TEXT, &jvalue); +} + +/* + * Nodes + */ + +static int json_ostream_write_node_more(struct json_ostream *stream) +{ + int ret; + + switch (stream->node_state) { + case JSON_OSTREAM_NODE_STATE_NONE: + break; + case JSON_OSTREAM_NODE_STATE_VALUE: + /* Continue value */ + ret = json_ostream_write_more(stream); + if (ret <= 0) + return ret; + stream->node_state = JSON_OSTREAM_NODE_STATE_NONE; + i_zero(&stream->node); + break; + case JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE: + /* Continue array close */ + ret = json_generate_array_close(stream->generator); + if (ret <= 0) + return ret; + stream->node_state = JSON_OSTREAM_NODE_STATE_NONE; + i_zero(&stream->node); + break; + case JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE: + /* Continue object close */ + ret = json_generate_object_close(stream->generator); + if (ret <= 0) + return ret; + stream->node_state = JSON_OSTREAM_NODE_STATE_NONE; + i_zero(&stream->node); + break; + default: + i_unreached(); + } + return 1; +} + +static int +json_ostream_do_write_node(struct json_ostream *stream, + const struct json_node *node, bool flush, + bool persist) +{ + int ret; + + if (flush) { + ret = json_ostream_flush(stream); + if (ret <= 0) + return ret; + + i_assert(stream->node_state == JSON_OSTREAM_NODE_STATE_NONE); + } else if (stream->node_state != JSON_OSTREAM_NODE_STATE_NONE) { + return 0; + } + + i_assert(!json_node_is_none(node)); + + if (!json_node_is_end(node) && node->name != NULL) { + i_assert(!stream->member_name_written); + ret = json_generate_object_member(stream->generator, + node->name); + if (ret <= 0) + return ret; + } + stream->member_name_written = FALSE; + + switch (node->type) { + case JSON_TYPE_ARRAY: + if (!json_node_is_array_end(node)) { + /* Open array */ + json_generate_array_open(stream->generator); + return 1; + } + /* Close array */ + stream->node = *node; + stream->node_state = JSON_OSTREAM_NODE_STATE_ARRAY_CLOSE; + break; + case JSON_TYPE_OBJECT: + if (!json_node_is_object_end(node)) { + /* Open object */ + json_generate_object_open(stream->generator); + return 1; + } + /* Close object */ + stream->node = *node; + stream->node_state = JSON_OSTREAM_NODE_STATE_OBJECT_CLOSE; + break; + default: + /* Write normal value */ + stream->node = *node; + stream->node_state = JSON_OSTREAM_NODE_STATE_VALUE; + break; + } + + ret = json_ostream_write_node_more(stream); + if (ret < 0) + return -1; + if (ret == 0 && persist) + json_ostream_persist_value(stream); + + return 1; +} + +int json_ostream_write_node(struct json_ostream *stream, + const struct json_node *node, bool copy) +{ + return json_ostream_do_write_node(stream, node, TRUE, copy); +} + +void json_ostream_nwrite_node(struct json_ostream *stream, + const struct json_node *node) +{ + switch (node->type) { + case JSON_TYPE_ARRAY: + if (!json_node_is_array_end(node)) { + /* Open array */ + json_ostream_ndescend_array(stream, node->name); + return; + } + /* Close array */ + json_ostream_nascend_array(stream); + return; + case JSON_TYPE_OBJECT: + if (!json_node_is_object_end(node)) { + /* Open object */ + json_ostream_ndescend_object(stream, node->name); + return; + } + /* Close object */ + json_ostream_nascend_object(stream); + return; + default: + break; + } + + json_ostream_nwrite_value(stream, node->name, + node->type, &node->value); +} diff --git a/src/lib-json/json-ostream.h b/src/lib-json/json-ostream.h new file mode 100644 index 0000000000..de917af508 --- /dev/null +++ b/src/lib-json/json-ostream.h @@ -0,0 +1,256 @@ +#ifndef JSON_OSTREAM_H +#define JSON_OSTREAM_H + +#include "lib.h" + +#include "json-types.h" +#include "json-generator.h" + +struct json_ostream; + +/* + * JSON ostream + */ + +struct json_ostream * +json_ostream_create(struct ostream *output, + enum json_generator_flags gen_flags); +struct json_ostream * +json_ostream_create_str(string_t *buf, + enum json_generator_flags gen_flags); + +void json_ostream_ref(struct json_ostream *stream); +void json_ostream_unref(struct json_ostream **_stream); +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; + +/* + * Position + */ + +unsigned int json_ostream_get_write_node_level(struct json_ostream *stream); + +/* + * Cork + */ + +void json_ostream_cork(struct json_ostream *stream); +void json_ostream_uncork(struct json_ostream *stream); +bool json_ostream_is_corked(struct json_ostream *stream); + +/* + * Flush + */ + +/* Try to flush the output stream. Returns 1 if all sent, 0 if not, + -1 if error. */ +int json_ostream_flush(struct json_ostream *stream); +void json_ostream_nflush(struct json_ostream *stream); + +/* + * Error handling + */ + +/* Returns error string for the previous error. */ +const char *json_ostream_get_error(struct json_ostream *stream); + +/* Marks the stream's error handling as completed. Flushes the stream and + returns -1 if any of the nwrite*(), ndescend*(), etc. calls didn't write + all data. */ +int json_ostream_nfinish(struct json_ostream *stream); +/* Same as json_ostream_nfinish() but expects guaranteed success and implicitly + destroys the stream. This will assert fail if the internal + json_ostream_nfinish() call fails, so this is mostly only suitable for + buffer output. */ +void json_ostream_nfinish_destroy(struct json_ostream **_stream); +/* Marks the stream's error handling as completed to avoid i_panic() on + destroy. */ +void json_ostream_ignore_last_errors(struct json_ostream *stream); +/* If error handling is disabled, the i_panic() on destroy is never called. + This function can be called immediately after the stream is created. */ +void json_ostream_set_no_error_handling(struct json_ostream *stream, bool set); + +/* + * Write functions + */ + +/* The 'name' argument is the name of the object member if the value is + written in the context of an object. If not, it MUST be NULL, or the + underlying JSON generator will trigger an assertion panic. The object member + name can also be written earlier using json_ostream_write_object_member(), + in which case the name argument of the subsequent value write function must + also be NULL. + + Just like an ostream, the 'n' functions send their data with delayed error + handling. json_ostream_nfinish() or json_ostream_ignore_last_errors() + must be called after these functions before the stream is destroyed. If + any of the data can't be sent due to stream's buffer getting full, all + further 'n' function calls are ignored and json_ostream_nfinish() will + fail. + */ + +/* value */ + +/* object member */ + +int json_ostream_write_object_member(struct json_ostream *stream, + const char *name); +void json_ostream_nwrite_object_member(struct json_ostream *stream, + const char *name); + +/* Try to write the value to the output stream. Returns 1 if buffered, 0 + if not, -1 if error. */ +int json_ostream_write_value(struct json_ostream *stream, + const char *name, enum json_type type, + const struct json_value *value); +void json_ostream_nwrite_value(struct json_ostream *stream, + const char *name, enum json_type type, + const struct json_value *value); + +/* node */ + +/* Try to write the JSON node to the output stream. Returns 1 if buffered, + 0 if not, -1 if error. Value is copied to stream upon partial write if + copy is TRUE, otherwise caller is responsible for keeping it allocated until + the potentially buffered node is flushed by the stream. */ +int json_ostream_write_node(struct json_ostream *stream, + const struct json_node *node, bool copy); +void json_ostream_nwrite_node(struct json_ostream *stream, + const struct json_node *node); + +/* number */ + +/* Try to write the number to the output stream. Returns 1 if buffered, + 0 if not, -1 if error. */ +int json_ostream_write_number(struct json_ostream *stream, + const char *name, intmax_t number); +void json_ostream_nwrite_number(struct json_ostream *stream, + const char *name, intmax_t number); +/* Try to write the number (the string) to the output stream. Returns 1 + if buffered, 0 if not, -1 if error. */ +int json_ostream_write_number_raw(struct json_ostream *stream, + const char *name, const char *number); +void json_ostream_nwrite_number_raw(struct json_ostream *stream, + const char *name, const char *number); + +/* string */ + +/* Try to write the data to the output stream as a string. Returns 1 if + buffered, 0 if not, -1 if error. */ +int json_ostream_write_string_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size); +void json_ostream_nwrite_string_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size); +/* Try to write the buffer to the output stream as a string. Returns 1 if + buffered, 0 if not, -1 if error. */ +static inline int +json_ostream_write_string_buffer(struct json_ostream *stream, + const char *name, const buffer_t *buf) +{ + return json_ostream_write_string_data(stream, name, + buf->data, buf->used); +} +static inline void +json_ostream_nwrite_string_buffer(struct json_ostream *stream, + const char *name, const buffer_t *buf) +{ + json_ostream_nwrite_string_data(stream, name, buf->data, buf->used); +} +/* Try to write the string to the output stream. Returns 1 if buffered, + 0 if not, -1 if error. */ +int json_ostream_write_string(struct json_ostream *stream, + const char *name, const char *str); +void json_ostream_nwrite_string(struct json_ostream *stream, + const char *name, const char *str); +void json_ostream_nwritef_string(struct json_ostream *stream, + const char *name, + const char *format, ...) ATTR_FORMAT(3, 4); + +/* Open a string on the stream, which means that all subsequent string + write functions are concatenated into a single JSON string value. Note that + the individual string values need to be valid and complete UTF-8. Any invalid + or incomplete UTF-8 code point will yield replacement characters in the + output, so code points cannot span sequential string values and must always + be fully contained within a single write. */ +int json_ostream_open_string(struct json_ostream *stream, const char *name); +void json_ostream_nopen_string(struct json_ostream *stream, const char *name); +/* Close the earlier opened string value on the stream. All subsequent string + write functions will create separate string values once more. */ +int json_ostream_close_string(struct json_ostream *stream); +void json_ostream_nclose_string(struct json_ostream *stream); + +/* null */ + +/* Try to write the `null' literal to the output stream. Returns 1 if + buffered, 0 if not, -1 if error. */ +int json_ostream_write_null(struct json_ostream *stream, const char *name); +void json_ostream_nwrite_null(struct json_ostream *stream, const char *name); + +/* false, true */ + +/* Try to write the `false' literal to the output stream. Returns 1 if + buffered, 0 if not, -1 if error. */ +int json_ostream_write_false(struct json_ostream *stream, const char *name); +void json_ostream_nwrite_false(struct json_ostream *stream, const char *name); +/* Try to write the `true' literal to the output stream. Returns 1 if + buffered, 0 if not, -1 if error. */ +int json_ostream_write_true(struct json_ostream *stream, const char *name); +void json_ostream_nwrite_true(struct json_ostream *stream, const char *name); +/* Try to write the boolean value to the output stream. Returns 1 if + buffered, 0 if not, -1 if error. */ +int json_ostream_write_bool(struct json_ostream *stream, + const char *name, bool value); +void json_ostream_nwrite_bool(struct json_ostream *stream, + const char *name, bool value); + +/* object */ + +/* Try to descend into a JSON object by writing '{' to the output stream. + Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_descend_object(struct json_ostream *stream, + const char *name); +void json_ostream_ndescend_object(struct json_ostream *stream, + const char *name); + +/* Try to ascend from a JSON object by writing '}' to the output stream. + Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_ascend_object(struct json_ostream *stream); +void json_ostream_nascend_object(struct json_ostream *stream); + +/* array */ + +/* Try to descend into a JSON array by writing '[' to the output stream. + Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_descend_array(struct json_ostream *stream, + const char *name); +void json_ostream_ndescend_array(struct json_ostream *stream, + const char *name); + +/* Try to ascend from a JSON arrayh by writing ']' to the output stream. + Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_ascend_array(struct json_ostream *stream); +void json_ostream_nascend_array(struct json_ostream *stream); + +/* JSON-text */ + +/* Try to write the data to the output stream directly (JSON-text, not as + a string). Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_write_text_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size); +void json_ostream_nwrite_text_data(struct json_ostream *stream, + const char *name, + const void *data, size_t size); + +/* Try to write the string to the output stream directly (JSON-text, not as + a string). Returns 1 if buffered, 0 if not, -1 if error. */ +int json_ostream_write_text(struct json_ostream *stream, + const char *name, const char *str); +void json_ostream_nwrite_text(struct json_ostream *stream, + const char *name, const char *str); + +#endif diff --git a/src/lib-json/test-json-io.c b/src/lib-json/test-json-io.c index 31f8e6f98f..0813e4caea 100644 --- a/src/lib-json/test-json-io.c +++ b/src/lib-json/test-json-io.c @@ -11,6 +11,8 @@ #include "json-parser.new.h" #include "json-generator.h" +#include "json-istream.h" +#include "json-ostream.h" #include #include @@ -1249,6 +1251,553 @@ static void test_json_io_async(void) } T_END; } +/* + * Stream I/O + */ + +struct test_sio_context; + +struct test_sio_processor { + struct test_sio_context *tctx; + const char *name; + + struct istream *input; + struct ostream *output; + struct io *io; + + struct json_node jnode; + + struct json_istream *jinput; + struct json_ostream *joutput; + + bool input_finished:1; +}; + +struct test_sio_context { + const struct json_io_test *test; + unsigned int scenario; + + struct iostream_pump *pump_in, *pump_out; +}; + +static void test_json_stream_io(void) +{ + static const unsigned int margins[] = { 0, 1, 2, 10, 50 }; + string_t *outbuf; + unsigned int i, j; + + outbuf = str_new(default_pool, 256); + + for (i = 0; i < tests_count; i++) T_BEGIN { + const struct json_io_test *test; + const char *text, *text_out; + unsigned int pos, margin, text_len; + + 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 stream io [%d]", i)); + + for (j = 0; j < N_ELEMENTS(margins); j++) { + struct istream *input; + struct ostream *output; + struct json_istream *jinput; + struct json_ostream *joutput; + struct json_node jnode; + int pret = 0, wret = 0; + const char *error = NULL; + + margin = margins[j]; + + buffer_set_used_size(outbuf, 0); + + output = o_stream_create_buffer(outbuf); + o_stream_set_no_error_handling(output, TRUE); + input = test_istream_create_data(text, text_len); + + jinput = json_istream_create(input, 0, &test->limits, test->flags); + joutput = json_ostream_create(output, 0); + + o_stream_set_max_buffer_size(output, 0); + pret = 0; wret = 1; + i_zero(&jnode); + for (pos = 0; + pos <= (text_len+margin+1) && (pret == 0 || wret == 0); + pos++) { + test_istream_set_size(input, pos); + o_stream_set_max_buffer_size(output, + (pos > margin ? pos - margin : 0)); + + if (wret > 0 && pret == 0) { + pret = json_istream_walk(jinput, &jnode); + if (pret < 0) + break; + test_assert(!json_istream_failed(jinput)); + } + if (json_node_is_none(&jnode)) + wret = 1; + else + wret = json_ostream_write_node(joutput, &jnode, TRUE); + if (wret == 0) + continue; + if (wret < 0) + break; + i_zero(&jnode); + pret = 0; + } + + o_stream_set_max_buffer_size(output, SIZE_MAX); + wret = json_ostream_flush(joutput); + json_ostream_destroy(&joutput); + + pret = json_istream_finish(&jinput, &error); + + test_out_reason_quiet( + t_strdup_printf("read success " + "(trickle, margin=%u)", margin), + pret > 0, error); + test_out_quiet( + t_strdup_printf("write success " + "(trickle, margin=%u)", margin), + wret > 0); + test_out_quiet( + t_strdup_printf("io match (trickle, margin=%u)", + margin), + strcmp(text_out, str_c(outbuf)) == 0); + if (debug) { + i_debug("OUT: >%s<", text_out); + i_debug("OUT: >%s<", str_c(outbuf)); + } + + o_stream_unref(&output); + i_stream_unref(&input); + } + + test_end(); + + } T_END; + + buffer_free(&outbuf); +} + +static void test_json_stream_io_input_callback(struct test_sio_processor *tproc) +{ + int ret; + + if (!json_node_is_none(&tproc->jnode)) { + io_remove(&tproc->io); + return; + } + + ret = json_istream_walk(tproc->jinput, &tproc->jnode); + test_assert(!json_istream_failed(tproc->jinput) || ret < 0); + if (ret < 0) { + const char *error = NULL; + + ret = json_istream_finish(&tproc->jinput, &error); + i_assert(ret != 0); + + test_out_reason_quiet( + t_strdup_printf("%u: %s: read success (async)", + tproc->tctx->scenario, tproc->name), + ret > 0, error); + + if (ret < 0) + io_loop_stop(current_ioloop); + else { + tproc->input_finished = TRUE; + io_remove(&tproc->io); + o_stream_set_flush_pending(tproc->output, TRUE); + } + return; + } + if (ret == 0) + return; + + o_stream_set_flush_pending(tproc->output, TRUE); +} + +static int test_json_stream_io_flush_callback(struct test_sio_processor *tproc) +{ + int ret; + + if (json_node_is_none(&tproc->jnode)) + ret = 1; + else { + ret = json_ostream_write_node(tproc->joutput, &tproc->jnode, + TRUE); + } + if (ret < 0) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + return -1; + } + if (ret == 0) { + io_remove(&tproc->io); + return 0; + } + i_zero(&tproc->jnode); + + if (tproc->input_finished) { + ret = json_ostream_flush(tproc->joutput); + if (ret == 0) + return 0; + test_out_quiet( + t_strdup_printf("%u: %s: write success (async)", + tproc->tctx->scenario, tproc->name), + ret > 0); + if (ret < 0) + io_loop_stop(current_ioloop); + else { + io_remove(&tproc->io); + o_stream_close(tproc->output); + } + return ret; + } + + if (tproc->io == NULL) { + tproc->io = io_add_istream( + tproc->input, test_json_stream_io_input_callback, tproc); + i_stream_set_input_pending(tproc->input, TRUE); + } + return 1; +} + +static void +test_json_stream_io_pump_in_callback(enum iostream_pump_status status, + struct test_sio_context *tctx) +{ + if (status != IOSTREAM_PUMP_STATUS_INPUT_EOF) { + test_assert(FALSE); + io_loop_stop(current_ioloop); + return; + } + + struct ostream *output = iostream_pump_get_output(tctx->pump_in); + + o_stream_close(output); + iostream_pump_destroy(&tctx->pump_in); +} + +static void +test_json_stream_io_pump_out_callback(enum iostream_pump_status status, + struct test_sio_context *tctx) +{ + if (status != IOSTREAM_PUMP_STATUS_INPUT_EOF) + test_assert(FALSE); + + io_loop_stop(current_ioloop); + iostream_pump_destroy(&tctx->pump_out); +} + +static void +test_sio_processor_init(struct test_sio_processor *tproc, + const struct json_io_test *test, + struct istream *input, struct ostream *output) +{ + i_zero(tproc); + + tproc->output = output; + o_stream_set_no_error_handling(tproc->output, TRUE); + o_stream_set_flush_callback(tproc->output, + test_json_stream_io_flush_callback, tproc); + o_stream_uncork(tproc->output); + + tproc->input = input; + tproc->io = io_add_istream(tproc->input, + test_json_stream_io_input_callback, tproc); + + tproc->jinput = json_istream_create(input, 0, &test->limits, + test->flags); + tproc->joutput = json_ostream_create(output, 0); +} + +static void test_sio_processor_deinit(struct test_sio_processor *tproc) +{ + json_ostream_destroy(&tproc->joutput); + json_istream_destroy(&tproc->jinput); +} + +static void +test_json_stream_io_async_run(const struct json_io_test *test, + unsigned int scenario) +{ + struct test_sio_context tctx; + string_t *outbuf; + struct test_sio_processor tproc1, tproc2; + struct ioloop *ioloop; + int fd_pipe1[2], fd_pipe2[2], fd_pipe3[2]; + const char *text, *text_out; + unsigned int text_len; + struct istream *input, *pipe1_input, *pipe2_input, *pipe3_input; + struct ostream *output, *pipe1_output, *pipe2_output, *pipe3_output; + + i_zero(&tctx); + tctx.test = test; + tctx.scenario = scenario; + + text = test->input; + text_out = test->output; + if (text_out == NULL) + text_out = test->input; + text_len = strlen(text); + + outbuf = str_new(default_pool, 256); + + if (pipe(fd_pipe1) < 0) + i_fatal("pipe() failed: %m"); + if (pipe(fd_pipe2) < 0) + i_fatal("pipe() failed: %m"); + if (pipe(fd_pipe3) < 0) + i_fatal("pipe() failed: %m"); + fd_set_nonblock(fd_pipe1[0], TRUE); + fd_set_nonblock(fd_pipe1[1], TRUE); + fd_set_nonblock(fd_pipe2[0], TRUE); + fd_set_nonblock(fd_pipe2[1], TRUE); + fd_set_nonblock(fd_pipe3[0], TRUE); + fd_set_nonblock(fd_pipe3[1], TRUE); + + ioloop = io_loop_create(); + + input = i_stream_create_from_data(text, text_len); + output = o_stream_create_buffer(outbuf); + + switch (scenario) { + case 0: case 2: + 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: + 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); + break; + default: + i_unreached(); + } + + switch (scenario) { + case 0: case 1: + 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: + 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); + break; + default: + i_unreached(); + } + + tctx.pump_in = iostream_pump_create(input, pipe1_output); + tctx.pump_out = iostream_pump_create(pipe3_input, output); + + iostream_pump_set_completion_callback( + tctx.pump_in, test_json_stream_io_pump_in_callback, &tctx); + iostream_pump_set_completion_callback( + tctx.pump_out, test_json_stream_io_pump_out_callback, &tctx); + + /* Processor 1 */ + test_sio_processor_init(&tproc1, test, pipe1_input, pipe2_output); + tproc1.tctx = &tctx; + tproc1.name = "proc_a"; + + /* Processor 2 */ + test_sio_processor_init(&tproc2, test, pipe2_input, pipe3_output); + tproc2.tctx = &tctx; + tproc2.name = "proc_b"; + + struct timeout *to = timeout_add(5000, io_loop_stop, ioloop); + + iostream_pump_start(tctx.pump_in); + iostream_pump_start(tctx.pump_out); + + io_loop_run(ioloop); + + timeout_remove(&to); + + test_sio_processor_deinit(&tproc1); + test_sio_processor_deinit(&tproc2); + + iostream_pump_destroy(&tctx.pump_in); + iostream_pump_destroy(&tctx.pump_out); + + i_stream_destroy(&input); + i_stream_destroy(&pipe1_input); + i_stream_destroy(&pipe2_input); + i_stream_destroy(&pipe3_input); + + o_stream_destroy(&output); + o_stream_destroy(&pipe1_output); + o_stream_destroy(&pipe2_output); + o_stream_destroy(&pipe3_output); + + io_loop_destroy(&ioloop); + + test_out_quiet(t_strdup_printf("%u: io match (async)", scenario), + strcmp(text_out, str_c(outbuf)) == 0); + + buffer_free(&outbuf); +} + +static void test_json_stream_io_async(void) +{ + unsigned int i, sc; + + 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++) + test_json_stream_io_async_run(&tests[i], sc); + test_end(); + } T_END; +} + +/* + * File I/O + */ + +static void +test_json_file_io_run(struct istream *input, struct ostream *output) +{ + struct json_istream *jinput; + struct json_ostream *joutput; + struct json_node jnode; + struct json_limits json_limits; + int rret, wret; + + i_zero(&json_limits); + json_limits.max_name_size = SIZE_MAX; + json_limits.max_string_size = SIZE_MAX; + json_limits.max_nesting = UINT_MAX; + json_limits.max_list_items = UINT_MAX; + + jinput = json_istream_create(input, 0, &json_limits, 0); + joutput = json_ostream_create(output, 0); + + rret = 0; wret = 1; + i_zero(&jnode); + for (;;) { + if (wret > 0 && rret == 0) { + rret = json_istream_walk(jinput, &jnode); + if (rret < 0) + break; + test_assert(!json_istream_failed(jinput)); + } + if (json_node_is_none(&jnode)) + wret = 1; + else + wret = json_ostream_write_node(joutput, &jnode, TRUE); + if (wret == 0) + continue; + if (wret < 0) + break; + i_zero(&jnode); + rret = 0; + } + wret = json_ostream_flush(joutput); + + test_out_reason("read success", + !json_istream_failed(jinput), + (!json_istream_failed(jinput) ? + NULL : json_istream_get_error(jinput))); + test_out("write success", wret > 0); + + json_ostream_destroy(&joutput); + json_istream_destroy(&jinput); +} + +static void +test_json_file_compare(struct istream *input1, struct istream *input2) +{ + const unsigned char *data1, *data2; + size_t size1, size2, pleft; + off_t ret; + + i_stream_seek(input1, 0); + i_stream_seek(input2, 0); + + /* read payload */ + while ((ret = i_stream_read_more(input1, &data1, &size1)) > 0) { + /* compare with file on disk */ + pleft = size1; + while ((ret = i_stream_read_more(input2, + &data2, &size2)) > 0 && + pleft > 0) { + size2 = (size2 > pleft ? pleft : size2); + if (memcmp(data1, data2, size2) != 0) { + i_fatal("processed data does not match " + "(%"PRIuUOFF_T":%"PRIuUOFF_T")", + input1->v_offset, input2->v_offset); + } + i_stream_skip(input2, size2); + pleft -= size2; + data1 += size2; + } + if (ret < 0 && input2->stream_errno != 0) { + i_fatal("failed to read result stream: %s", + i_stream_get_error(input2)); + } + i_stream_skip(input1, size1); + } + + i_assert(ret != 0); + + (void)i_stream_read(input2); + if (input2->stream_errno != 0) { + i_fatal("failed to read input stream: %s", + i_stream_get_error(input1)); + } if (i_stream_have_bytes_left(input2)) { + if (i_stream_read_more(input2, &data2, &size2) <= 0) + size2 = 0; + i_fatal("result stream ended prematurely " + "(at least %zu bytes left)", size2); + } +} + +static void test_json_file_io(const char *file) +{ + struct istream *input, *oinput; + struct ostream *output; + int fd; + + test_begin(t_strdup_printf("json file io [%s]", file)); + + fd = open(file, O_RDONLY); + if (fd < 0) + i_fatal("Failed to open: %m"); + + input = i_stream_create_fd_autoclose(&fd, 1024); + output = iostream_temp_create("/tmp/.test-json-io-", 0); + o_stream_set_no_error_handling(output, TRUE); + test_json_file_io_run(input, output); + i_stream_unref(&input); + + if (test_has_failed()) { + o_stream_unref(&output); + return; + } + + input = iostream_temp_finish(&output, IO_BLOCK_SIZE); + output = iostream_temp_create("/tmp/.test-json-io-", 0); + test_json_file_io_run(input, output); + + oinput = iostream_temp_finish(&output, IO_BLOCK_SIZE); + test_json_file_compare(input, oinput); + + i_stream_unref(&input); + i_stream_unref(&oinput); + + test_end(); +} + int main(int argc, char *argv[]) { int ret, c; @@ -1258,6 +1807,8 @@ int main(int argc, char *argv[]) static void (*test_functions[])(void) = { test_json_io, test_json_io_async, + test_json_stream_io, + test_json_stream_io_async, NULL }; @@ -1267,14 +1818,18 @@ int main(int argc, char *argv[]) debug = TRUE; break; default: - i_fatal("Usage: %s [-D]", argv[0]); + i_fatal("Usage: %s [-D] []", argv[0]); } } argc -= optind; argv += optind; - if (argc > 0) - i_fatal("Usage: %s [-D]", argv[0]); + if (argc > 1 ) + i_fatal("Usage: %s [-D] []", argv[0]); + if (argc == 1) { + test_json_file_io(argv[0]); + return 0; + } ret = test_run(test_functions); diff --git a/src/lib-json/test-json-ostream.c b/src/lib-json/test-json-ostream.c new file mode 100644 index 0000000000..0fed58408a --- /dev/null +++ b/src/lib-json/test-json-ostream.c @@ -0,0 +1,999 @@ +/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "ostream.h" +#include "test-common.h" + +#include "json-ostream.h" + +#include + +static bool debug = FALSE; + +static void test_json_ostream_write(void) +{ + string_t *buffer; + struct ostream *output; + struct json_ostream *joutput; + 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 - number"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_number(joutput, NULL, 23423); + i_assert(ret > 0); + 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 - false"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_false(joutput, NULL); + i_assert(ret > 0); + 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; + + /* false */ + test_begin("json ostream write - null"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_null(joutput, NULL); + i_assert(ret > 0); + 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 - true"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_true(joutput, NULL); + i_assert(ret > 0); + 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 - string"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_string(joutput, NULL, "frop!"); + i_assert(ret > 0); + 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; + + /* */ + test_begin("json ostream write - "); + joutput = json_ostream_create(output, 0); + ret = json_ostream_write_text(joutput, NULL, "\"frop!\""); + i_assert(ret > 0); + 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; + + /* [ ] */ + test_begin("json ostream write - array [ ]"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_array(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + 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 - array [ string ]"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_array(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_string(joutput, NULL, "frop"); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + 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; + + /* [ ] */ + test_begin("json ostream write - array [ ]"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_array(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_text(joutput, NULL, "[1,\"frop\",2]"); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + ret = json_ostream_flush(joutput); + i_assert(ret > 0); + json_ostream_unref(&joutput); + test_assert_strcmp("[[1,\"frop\",2]]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { } */ + test_begin("json ostream write - object { }"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + 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("{}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": "friep" } */ + test_begin("json ostream write - object { \"frop\": \"friep\" }"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_string(joutput, "frop", "friep"); + i_assert(ret > 0); + 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("{\"frop\":\"friep\"}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": 1234 } */ + test_begin("json ostream write - object { \"frop\": 1234 }"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_object_member(joutput, "frop"); + i_assert(ret > 0); + ret = json_ostream_write_number(joutput, NULL, 1234); + i_assert(ret > 0); + 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("{\"frop\":1234}", 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 - " + "object { \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], " + "\"c\": [{\"f\": 3}] }"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_descend_array(joutput, "a"); + i_assert(ret > 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_number(joutput, "d", 1); + i_assert(ret > 0); + ret = json_ostream_ascend_object(joutput); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + ret = json_ostream_descend_array(joutput, "b"); + i_assert(ret > 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_number(joutput, "e", 2); + i_assert(ret > 0); + ret = json_ostream_ascend_object(joutput); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + ret = json_ostream_descend_array(joutput, "c"); + i_assert(ret > 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_number(joutput, "f", 3); + i_assert(ret > 0); + ret = json_ostream_ascend_object(joutput); + i_assert(ret > 0); + ret = json_ostream_ascend_array(joutput); + i_assert(ret > 0); + 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; + + /* { "frop": } */ + test_begin("json ostream write - object { \"frop\": }"); + joutput = json_ostream_create(output, 0); + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret > 0); + ret = json_ostream_write_text(joutput, "frop", + "[false,\"friep\",true]"); + i_assert(ret > 0); + 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("{\"frop\":[false,\"friep\",true]}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle[1] */ + test_begin("json ostream write - object, trickle[1]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 17; 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: + ret = json_ostream_descend_array(joutput, "aaaaaa"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_ostream_write_number(joutput, "dddddd", 1); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 4: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 6: + ret = json_ostream_descend_array(joutput, "bbbbbb"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_ostream_write_number(joutput, "eeeeee", 2); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 9: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 11: + ret = json_ostream_descend_array(joutput, "cccccc"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 12: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_ostream_write_object_member( + joutput, "ffffff"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_ostream_write_number(joutput, NULL, 3); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 16: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 17: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + 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 - object, trickle[2]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 65535 && state <= 25; 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: + ret = json_ostream_descend_array(joutput, "aaaaaa"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_ostream_write_number(joutput, "dddddd", 1); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 4: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 6: + ret = json_ostream_write_number(joutput, "gggggg", 4); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 9: + ret = json_ostream_descend_array(joutput, "bbbbbb"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 11: + ret = json_ostream_write_number(joutput, "eeeeee", 2); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 12: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_ostream_write_number(joutput, "hhhhhh", 5); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 16: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 17: + ret = json_ostream_descend_array(joutput, "cccccc"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 18: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 19: + ret = json_ostream_write_number(joutput, "ffffff", 3); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 20: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 21: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 22: + ret = json_ostream_write_number(joutput, "iiiiii", 6); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 23: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 24: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 25: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + test_assert_strcmp("{\"aaaaaa\":[{\"dddddd\":1},{\"gggggg\":4}]," + "\"bbbbbb\":[{\"eeeeee\":2},{\"hhhhhh\":5}]," + "\"cccccc\":[{\"ffffff\":3},{\"iiiiii\":6}]}", + str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* trickle[3] */ + test_begin("json ostream write - object, trickle[3]"); + o_stream_set_max_buffer_size(output, 0); + joutput = json_ostream_create(output, 0); + state = 0; + for (pos = 0; pos < 400 && state <= 16; 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: + ret = json_ostream_descend_array(joutput, "aaaaaa"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 2: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 3: + ret = json_ostream_write_text(joutput, "dddddd", + "1234567"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 4: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 5: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 6: + ret = json_ostream_descend_array(joutput, "bbbbbb"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 7: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 8: + ret = json_ostream_write_text(joutput, "eeeeee", + "[1,2,3,4,5,6,7]"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 9: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 10: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 11: + ret = json_ostream_descend_array(joutput, "cccccc"); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 12: + ret = json_ostream_descend_object(joutput, NULL); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 13: + ret = json_ostream_write_text(joutput, "ffffff", + "\"1234567\""); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 14: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 15: + ret = json_ostream_ascend_array(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + case 16: + ret = json_ostream_ascend_object(joutput); + i_assert(ret >= 0); + if (ret == 0) break; + state++; + continue; + } + } + json_ostream_unref(&joutput); + 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; + + o_stream_destroy(&output); + str_free(&buffer); +} + +static void test_json_ostream_nwrite(void) +{ + string_t *buffer; + struct ostream *output; + struct json_ostream *joutput; + + 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 - number"); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_number(joutput, NULL, 23423); + 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 - false"); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_false(joutput, NULL); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("false", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* false */ + test_begin("json ostream nwrite - null"); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_null(joutput, NULL); + 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 - true"); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_true(joutput, NULL); + 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 - string"); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("\"frop!\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* formatted string */ + test_begin("json ostream nwrite - formatted string"); + joutput = json_ostream_create(output, 0); + json_ostream_nwritef_string(joutput, NULL, "%u", 12345); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("\"12345\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* */ + test_begin("json ostream nwrite - "); + joutput = json_ostream_create(output, 0); + json_ostream_nwrite_text(joutput, NULL, "\"frop!\""); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("\"frop!\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ ] */ + test_begin("json ostream nwrite - array [ ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_array(joutput, NULL); + json_ostream_nascend_array(joutput); + 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 - array [ string ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_array(joutput, NULL); + json_ostream_nwrite_string(joutput, NULL, "frop"); + json_ostream_nascend_array(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[\"frop\"]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* [ ] */ + test_begin("json ostream nwrite - array [ ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_array(joutput, NULL); + json_ostream_nwrite_text(joutput, NULL, "[1,\"frop\",2]"); + json_ostream_nascend_array(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[[1,\"frop\",2]]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { } */ + test_begin("json ostream nwrite - object { }"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* { "frop": "friep" } */ + test_begin("json ostream nwrite - object { \"frop\": \"friep\" }"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + json_ostream_nwrite_string(joutput, "frop", "friep"); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{\"frop\":\"friep\"}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* complex example */ + test_begin("json ostream nwrite - complex example"); + joutput = json_ostream_create_str(buffer, 0); + json_ostream_ndescend_object(joutput, NULL); + json_ostream_nwrite_string(joutput, "user", "testuser3"); + json_ostream_nwrite_string(joutput, "event", "messageNew"); + json_ostream_nwrite_string(joutput, "folder", "INBOX"); + json_ostream_nwrite_number(joutput, "imap-uidvalidity", 1588805816); + json_ostream_nwrite_number(joutput, "imap-uid", 1); + json_ostream_nwrite_string( + joutput, "from", + "Source =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?= " + ""); + json_ostream_nwrite_string( + joutput, "subject", + "Stuff =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?="); + json_ostream_nwrite_string(joutput, "snippet", + "P\xc3\xa4iv\xc3\xa4\xc3\xa4."); + json_ostream_nwrite_number(joutput, "unseen", 1); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp( + "{\"user\":\"testuser3\"," + "\"event\":\"messageNew\"," + "\"folder\":\"INBOX\"," + "\"imap-uidvalidity\":1588805816," + "\"imap-uid\":1," + "\"from\":\"Source =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?= \"," + "\"subject\":\"Stuff =?utf8?q?p=C3=A4_=3Dutf8=3Fq=3Fencoding=3F=3D?=\"," + "\"snippet\":\"P\xc3\xa4iv\xc3\xa4\xc3\xa4.\"," + "\"unseen\":1}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* concatenated string */ + test_begin("json ostream nwrite - concatenated string"); + joutput = json_ostream_create(output, 0); + json_ostream_nopen_string(joutput, NULL); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nclose_string(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("\"friep!frop!frml!\"", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* array [ concatenated string ] */ + test_begin("json ostream nwrite - array [ concatenated string ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_array(joutput, NULL); + json_ostream_nopen_string(joutput, NULL); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nclose_string(joutput); + json_ostream_nascend_array(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[\"friep!frop!frml!\"]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* object { concatenated string } */ + test_begin("json ostream nwrite - object { concatenated string }"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + json_ostream_nopen_string(joutput, "foo"); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nclose_string(joutput); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{\"foo\":\"friep!frop!frml!\"}", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* array [ complex concatenated string ] */ + test_begin("json ostream nwrite - " + "array [ complex concatenated string ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_array(joutput, NULL); + json_ostream_nopen_string(joutput, NULL); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nclose_string(joutput); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nopen_string(joutput, NULL); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nclose_string(joutput); + json_ostream_nascend_array(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("[\"friep!frop!frml!\"," + "\"friep!\",\"frop!\",\"frml!\"," + "\"frml!frop!friep!\"]", str_c(buffer)); + test_end(); + str_truncate(buffer, 0); + output->offset = 0; + + /* object [ complex concatenated string ] */ + test_begin("json ostream nwrite - " + "object [ complex concatenated string ]"); + joutput = json_ostream_create(output, 0); + json_ostream_ndescend_object(joutput, NULL); + json_ostream_nopen_string(joutput, "a"); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nclose_string(joutput); + json_ostream_nwrite_string(joutput, "b", "friep!"); + json_ostream_nwrite_string(joutput, "c", "frop!"); + json_ostream_nwrite_string(joutput, "d", "frml!"); + json_ostream_nopen_string(joutput, "e"); + json_ostream_nwrite_string(joutput, NULL, "frml!"); + json_ostream_nwrite_string(joutput, NULL, "frop!"); + json_ostream_nwrite_string(joutput, NULL, "friep!"); + json_ostream_nclose_string(joutput); + json_ostream_nascend_object(joutput); + json_ostream_nfinish_destroy(&joutput); + test_assert_strcmp("{\"a\":\"friep!frop!frml!\"," + "\"b\":\"friep!\",\"c\":\"frop!\",\"d\":\"frml!\"," + "\"e\":\"frml!frop!friep!\"}", 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; + + static void (*test_functions[])(void) = { + test_json_ostream_write, + test_json_ostream_nwrite, + NULL + }; + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + return test_run(test_functions); +}