]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
json: extend JsonDispatch flags with nullable and refuse-null flags
authorLennart Poettering <lennart@poettering.net>
Wed, 8 May 2024 07:42:12 +0000 (09:42 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sat, 15 Jun 2024 09:58:02 +0000 (10:58 +0100)
currently when dispatching json objects into C structs we either insist
on the field type or we don't. Let's extend this model a bit: depending
on two new fields either allow or refuse null types in addition to the
specified type.

This is useful for example when dispatch enums as this allows us
explicitly refuse null in various scenarios where we allow multiple
types.

src/libsystemd/sd-json/sd-json.c
src/systemd/sd-json.h
src/test/test-json.c

index 13164986209f2a5019d82906ce3abc53218efa17..f7b36af880bac75bb28780de7acbddd1ea2f0561 100644 (file)
@@ -4656,8 +4656,10 @@ _public_ int sd_json_dispatch_full(
 
                         merged_flags = flags | p->flags;
 
+                        /* If an explicit type is specified, verify it matches */
                         if (p->type != _SD_JSON_VARIANT_TYPE_INVALID &&
-                            !sd_json_variant_has_type(value, p->type)) {
+                            !sd_json_variant_has_type(value, p->type) &&
+                            !(FLAGS_SET(merged_flags, SD_JSON_NULLABLE) && sd_json_variant_is_null(value))) {
 
                                 json_log(value, merged_flags, 0,
                                          "Object field '%s' has wrong type %s, expected %s.", sd_json_variant_string(key),
@@ -4672,6 +4674,22 @@ _public_ int sd_json_dispatch_full(
                                 return -EINVAL;
                         }
 
+                        /* If the SD_JSON_REFUSE_NULL flag is specified, insist the field is not "null". Note
+                         * that this provides overlapping functionality with the type check above. */
+                        if (FLAGS_SET(merged_flags, SD_JSON_REFUSE_NULL) && sd_json_variant_is_null(value)) {
+
+                                json_log(value, merged_flags, 0,
+                                         "Object field '%s' may not be null.", sd_json_variant_string(key));
+
+                                if (merged_flags & SD_JSON_PERMISSIVE)
+                                        continue;
+
+                                if (reterr_bad_field)
+                                        *reterr_bad_field = p->name;
+
+                                return -EINVAL;
+                        }
+
                         if (found[p-table]) {
                                 json_log(value, merged_flags, 0, "Duplicate object field '%s'.", sd_json_variant_string(key));
 
index 434a6ffff30f30791821af60480f9a1459af71da..d86f16106ea6adf16b56c66f23ca8dd2118b5933 100644 (file)
@@ -280,10 +280,12 @@ typedef enum sd_json_dispatch_flags_t {
         SD_JSON_STRICT           = 1 << 3, /* Use slightly stricter validation than usually (means different things for different dispatchers, for example: don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string()) */
         SD_JSON_RELAX            = 1 << 4, /* Use slightly more relaxed validation than usually (similar, for example: relaxed user name checking in json_dispatch_user_group_name()) */
         SD_JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */
+        SD_JSON_NULLABLE         = 1 << 6, /* Allow both specified type and null for this field */
+        SD_JSON_REFUSE_NULL      = 1 << 7, /* Never allow null, even if type is otherwise not specified */
 
         /* The following two may be passed into log_json() in addition to those above */
-        SD_JSON_DEBUG            = 1 << 6, /* Indicates that this log message is a debug message */
-        SD_JSON_WARNING          = 1 << 7  /* Indicates that this log message is a warning message */
+        SD_JSON_DEBUG            = 1 << 8, /* Indicates that this log message is a debug message */
+        SD_JSON_WARNING          = 1 << 9  /* Indicates that this log message is a warning message */
 } sd_json_dispatch_flags_t;
 
 typedef int (*sd_json_dispatch_callback_t)(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata);
index 4323bd5c97bcc48bead9cec9f0e12736ce67e920..a3e7fe29af517122449ad70970f04b357f2f855e 100644 (file)
@@ -1109,6 +1109,86 @@ TEST(json_iovec) {
         assert_se(iovec_memcmp(&iov1, &b) > 0);
 }
 
+TEST(json_dispatch_nullable) {
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+
+        assert_se(sd_json_build(&j, SD_JSON_BUILD_OBJECT(
+                                             SD_JSON_BUILD_PAIR("x1", JSON_BUILD_CONST_STRING("foo")),
+                                             SD_JSON_BUILD_PAIR("x2", JSON_BUILD_CONST_STRING("bar")),
+                                             SD_JSON_BUILD_PAIR("x3", JSON_BUILD_CONST_STRING("waldo")),
+                                             SD_JSON_BUILD_PAIR("x4", JSON_BUILD_CONST_STRING("foo2")),
+                                             SD_JSON_BUILD_PAIR("x5", JSON_BUILD_CONST_STRING("bar2")),
+                                             SD_JSON_BUILD_PAIR("x6", JSON_BUILD_CONST_STRING("waldo2")),
+                                             SD_JSON_BUILD_PAIR("x7", SD_JSON_BUILD_NULL),
+                                             SD_JSON_BUILD_PAIR("x8", SD_JSON_BUILD_NULL),
+                                             SD_JSON_BUILD_PAIR("x9", SD_JSON_BUILD_NULL))) >= 0);
+
+        struct data {
+                const char *x1, *x2, *x3, *x4, *x5, *x6, *x7, *x8, *x9;
+        } data = {
+                .x1 = POINTER_MAX,
+                .x2 = POINTER_MAX,
+                .x3 = POINTER_MAX,
+                .x4 = POINTER_MAX,
+                .x5 = POINTER_MAX,
+                .x6 = POINTER_MAX,
+                .x7 = POINTER_MAX,
+                .x8 = POINTER_MAX,
+                .x9 = POINTER_MAX,
+        };
+
+        assert_se(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "x1", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x1), SD_JSON_NULLABLE    },
+                                        { "x2", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x2), SD_JSON_REFUSE_NULL },
+                                        { "x3", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x3), 0                   },
+                                        { "x4", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(struct data, x4), SD_JSON_NULLABLE    },
+                                        { "x5", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(struct data, x5), SD_JSON_REFUSE_NULL },
+                                        { "x6", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(struct data, x6), 0                   },
+                                        { "x7", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x7), SD_JSON_NULLABLE    },
+                                        { "x8", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x8), 0                   },
+                                        { "x9", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(struct data, x9), SD_JSON_NULLABLE    },
+                                        {},
+                                },
+                                /* flags= */ 0,
+                                &data) >= 0);
+
+        assert_se(streq_ptr(data.x1, "foo"));
+        assert_se(streq_ptr(data.x2, "bar"));
+        assert_se(streq_ptr(data.x3, "waldo"));
+        assert_se(streq_ptr(data.x4, "foo2"));
+        assert_se(streq_ptr(data.x5, "bar2"));
+        assert_se(streq_ptr(data.x6, "waldo2"));
+        assert_se(!data.x7);
+        assert_se(!data.x8);
+        assert_se(!data.x9);
+
+        assert_se(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "x7", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, offsetof(struct data, x7), SD_JSON_REFUSE_NULL },
+                                        {},
+                                },
+                                /* flags= */ SD_JSON_ALLOW_EXTENSIONS,
+                                &data) == -EINVAL);
+
+        assert_se(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "x7", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct data, x7), SD_JSON_REFUSE_NULL },
+                                        {},
+                                },
+                                /* flags= */ SD_JSON_ALLOW_EXTENSIONS,
+                                &data) == -EINVAL);
+
+        assert_se(sd_json_dispatch(j,
+                                (const sd_json_dispatch_field[]) {
+                                        { "x7", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct data, x7), 0 },
+                                        {},
+                                },
+                                /* flags= */ SD_JSON_ALLOW_EXTENSIONS,
+                                &data) == -EINVAL);
+}
+
 TEST(parse_continue) {
         unsigned line = 23, column = 43;