]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
format-table: allow to explicitly override JSON field names
authorLennart Poettering <lennart@poettering.net>
Fri, 3 Sep 2021 09:11:18 +0000 (11:11 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 3 Sep 2021 13:37:26 +0000 (14:37 +0100)
In some cases it's useful to explicitly generate the JSON field names to
generate for table columns, instead of auto-mangling them from table
header names that are intended for human consumption.

This adds the infra and a test for it.

It's intended to be used by #20544, for the first column, which in text
mode should have an empty header field, but have an explicit name in
json output mode.

src/shared/format-table.c
src/shared/format-table.h
src/test/test-format-table.c

index 9651fd8c0010cafd994ab993aa619c15e66fd612..5390eddcd67aff77fbff512bc2d0dcce322b361c 100644 (file)
@@ -145,6 +145,9 @@ struct Table {
         size_t *sort_map;     /* The columns to order rows by, in order of preference. */
         size_t n_sort_map;
 
+        char **json_fields;
+        size_t n_json_fields;
+
         bool *reverse_map;
 
         char *empty_string;
@@ -241,6 +244,11 @@ Table *table_unref(Table *t) {
         free(t->reverse_map);
         free(t->empty_string);
 
+        for (size_t i = 0; i < t->n_json_fields; i++)
+                free(t->json_fields[i]);
+
+        free(t->json_fields);
+
         return mfree(t);
 }
 
@@ -2608,6 +2616,12 @@ static char* string_to_json_field_name(const char *f) {
         return c;
 }
 
+static const char *table_get_json_field_name(Table *t, size_t column) {
+        assert(t);
+
+        return column < t->n_json_fields ? t->json_fields[column] : NULL;
+}
+
 int table_to_json(Table *t, JsonVariant **ret) {
         JsonVariant **rows = NULL, **elements = NULL;
         _cleanup_free_ size_t *sorted = NULL;
@@ -2651,26 +2665,36 @@ int table_to_json(Table *t, JsonVariant **ret) {
 
         for (size_t j = 0; j < display_columns; j++) {
                 _cleanup_free_ char *mangled = NULL;
-                const char *formatted;
-                TableData *d;
+                const char *n;
+                size_t c;
 
-                assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
+                c = t->display_map ? t->display_map[j] : j;
 
-                /* Field names must be strings, hence format whatever we got here as a string first */
-                formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
-                if (!formatted) {
-                        r = -ENOMEM;
-                        goto finish;
-                }
+                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
+                n = table_get_json_field_name(t, c);
+                if (!n) {
+                        const char *formatted;
+                        TableData *d;
 
-                /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
-                mangled = string_to_json_field_name(formatted);
-                if (!mangled) {
-                        r = -ENOMEM;
-                        goto finish;
+                        assert_se(d = t->data[c]);
+
+                        /* Field names must be strings, hence format whatever we got here as a string first */
+                        formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
+                        if (!formatted) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
+                        mangled = string_to_json_field_name(formatted);
+                        if (!mangled) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+                        n = mangled;
                 }
 
-                r = json_variant_new_string(elements + j*2, mangled);
+                r = json_variant_new_string(elements + j*2, n);
                 if (r < 0)
                         goto finish;
         }
@@ -2771,3 +2795,30 @@ int table_print_with_pager(
 
         return 0;
 }
+
+int table_set_json_field_name(Table *t, size_t column, const char *name) {
+        int r;
+
+        assert(t);
+
+        if (name) {
+                size_t m;
+
+                m = MAX(column + 1, t->n_json_fields);
+                if (!GREEDY_REALLOC0(t->json_fields, m))
+                        return -ENOMEM;
+
+                r = free_and_strdup(t->json_fields + column, name);
+                if (r < 0)
+                        return r;
+
+                t->n_json_fields = m;
+                return r;
+        } else {
+                if (column >= t->n_json_fields)
+                        return 0;
+
+                t->json_fields[column] = mfree(t->json_fields[column]);
+                return 1;
+        }
+}
index 087daf348592c0ec9ba3dd444a90624c1419645d..2b189f88923a840e0a89146c3a08d1a7410e39db 100644 (file)
@@ -130,6 +130,8 @@ int table_print_json(Table *t, FILE *f, JsonFormatFlags json_flags);
 
 int table_print_with_pager(Table *t, JsonFormatFlags json_format_flags, PagerFlags pager_flags, bool show_header);
 
+int table_set_json_field_name(Table *t, size_t column, const char *name);
+
 #define table_log_add_error(r) \
         log_error_errno(r, "Failed to add cell(s) to table: %m")
 
index 44d92a719d1b6dcdf69e08232dbf14a618712e4a..ea96e223916ae0a789e96b137ed54ab417c5262a 100644 (file)
@@ -366,6 +366,41 @@ static void test_strv_wrapped(void) {
         formatted = mfree(formatted);
 }
 
+static void test_json(void) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
+        _cleanup_(table_unrefp) Table *t = NULL;
+
+        log_info("/* %s */", __func__);
+
+        assert_se(t = table_new("foo bar", "quux", "piep miau"));
+        assert_se(table_set_json_field_name(t, 2, "zzz") >= 0);
+
+        assert_se(table_add_many(t,
+                                 TABLE_STRING, "v1",
+                                 TABLE_UINT64, UINT64_C(4711),
+                                 TABLE_BOOLEAN, true) >= 0);
+
+        assert_se(table_add_many(t,
+                                 TABLE_STRV, STRV_MAKE("a", "b", "c"),
+                                 TABLE_EMPTY,
+                                 TABLE_MODE, 0755) >= 0);
+
+        assert_se(table_to_json(t, &v) >= 0);
+
+        assert_se(json_build(&w,
+                             JSON_BUILD_ARRAY(
+                                             JSON_BUILD_OBJECT(
+                                                             JSON_BUILD_PAIR("foo_bar", JSON_BUILD_STRING("v1")),
+                                                             JSON_BUILD_PAIR("quux", JSON_BUILD_UNSIGNED(4711)),
+                                                             JSON_BUILD_PAIR("zzz", JSON_BUILD_BOOLEAN(true))),
+                                             JSON_BUILD_OBJECT(
+                                                             JSON_BUILD_PAIR("foo_bar", JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))),
+                                                             JSON_BUILD_PAIR("quux", JSON_BUILD_NULL),
+                                                             JSON_BUILD_PAIR("zzz", JSON_BUILD_UNSIGNED(0755))))) >= 0);
+
+        assert_se(json_variant_equal(v, w));
+}
+
 int main(int argc, char *argv[]) {
         _cleanup_(table_unrefp) Table *t = NULL;
         _cleanup_free_ char *formatted = NULL;
@@ -509,6 +544,7 @@ int main(int argc, char *argv[]) {
         test_multiline();
         test_strv();
         test_strv_wrapped();
+        test_json();
 
         return 0;
 }