diff options
author | Lennart Poettering <lennart@poettering.net> | 2024-07-17 23:03:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-17 23:03:54 +0200 |
commit | 5d6658deecd4d14826c341866fa15988dd893688 (patch) | |
tree | 08c9efa5b0b7f70c38d49cf3eedefeb890d45bb8 | |
parent | Merge pull request #33752 from DaanDeMeyer/lsm (diff) | |
parent | table: Fix JSON name mangling breaking changes (diff) | |
download | systemd-5d6658deecd4d14826c341866fa15988dd893688.tar.xz systemd-5d6658deecd4d14826c341866fa15988dd893688.zip |
Merge pull request #33609 from AdrianVovk/table-json
Improve table JSON name mangling
-rw-r--r-- | src/import/importctl.c | 5 | ||||
-rw-r--r-- | src/partition/repart.c | 4 | ||||
-rw-r--r-- | src/shared/format-table.c | 49 | ||||
-rw-r--r-- | src/shared/format-table.h | 2 | ||||
-rw-r--r-- | src/sysupdate/sysupdate.c | 5 | ||||
-rw-r--r-- | src/test/test-format-table.c | 56 |
6 files changed, 109 insertions, 12 deletions
diff --git a/src/import/importctl.c b/src/import/importctl.c index 3334f37b94..30566a1917 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -912,6 +912,11 @@ static int list_images(int argc, char *argv[], void *userdata) { (void) table_hide_column_from_display(t, 8); (void) table_hide_column_from_display(t, 10); + /* Starting in v257, these fields would be automatically formatted with underscores. However, this + * command was introduced in v256, so changing the field name would be a breaking change. */ + (void) table_set_json_field_name(t, 8, "usage-exclusive"); + (void) table_set_json_field_name(t, 10, "limit-exclusive"); + for (;;) { uint64_t crtime, mtime, usage, usage_exclusive, limit, limit_exclusive; const char *class, *name, *type, *path; diff --git a/src/partition/repart.c b/src/partition/repart.c index 0509364063..0ad8fad150 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3043,6 +3043,10 @@ static int context_dump_partitions(Context *context) { if (!t) return log_oom(); + /* Starting in v257, these fields would be automatically formatted with underscores. This would have + * been a breaking change, so to avoid that let's hard-code their original names. */ + table_set_json_field_name(t, 15, "drop-in_files"); + if (!DEBUG_LOGGING) { if (arg_json_format_flags & SD_JSON_FORMAT_OFF) (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 14eeb4647f..ee641d1144 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -1184,6 +1184,20 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { goto check; } + case TABLE_SET_JSON_FIELD_NAME: { + const char *n = va_arg(ap, const char*); + size_t idx; + if (t->vertical) { + assert(TABLE_CELL_TO_INDEX(last_cell) >= t->n_columns); + idx = TABLE_CELL_TO_INDEX(last_cell) / t->n_columns - 1; + } else { + idx = TABLE_CELL_TO_INDEX(last_cell); + assert(idx < t->n_columns); + } + r = table_set_json_field_name(t, idx, n); + goto check; + } + case _TABLE_DATA_TYPE_MAX: /* Used as end marker */ va_end(ap); @@ -2881,18 +2895,39 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) { } } -static char* string_to_json_field_name(const char *f) { +char* table_mangle_to_json_field_name(const char *str) { /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a - * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to - * underscores and leave everything as is. */ + * field name can be hence this is a bit vague and black magic. Here's what we do: + * - Convert spaces to underscores + * - Convert dashes to underscores (some JSON parsers don't like dealing with dashes) + * - Convert most other symbols to underscores (for similar reasons) + * - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase) + */ + + bool new_word = true; + char *c; + + assert(str); - char *c = strdup(f); + c = strdup(str); if (!c) return NULL; - for (char *x = c; *x; x++) - if (isspace(*x)) + for (char *x = c; *x; x++) { + if (!strchr(ALPHANUMERICAL, *x)) { *x = '_'; + new_word = true; + continue; + } + + if (new_word) { + if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case + * then we assume the whole word is all-caps + * and avoid lowercasing it. */ + *x = ascii_tolower(*x); + new_word = false; + } + } return c; } @@ -2913,7 +2948,7 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) { return -ENOMEM; } - mangled = string_to_json_field_name(n); + mangled = table_mangle_to_json_field_name(n); if (!mangled) return -ENOMEM; diff --git a/src/shared/format-table.h b/src/shared/format-table.h index dcd339727f..bb2eb70759 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -74,6 +74,7 @@ typedef enum TableDataType { TABLE_SET_BOTH_UNDERLINES, TABLE_SET_URL, TABLE_SET_UPPERCASE, + TABLE_SET_JSON_FIELD_NAME, _TABLE_DATA_TYPE_INVALID = -EINVAL, } TableDataType; @@ -165,6 +166,7 @@ int table_print_json(Table *t, FILE *f, sd_json_format_flags_t json_flags); int table_print_with_pager(Table *t, sd_json_format_flags_t json_format_flags, PagerFlags pager_flags, bool show_header); +char* table_mangle_to_json_field_name(const char *str); int table_set_json_field_name(Table *t, size_t idx, const char *name); #define table_log_add_error(r) \ diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 3fcb2839d0..aded7dde0b 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -470,6 +470,11 @@ static int context_show_version(Context *c, const char *version) { (void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + /* Starting in v257, these fields would be automatically formatted with underscores. This would have + * been a breaking change, so to avoid that let's hard-code their original names. */ + (void) table_set_json_field_name(t, 7, "tries-done"); + (void) table_set_json_field_name(t, 8, "tries-left"); + /* Determine if the target will make use of partition/fs attributes for any of the transfers */ FOREACH_ARRAY(transfer, c->transfers, c->n_transfers) { Transfer *tr = *transfer; diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 85cd31ecee..41621dbb5b 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -364,18 +364,27 @@ TEST(json) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL; _cleanup_(table_unrefp) Table *t = NULL; - assert_se(t = table_new("foo bar", "quux", "piep miau")); + assert_se(t = table_new_raw(4)); + + assert_se(table_add_many(t, + TABLE_HEADER, "foo bar", + TABLE_HEADER, "quux", + TABLE_HEADER, "piep miau", + TABLE_HEADER, "asdf", + TABLE_SET_JSON_FIELD_NAME, "asdf-custom") >= 0); 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); + TABLE_BOOLEAN, true, + TABLE_EMPTY) >= 0); assert_se(table_add_many(t, TABLE_STRV, STRV_MAKE("a", "b", "c"), TABLE_EMPTY, - TABLE_MODE, 0755) >= 0); + TABLE_MODE, 0755, + TABLE_EMPTY) >= 0); assert_se(table_to_json(t, &v) >= 0); @@ -384,15 +393,49 @@ TEST(json) { SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", JSON_BUILD_CONST_STRING("v1")), SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_UNSIGNED(4711)), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_BOOLEAN(true))), + SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL)), SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("foo_bar", SD_JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))), SD_JSON_BUILD_PAIR("quux", SD_JSON_BUILD_NULL), - SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_UNSIGNED(0755))))) >= 0); + SD_JSON_BUILD_PAIR("zzz", SD_JSON_BUILD_UNSIGNED(0755)), + SD_JSON_BUILD_PAIR("asdf-custom", SD_JSON_BUILD_NULL)))) >= 0); assert_se(sd_json_variant_equal(v, w)); } +TEST(json_mangling) { + static const struct { + const char *arg; + const char *exp; + } cases[] = { + /* Not Mangled */ + { "foo", "foo" }, + { "foo_bar", "foo_bar" }, + { "fooBar", "fooBar" }, + { "fooBar123", "fooBar123" }, + { "foo_bar123", "foo_bar123" }, + { ALPHANUMERICAL, ALPHANUMERICAL }, + { "_123", "_123" }, + + /* Mangled */ + { "Foo Bar", "foo_bar" }, + { "Foo-Bar", "foo_bar" }, + { "Foo@Bar", "foo_bar" }, + { "Foo (Bar)", "foo__bar_"}, + { "MixedCase ALLCAPS", "mixedCase_ALLCAPS" }, + { "_X", "_x" }, + { "_Foo", "_foo" }, + }; + + FOREACH_ELEMENT(i, cases) { + _cleanup_free_ char *ret = NULL; + assert_se(ret = table_mangle_to_json_field_name(i->arg)); + printf("\"%s\" -> \"%s\"\n", i->arg, ret); + assert_se(streq(ret, i->exp)); + } +} + TEST(table) { _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *formatted = NULL; @@ -544,6 +587,7 @@ TEST(vertical) { assert_se(table_add_many(t, TABLE_FIELD, "pfft aa", TABLE_STRING, "foo", TABLE_FIELD, "uuu o", TABLE_SIZE, UINT64_C(1024), + TABLE_FIELD, "quux", TABLE_STRING, "asdf", TABLE_SET_JSON_FIELD_NAME, "custom-quux", TABLE_FIELD, "lllllllllllo", TABLE_STRING, "jjjjjjjjjjjjjjjjj") >= 0); assert_se(table_set_json_field_name(t, 1, "dimpfelmoser") >= 0); @@ -553,6 +597,7 @@ TEST(vertical) { assert_se(streq(formatted, " pfft aa: foo\n" " uuu o: 1K\n" + " quux: asdf\n" "lllllllllllo: jjjjjjjjjjjjjjjjj\n")); _cleanup_(sd_json_variant_unrefp) sd_json_variant *a = NULL, *b = NULL; @@ -561,6 +606,7 @@ TEST(vertical) { assert_se(sd_json_build(&b, SD_JSON_BUILD_OBJECT( SD_JSON_BUILD_PAIR("pfft_aa", SD_JSON_BUILD_STRING("foo")), SD_JSON_BUILD_PAIR("dimpfelmoser", SD_JSON_BUILD_UNSIGNED(1024)), + SD_JSON_BUILD_PAIR("custom-quux", SD_JSON_BUILD_STRING("asdf")), SD_JSON_BUILD_PAIR("lllllllllllo", SD_JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj")))) >= 0); assert_se(sd_json_variant_equal(a, b)); |