summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2024-07-17 23:03:54 +0200
committerGitHub <noreply@github.com>2024-07-17 23:03:54 +0200
commit5d6658deecd4d14826c341866fa15988dd893688 (patch)
tree08c9efa5b0b7f70c38d49cf3eedefeb890d45bb8
parentMerge pull request #33752 from DaanDeMeyer/lsm (diff)
parenttable: Fix JSON name mangling breaking changes (diff)
downloadsystemd-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.c5
-rw-r--r--src/partition/repart.c4
-rw-r--r--src/shared/format-table.c49
-rw-r--r--src/shared/format-table.h2
-rw-r--r--src/sysupdate/sysupdate.c5
-rw-r--r--src/test/test-format-table.c56
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));