From 094fce37765964a9c3b32d1473e75c7d121eace1 Mon Sep 17 00:00:00 2001 From: Peter Rajnoha Date: Mon, 2 May 2016 14:21:40 +0200 Subject: [PATCH] libdm: report: implement DM_REPORT_GROUP_JSON for JSON report output This patch introduces DM_REPORT_GROUP_JSON report group type. When using this group type and when pushing a report to such a group, these flags are automatically unset: DM_REPORT_OUTPUT_ALIGNED DM_REPORT_OUTPUT_HEADINGS DM_REPORT_OUTPUT_COLUMNS_AS_ROWS ...and this flag is set: DM_REPORT_OUTPUT_BUFFERED The whole group is encapsulated in { } for the outermost JSON object and then each report is reported on output as array of objects where each object is the row from report: { "report_name1": [ {field1="value", field2="value",...}, {field1="value", field2="value",...} ... ], "report_name2": [ {field1="value", field2="value",...}, {field1="value", field2="value",...} ... ] ... } --- WHATS_NEW_DM | 1 + libdm/libdevmapper.h | 3 +- libdm/libdm-report.c | 241 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 233 insertions(+), 12 deletions(-) diff --git a/WHATS_NEW_DM b/WHATS_NEW_DM index 9e1985ac1..da36f490a 100644 --- a/WHATS_NEW_DM +++ b/WHATS_NEW_DM @@ -1,5 +1,6 @@ Version 1.02.128 - ================================= + Introduce DM_REPORT_GROUP_JSON for report group with JSON output format. Introduce DM_REPORT_GROUP_BASIC for report group with basic report output. Introduce DM_REPORT_GROUP_SINGLE for report group having single report only. Add dm_report_group_{create,push,pop,destroy} to support report grouping. diff --git a/libdm/libdevmapper.h b/libdm/libdevmapper.h index fbe432298..cad79dd0c 100644 --- a/libdm/libdevmapper.h +++ b/libdm/libdevmapper.h @@ -2710,7 +2710,8 @@ struct dm_report_group; typedef enum { DM_REPORT_GROUP_SINGLE, - DM_REPORT_GROUP_BASIC + DM_REPORT_GROUP_BASIC, + DM_REPORT_GROUP_JSON } dm_report_group_type_t; struct dm_report_group *dm_report_group_create(dm_report_group_type_t type, void *data); diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c index d73598fb1..485bbdf9a 100644 --- a/libdm/libdm-report.c +++ b/libdm/libdm-report.c @@ -4203,6 +4203,16 @@ static int _sort_rows(struct dm_report *rh) #define STANDARD_QUOTE "\'" #define STANDARD_PAIR "=" +#define JSON_INDENT_UNIT 4 +#define JSON_SPACE " " +#define JSON_QUOTE "\"" +#define JSON_PAIR ":" +#define JSON_SEPARATOR "," +#define JSON_OBJECT_START "{" +#define JSON_OBJECT_END "}" +#define JSON_ARRAY_START "[" +#define JSON_ARRAY_END "]" + #define UNABLE_TO_EXTEND_OUTPUT_LINE_MSG "dm_report: Unable to extend output line" static int _is_basic_report(struct dm_report *rh) @@ -4211,6 +4221,12 @@ static int _is_basic_report(struct dm_report *rh) (rh->group_item->group->type == DM_REPORT_GROUP_BASIC); } +static int _is_json_report(struct dm_report *rh) +{ + return rh->group_item && + (rh->group_item->group->type == DM_REPORT_GROUP_JSON); +} + /* * Produce report output */ @@ -4225,7 +4241,16 @@ static int _output_field(struct dm_report *rh, struct dm_report_field *field) char *buf = NULL; size_t buf_size = 0; - if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) { + if (_is_json_report(rh)) { + if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1) || + !dm_pool_grow_object(rh->mem, fields[field->props->field_num].id, 0) || + !dm_pool_grow_object(rh->mem, JSON_QUOTE, 1) || + !dm_pool_grow_object(rh->mem, JSON_PAIR, 1) || + !dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) { + log_error("dm_report: Unable to extend output line"); + return 0; + } + } else if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) { if (!(field_id = dm_strdup(fields[field->props->field_num].id))) { log_error("dm_report: Failed to copy field name"); return 0; @@ -4300,12 +4325,19 @@ static int _output_field(struct dm_report *rh, struct dm_report_field *field) } } - if ((rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) && - !(rh->flags & DM_REPORT_OUTPUT_FIELD_UNQUOTED)) - if (!dm_pool_grow_object(rh->mem, STANDARD_QUOTE, 1)) { + if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) { + if (!(rh->flags & DM_REPORT_OUTPUT_FIELD_UNQUOTED)) { + if (!dm_pool_grow_object(rh->mem, STANDARD_QUOTE, 1)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + } + } else if (_is_json_report(rh)) { + if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) { log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); goto bad; } + } dm_free(buf); return 1; @@ -4407,39 +4439,78 @@ static int _output_as_columns(struct dm_report *rh) struct dm_list *fh, *rowh, *ftmp, *rtmp; struct row *row = NULL; struct dm_report_field *field; + struct dm_list *last_row; + int do_field_delim; + char *line; /* If headings not printed yet, calculate field widths and print them */ if (!(rh->flags & RH_HEADINGS_PRINTED)) _report_headings(rh); /* Print and clear buffer */ + last_row = dm_list_last(&rh->rows); dm_list_iterate_safe(rowh, rtmp, &rh->rows) { if (!dm_pool_begin_object(rh->mem, 512)) { log_error("dm_report: Unable to allocate output line"); return 0; } + + if (_is_json_report(rh)) { + if (!dm_pool_grow_object(rh->mem, JSON_OBJECT_START, 0)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + } + row = dm_list_item(rowh, struct row); + do_field_delim = 0; + dm_list_iterate_safe(fh, ftmp, &row->fields) { field = dm_list_item(fh, struct dm_report_field); if (field->props->flags & FLD_HIDDEN) continue; + if (do_field_delim) { + if (_is_json_report(rh)) { + if (!dm_pool_grow_object(rh->mem, JSON_SEPARATOR, 0) || + !dm_pool_grow_object(rh->mem, JSON_SPACE, 0)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + } else { + if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + } + } else + do_field_delim = 1; + if (!_output_field(rh, field)) goto bad; - if (!dm_list_end(&row->fields, fh)) - if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) { - log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); - goto bad; - } - dm_list_del(&field->list); } + + if (_is_json_report(rh)) { + if (!dm_pool_grow_object(rh->mem, JSON_OBJECT_END, 0)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + if (rowh != last_row && + !dm_pool_grow_object(rh->mem, JSON_SEPARATOR, 0)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + } + if (!dm_pool_grow_object(rh->mem, "\0", 1)) { log_error("dm_report: Unable to terminate output line"); goto bad; } - log_print("%s", (char *) dm_pool_end_object(rh->mem)); + + line = (char *) dm_pool_end_object(rh->mem); + log_print("%*s", rh->group_item ? rh->group_item->group->indent + (int) strlen(line) : 0, line); dm_list_del(&row->list); } @@ -4469,6 +4540,70 @@ static struct report_group_item *_get_topmost_report_group_item(struct dm_report return item; } +static int _json_output_array_start(struct dm_pool *mem, struct report_group_item *item) +{ + const char *name = (const char *) item->data; + char *output; + + if (!dm_pool_begin_object(mem, 32)) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + return 0; + } + + if (!dm_pool_grow_object(mem, JSON_QUOTE, 1) || + !dm_pool_grow_object(mem, name, 0) || + !dm_pool_grow_object(mem, JSON_QUOTE JSON_PAIR JSON_SPACE JSON_ARRAY_START, 0) || + !dm_pool_grow_object(mem, "\0", 1) || + !(output = dm_pool_end_object(mem))) { + log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG); + goto bad; + } + + if (item->parent->store.finished_count > 0) + log_print("%*s", item->group->indent + (int) sizeof(JSON_SEPARATOR) - 1, JSON_SEPARATOR); + + if (item->parent->parent && item->parent->data) { + log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_START) - 1, JSON_OBJECT_START); + item->group->indent += JSON_INDENT_UNIT; + } + + log_print("%*s", item->group->indent + (int) strlen(output), output); + item->group->indent += JSON_INDENT_UNIT; + + dm_pool_free(mem, output); + return 1; +bad: + dm_pool_abandon_object(mem); + return 0; +} + +static int _prepare_json_report_output(struct dm_report *rh) +{ + if (rh->group_item->output_done && dm_list_empty(&rh->rows)) + return 1; + + /* + * If this report is in JSON group, it must be at the + * top of the stack of reports so the output from + * different reports do not interleave with each other. + */ + if (_get_topmost_report_group_item(rh->group_item->group) != rh->group_item) { + log_error("dm_report: dm_report_output: interleaved reports detected for JSON output"); + return 0; + } + + if (rh->group_item->needs_closing) { + log_error("dm_report: dm_report_output: unfinished JSON output detected"); + return 0; + } + + if (!_json_output_array_start(rh->mem, rh->group_item)) + return_0; + + rh->group_item->needs_closing = 1; + return 1; +} + static int _print_basic_report_header(struct dm_report *rh) { const char *report_name = (const char *) rh->group_item->data; @@ -4493,6 +4628,10 @@ int dm_report_output(struct dm_report *rh) { int r = 0; + if (_is_json_report(rh) && + !_prepare_json_report_output(rh)) + return_0; + if (dm_list_empty(&rh->rows)) { r = 1; goto out; @@ -4524,6 +4663,13 @@ static int _report_group_create_basic(struct dm_report_group *group) return 1; } +static int _report_group_create_json(struct dm_report_group *group) +{ + log_print(JSON_OBJECT_START); + group->indent += JSON_INDENT_UNIT; + return 1; +} + struct dm_report_group *dm_report_group_create(dm_report_group_type_t type, void *data) { struct dm_report_group *group; @@ -4560,6 +4706,10 @@ struct dm_report_group *dm_report_group_create(dm_report_group_type_t type, void if (!_report_group_create_basic(group)) goto_bad; break; + case DM_REPORT_GROUP_JSON: + if (!_report_group_create_json(group)) + goto_bad; + break; default: goto_bad; } @@ -4597,6 +4747,40 @@ static int _report_group_push_basic(struct report_group_item *item, const char * return 1; } +static int _report_group_push_json(struct report_group_item *item, const char *name) +{ + if (name && !(item->data = dm_pool_strdup(item->group->mem, name))) { + log_error("dm_report: failed to duplicate json item name"); + return 0; + } + + if (item->report) { + item->report->flags &= ~(DM_REPORT_OUTPUT_ALIGNED | + DM_REPORT_OUTPUT_HEADINGS | + DM_REPORT_OUTPUT_COLUMNS_AS_ROWS); + item->report->flags |= DM_REPORT_OUTPUT_BUFFERED; + } else { + if (name) { + if (!_json_output_array_start(item->group->mem, item)) + return_0; + } else { + if (!item->parent->parent) { + log_error("dm_report: can't use unnamed object at top level of JSON output"); + return 0; + } + if (item->parent->store.finished_count > 0) + log_print("%*s", item->group->indent + (int) sizeof(JSON_SEPARATOR) - 1, JSON_SEPARATOR); + log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_START) - 1, JSON_OBJECT_START); + item->group->indent += JSON_INDENT_UNIT; + } + + item->output_done = 1; + item->needs_closing = 1; + } + + return 1; +} + int dm_report_group_push(struct dm_report_group *group, struct dm_report *report, void *data) { struct report_group_item *item, *tmp_item; @@ -4634,6 +4818,10 @@ int dm_report_group_push(struct dm_report_group *group, struct dm_report *report if (!_report_group_push_basic(item, data)) goto_bad; break; + case DM_REPORT_GROUP_JSON: + if (!_report_group_push_json(item, data)) + goto_bad; + break; default: goto_bad; } @@ -4655,6 +4843,23 @@ static int _report_group_pop_basic(struct report_group_item *item) return 1; } +static int _report_group_pop_json(struct report_group_item *item) +{ + if (item->output_done && item->needs_closing) { + if (item->data) { + item->group->indent -= JSON_INDENT_UNIT; + log_print("%*s", item->group->indent + (int) sizeof(JSON_ARRAY_END) - 1, JSON_ARRAY_END); + } + if (item->parent->data && item->parent->parent) { + item->group->indent -= JSON_INDENT_UNIT; + log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_END) - 1, JSON_OBJECT_END); + } + item->needs_closing = 0; + } + + return 1; +} + int dm_report_group_pop(struct dm_report_group *group) { struct report_group_item *item; @@ -4676,6 +4881,10 @@ int dm_report_group_pop(struct dm_report_group *group) if (!_report_group_pop_basic(item)) return_0; break; + case DM_REPORT_GROUP_JSON: + if (!_report_group_pop_json(item)) + return_0; + break; default: return 0; } @@ -4704,6 +4913,12 @@ static int _report_group_destroy_basic(void) return 1; } +static int _report_group_destroy_json(void) +{ + log_print(JSON_OBJECT_END); + return 1; +} + int dm_report_group_destroy(struct dm_report_group *group) { struct report_group_item *item, *tmp_item; @@ -4728,6 +4943,10 @@ int dm_report_group_destroy(struct dm_report_group *group) if (!_report_group_destroy_basic()) goto_out; break; + case DM_REPORT_GROUP_JSON: + if (!_report_group_destroy_json()) + goto_out; + break; default: goto_out; }