diff --git a/src/libbson/src/bson/bson-json-private.h b/src/libbson/src/bson/bson-json-private.h index 9f51dcbd7eb..003171b2a55 100644 --- a/src/libbson/src/bson/bson-json-private.h +++ b/src/libbson/src/bson/bson-json-private.h @@ -23,6 +23,9 @@ struct _bson_json_opts_t { bson_json_mode_t mode; int32_t max_len; + const char *initial_indent; + const char *subsequent_indent; + const char *level_indent; }; diff --git a/src/libbson/src/bson/bson-json.c b/src/libbson/src/bson/bson-json.c index 474b3e91174..1ed284dfad5 100644 --- a/src/libbson/src/bson/bson-json.c +++ b/src/libbson/src/bson/bson-json.c @@ -387,13 +387,12 @@ _noop (void) } - bson_json_opts_t * bson_json_opts_new (bson_json_mode_t mode, int32_t max_len) { bson_json_opts_t *opts; - opts = (bson_json_opts_t *) bson_malloc (sizeof *opts); + opts = (bson_json_opts_t *) bson_malloc0 (sizeof *opts); opts->mode = mode; opts->max_len = max_len; diff --git a/src/libbson/src/bson/bson.c b/src/libbson/src/bson/bson.c index 5af710e4ddb..ef9d180ea34 100644 --- a/src/libbson/src/bson/bson.c +++ b/src/libbson/src/bson/bson.c @@ -62,9 +62,9 @@ typedef struct { ssize_t *err_offset; uint32_t depth; bson_string_t *str; - bson_json_mode_t mode; int32_t max_len; bool max_len_reached; + const bson_json_opts_t *opts; } bson_json_state_t; @@ -84,8 +84,7 @@ _bson_as_json_visit_document (const bson_iter_t *iter, static char * _bson_as_json_visit_all (const bson_t *bson, size_t *length, - bson_json_mode_t mode, - int32_t max_len); + const bson_json_opts_t *opts); /* * Globals. @@ -2510,6 +2509,25 @@ bson_equal (const bson_t *bson, const bson_t *other) } +static void +_bson_json_newline_indent (const bson_json_state_t *state) +{ + if (!state->opts->level_indent) { + return; + } + bson_string_append (state->str, "\n"); + if (state->opts->subsequent_indent) { + bson_string_append (state->str, state->opts->subsequent_indent); + } + if (state->opts->level_indent) { + int i = 0; + for (i = 0; i < state->depth + 1; ++i) { + bson_string_append (state->str, state->opts->level_indent); + } + } +} + + static bool _bson_as_json_visit_utf8 (const bson_iter_t *iter, const char *key, @@ -2542,7 +2560,7 @@ _bson_as_json_visit_int32 (const bson_iter_t *iter, { bson_json_state_t *state = data; - if (state->mode == BSON_JSON_MODE_CANONICAL) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL) { bson_string_append_printf ( state->str, "{ \"$numberInt\" : \"%" PRId32 "\" }", v_int32); } else { @@ -2561,7 +2579,7 @@ _bson_as_json_visit_int64 (const bson_iter_t *iter, { bson_json_state_t *state = data; - if (state->mode == BSON_JSON_MODE_CANONICAL) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL) { bson_string_append_printf ( state->str, "{ \"$numberLong\" : \"%" PRId64 "\" }", v_int64); } else { @@ -2604,8 +2622,8 @@ _bson_as_json_visit_double (const bson_iter_t *iter, /* Determine if legacy (i.e. unwrapped) output should be used. Relaxed mode * will use this for nan and inf values, which we check manually since old * platforms may not have isinf or isnan. */ - legacy = state->mode == BSON_JSON_MODE_LEGACY || - (state->mode == BSON_JSON_MODE_RELAXED && + legacy = state->opts->mode == BSON_JSON_MODE_LEGACY || + (state->opts->mode == BSON_JSON_MODE_RELAXED && !(v_double != v_double || v_double * 0 != 0)); if (!legacy) { @@ -2697,8 +2715,8 @@ _bson_as_json_visit_binary (const bson_iter_t *iter, b64 = bson_malloc0 (b64_len); BSON_ASSERT (mcommon_b64_ntop (v_binary, v_binary_len, b64, b64_len) != -1); - if (state->mode == BSON_JSON_MODE_CANONICAL || - state->mode == BSON_JSON_MODE_RELAXED) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL || + state->opts->mode == BSON_JSON_MODE_RELAXED) { bson_string_append (state->str, "{ \"$binary\" : { \"base64\" : \""); bson_string_append (state->str, b64); bson_string_append (state->str, "\", \"subType\" : \""); @@ -2740,12 +2758,12 @@ _bson_as_json_visit_date_time (const bson_iter_t *iter, { bson_json_state_t *state = data; - if (state->mode == BSON_JSON_MODE_CANONICAL || - (state->mode == BSON_JSON_MODE_RELAXED && msec_since_epoch < 0)) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL || + (state->opts->mode == BSON_JSON_MODE_RELAXED && msec_since_epoch < 0)) { bson_string_append (state->str, "{ \"$date\" : { \"$numberLong\" : \""); bson_string_append_printf (state->str, "%" PRId64, msec_since_epoch); bson_string_append (state->str, "\" } }"); - } else if (state->mode == BSON_JSON_MODE_RELAXED) { + } else if (state->opts->mode == BSON_JSON_MODE_RELAXED) { bson_string_append (state->str, "{ \"$date\" : \""); _bson_iso8601_date_format (msec_since_epoch, state->str); bson_string_append (state->str, "\" }"); @@ -2774,8 +2792,8 @@ _bson_as_json_visit_regex (const bson_iter_t *iter, return true; } - if (state->mode == BSON_JSON_MODE_CANONICAL || - state->mode == BSON_JSON_MODE_RELAXED) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL || + state->opts->mode == BSON_JSON_MODE_RELAXED) { bson_string_append (state->str, "{ \"$regularExpression\" : { \"pattern\" : \""); bson_string_append (state->str, escaped); @@ -2832,8 +2850,8 @@ _bson_as_json_visit_dbpointer (const bson_iter_t *iter, return true; } - if (state->mode == BSON_JSON_MODE_CANONICAL || - state->mode == BSON_JSON_MODE_RELAXED) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL || + state->opts->mode == BSON_JSON_MODE_RELAXED) { bson_string_append (state->str, "{ \"$dbPointer\" : { \"$ref\" : \""); bson_string_append (state->str, escaped); bson_string_append (state->str, "\""); @@ -2909,6 +2927,8 @@ _bson_as_json_visit_before (const bson_iter_t *iter, bson_string_append (state->str, ", "); } + _bson_json_newline_indent (state); + if (state->keys) { escaped = bson_utf8_escape_for_json (key, -1); if (escaped) { @@ -2947,6 +2967,8 @@ _bson_as_json_visit_after (const bson_iter_t *iter, const char *key, void *data) return true; } + _bson_json_newline_indent (state); + return false; } @@ -2997,8 +3019,8 @@ _bson_as_json_visit_symbol (const bson_iter_t *iter, return true; } - if (state->mode == BSON_JSON_MODE_CANONICAL || - state->mode == BSON_JSON_MODE_RELAXED) { + if (state->opts->mode == BSON_JSON_MODE_CANONICAL || + state->opts->mode == BSON_JSON_MODE_RELAXED) { bson_string_append (state->str, "{ \"$symbol\" : \""); bson_string_append (state->str, escaped); bson_string_append (state->str, "\" }"); @@ -3025,7 +3047,6 @@ _bson_as_json_visit_codewscope (const bson_iter_t *iter, bson_json_state_t *state = data; char *code_escaped; char *scope; - int32_t max_scope_len = BSON_MAX_LEN_UNLIMITED; code_escaped = bson_utf8_escape_for_json (v_code, v_code_len); if (!code_escaped) { @@ -3038,12 +3059,7 @@ _bson_as_json_visit_codewscope (const bson_iter_t *iter, bson_free (code_escaped); - /* Encode scope with the same mode */ - if (state->max_len != BSON_MAX_LEN_UNLIMITED) { - max_scope_len = BSON_MAX (0, state->max_len - state->str->len); - } - - scope = _bson_as_json_visit_all (v_scope, NULL, state->mode, max_scope_len); + scope = _bson_as_json_visit_all (v_scope, NULL, state->opts); if (!scope) { return true; @@ -3090,10 +3106,12 @@ _bson_as_json_visit_document (const bson_iter_t *iter, return false; } - if (bson_iter_init (&child, v_document)) { + if (bson_empty (v_document)) { + child_state.str = bson_string_new ("{}"); + } else if (bson_iter_init (&child, v_document)) { child_state.str = bson_string_new ("{ "); child_state.depth = state->depth + 1; - child_state.mode = state->mode; + child_state.opts = state->opts; child_state.max_len = BSON_MAX_LEN_UNLIMITED; if (state->max_len != BSON_MAX_LEN_UNLIMITED) { child_state.max_len = BSON_MAX (0, state->max_len - state->str->len); @@ -3114,7 +3132,12 @@ _bson_as_json_visit_document (const bson_iter_t *iter, return !child_state.max_len_reached; } - bson_string_append (child_state.str, " }"); + child_state.depth -= 1; + _bson_json_newline_indent (&child_state); + bson_string_append (child_state.str, + state->opts->level_indent ? "}" : " }"); + } + if (child_state.str) { bson_string_append (state->str, child_state.str->str); bson_string_free (child_state.str, true); } @@ -3141,7 +3164,7 @@ _bson_as_json_visit_array (const bson_iter_t *iter, if (bson_iter_init (&child, v_array)) { child_state.str = bson_string_new ("[ "); child_state.depth = state->depth + 1; - child_state.mode = state->mode; + child_state.opts = state->opts; child_state.max_len = BSON_MAX_LEN_UNLIMITED; if (state->max_len != BSON_MAX_LEN_UNLIMITED) { child_state.max_len = BSON_MAX (0, state->max_len - state->str->len); @@ -3162,7 +3185,10 @@ _bson_as_json_visit_array (const bson_iter_t *iter, return !child_state.max_len_reached; } - bson_string_append (child_state.str, " ]"); + child_state.depth -= 1; + _bson_json_newline_indent (&child_state); + bson_string_append (child_state.str, + state->opts->level_indent ? "]" : " ]"); bson_string_append (state->str, child_state.str->str); bson_string_free (child_state.str, true); } @@ -3174,8 +3200,7 @@ _bson_as_json_visit_array (const bson_iter_t *iter, static char * _bson_as_json_visit_all (const bson_t *bson, size_t *length, - bson_json_mode_t mode, - int32_t max_len) + const bson_json_opts_t *opts) { bson_json_state_t state; bson_iter_t iter; @@ -3202,11 +3227,16 @@ _bson_as_json_visit_all (const bson_t *bson, state.count = 0; state.keys = true; - state.str = bson_string_new ("{ "); + state.opts = opts; + if (state.opts->initial_indent) { + state.str = bson_string_new (state.opts->initial_indent); + bson_string_append_c (state.str, '{'); + } else { + state.str = bson_string_new ("{ "); + } state.depth = 0; state.err_offset = &err_offset; - state.mode = mode; - state.max_len = max_len; + state.max_len = opts->max_len; state.max_len_reached = false; if ((bson_iter_visit_all (&iter, &bson_as_json_visitors, &state) || @@ -3222,11 +3252,16 @@ _bson_as_json_visit_all (const bson_t *bson, return NULL; } - /* Append closing space and } separately, in case we hit the max in between. */ + /* Append closing space and } separately, in case we hit the max in between. + */ remaining = state.max_len - state.str->len; - if (state.max_len == BSON_MAX_LEN_UNLIMITED || - remaining > 1) { - bson_string_append (state.str, " }"); + if (state.max_len == BSON_MAX_LEN_UNLIMITED || remaining > 1) { + if (state.opts->level_indent && state.opts->subsequent_indent) { + bson_string_append_printf ( + state.str, "\n%s}", state.opts->subsequent_indent); + } else { + bson_string_append (state.str, " }"); + } } else if (remaining == 1) { bson_string_append (state.str, " "); } @@ -3244,7 +3279,7 @@ bson_as_json_with_opts (const bson_t *bson, size_t *length, const bson_json_opts_t *opts) { - return _bson_as_json_visit_all (bson, length, opts->mode, opts->max_len); + return _bson_as_json_visit_all (bson, length, opts); } @@ -3286,7 +3321,7 @@ bson_array_as_json (const bson_t *bson, size_t *length) if (length) { *length = 0; - } + } if (bson_empty0 (bson)) { if (length) { @@ -3305,9 +3340,12 @@ bson_array_as_json (const bson_t *bson, size_t *length) state.str = bson_string_new ("[ "); state.depth = 0; state.err_offset = &err_offset; - state.mode = BSON_JSON_MODE_LEGACY; state.max_len = BSON_MAX_LEN_UNLIMITED; state.max_len_reached = false; + bson_json_opts_t opts = {0}; + opts.mode = BSON_JSON_MODE_LEGACY; + opts.max_len = BSON_MAX_LEN_UNLIMITED; + state.opts = &opts; if ((bson_iter_visit_all (&iter, &bson_as_json_visitors, &state) || err_offset != -1) && diff --git a/src/libmongoc/tests/json-test-monitoring.c b/src/libmongoc/tests/json-test-monitoring.c index 1a22bcda0dc..10f972fc1a6 100644 --- a/src/libmongoc/tests/json-test-monitoring.c +++ b/src/libmongoc/tests/json-test-monitoring.c @@ -24,6 +24,7 @@ #include "mongoc/mongoc-topology-private.h" #include "mongoc/mongoc-util-private.h" #include "mongoc/mongoc-util-private.h" +#include <bson/bson-json-private.h> #include "TestSuite.h" #include "test-conveniences.h" @@ -425,22 +426,33 @@ apm_match_visitor (match_ctx_t *ctx, return MATCH_ACTION_CONTINUE; } +static void +_print_bson_array_as_json (FILE *out, const bson_t *arr) +{ + bson_iter_t it; + for (bson_iter_init (&it, arr); bson_iter_next (&it);) { + bson_t elem; + bson_iter_bson (&it, &elem); + + bson_json_opts_t opts = {0}; + opts.initial_indent = ""; + opts.level_indent = " "; + opts.subsequent_indent = " "; + opts.mode = BSON_JSON_MODE_CANONICAL; + opts.max_len = BSON_MAX_LEN_UNLIMITED; + char *str = bson_as_json_with_opts (&elem, NULL, &opts); + fprintf (out, " - %s\n", str); + bson_free (str); + } +} static void _apm_match_error_context (const bson_t *actual, const bson_t *expectations) { - char *actual_str; - char *expectations_str; - - actual_str = bson_as_canonical_extended_json (actual, NULL); - expectations_str = bson_as_canonical_extended_json (expectations, NULL); - fprintf (stderr, - "Error in APM matching\nFull list of captured events: %s\nFull " - "list of expectations: %s", - actual_str, - expectations_str); - bson_free (actual_str); - bson_free (expectations_str); + fprintf (stderr, "Error in APM matching.\nFull list of captured events:\n"); + _print_bson_array_as_json (stderr, actual); + fprintf (stderr, "Expected events:\n"); + _print_bson_array_as_json (stderr, expectations); } bool