src/Makefile
src/plugins/common/Makefile
src/plugins/common/muxing/Makefile
+ src/plugins/common/param-validation/Makefile
src/plugins/ctf/common/bfcr/Makefile
src/plugins/ctf/common/Makefile
src/plugins/ctf/common/metadata/Makefile
tests/lib/Makefile
tests/lib/test-plugin-plugins/Makefile
tests/Makefile
+ tests/param-validation/Makefile
tests/plugins/Makefile
tests/plugins/src.ctf.fs/Makefile
tests/plugins/src.ctf.fs/succeed/Makefile
#include <babeltrace2/babeltrace.h>
+#include <glib.h>
+
#include "common/macros.h"
BT_HIDDEN
-SUBDIRS = muxing
+SUBDIRS = muxing param-validation
--- /dev/null
+noinst_LTLIBRARIES = libbabeltrace2-param-validation.la
+
+libbabeltrace2_param_validation_la_SOURCES = \
+ param-validation.c \
+ param-validation.h
--- /dev/null
+/*
+ * Copyright 2019 EfficiOS Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "param-validation.h"
+
+#include <babeltrace2/babeltrace.h>
+#include <glib.h>
+#include <inttypes.h>
+
+#include "common/common.h"
+
+struct bt_param_validation_context {
+ gchar *error;
+ GArray *scope_stack;
+};
+
+struct validate_ctx_stack_element {
+ enum {
+ VALIDATE_CTX_STACK_ELEMENT_MAP,
+ VALIDATE_CTX_STACK_ELEMENT_ARRAY,
+ } type;
+
+ union {
+ const char *map_key_name;
+ uint64_t array_index;
+ };
+};
+
+static
+void validate_ctx_push_map_scope(
+ struct bt_param_validation_context *ctx,
+ const char *key)
+{
+ struct validate_ctx_stack_element stack_element = {
+ .type = VALIDATE_CTX_STACK_ELEMENT_MAP,
+ .map_key_name = key,
+ };
+
+ g_array_append_val(ctx->scope_stack, stack_element);
+}
+
+static
+void validate_ctx_push_array_scope(
+ struct bt_param_validation_context *ctx, uint64_t index)
+{
+ struct validate_ctx_stack_element stack_element = {
+ .type = VALIDATE_CTX_STACK_ELEMENT_ARRAY,
+ .array_index = index,
+ };
+
+ g_array_append_val(ctx->scope_stack, stack_element);
+}
+
+static
+void validate_ctx_pop_scope(struct bt_param_validation_context *ctx)
+{
+ BT_ASSERT(ctx->scope_stack->len > 0);
+
+ g_array_remove_index_fast(ctx->scope_stack, ctx->scope_stack->len - 1);
+}
+
+static
+void append_scope_to_string(GString *str,
+ const struct validate_ctx_stack_element *elem,
+ bool first)
+{
+ switch (elem->type) {
+ case VALIDATE_CTX_STACK_ELEMENT_MAP:
+ if (!first) {
+ g_string_append_c(str, '.');
+ }
+
+ g_string_append(str, elem->map_key_name);
+ break;
+ case VALIDATE_CTX_STACK_ELEMENT_ARRAY:
+ g_string_append_printf(str, "[%" PRIu64 "]", elem->array_index);
+ break;
+ default:
+ abort();
+ }
+}
+
+enum bt_param_validation_status bt_param_validation_error(
+ struct bt_param_validation_context *ctx,
+ const char *format, ...) {
+ va_list ap;
+ enum bt_param_validation_status status;
+
+ GString *str = g_string_new(NULL);
+ if (!str) {
+ status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
+ goto end;
+ }
+
+ if (ctx->scope_stack->len > 0) {
+ guint i;
+
+ g_string_assign(str, "Error validating parameter `");
+
+ append_scope_to_string(str, &g_array_index(ctx->scope_stack,
+ struct validate_ctx_stack_element, 0), true);
+
+ for (i = 1; i < ctx->scope_stack->len; i++) {
+ append_scope_to_string(str,
+ &g_array_index(ctx->scope_stack,
+ struct validate_ctx_stack_element, i), false);
+ }
+
+ g_string_append(str, "`: ");
+ } else {
+ g_string_assign(str, "Error validating parameters: ");
+ }
+
+ va_start(ap, format);
+ g_string_append_vprintf(str, format, ap);
+ va_end(ap);
+
+ ctx->error = g_string_free(str, FALSE);
+ status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
+
+end:
+ return status;
+}
+
+struct validate_map_value_data
+{
+ GPtrArray *available_keys;
+ enum bt_param_validation_status status;
+ struct bt_param_validation_context *ctx;
+};
+
+static
+enum bt_param_validation_status validate_value(
+ const bt_value *value,
+ const struct bt_param_validation_value_descr *descr,
+ struct bt_param_validation_context *ctx);
+
+static
+bt_bool validate_map_value_entry(const char *key,
+ const bt_value *value, void *v_data)
+{
+ struct validate_map_value_data *data = v_data;
+ const struct bt_param_validation_map_value_entry_descr *candidate;
+ guint i;
+
+ /* Check if this key is in the available keys. */
+ for (i = 0; i < data->available_keys->len; i++) {
+ candidate = g_ptr_array_index(data->available_keys, i);
+
+ if (g_str_equal(key, candidate->key)) {
+ break;
+ }
+ }
+
+ if (i < data->available_keys->len) {
+ /* Key was found in available keys. */
+ g_ptr_array_remove_index_fast(data->available_keys, i);
+
+ /* Push key name as the scope. */
+ validate_ctx_push_map_scope(data->ctx, key);
+
+ /* Validate the value of the entry. */
+ data->status = validate_value(value, &candidate->value_descr,
+ data->ctx);
+
+ validate_ctx_pop_scope(data->ctx);
+ } else {
+ data->status = bt_param_validation_error(data->ctx,
+ "unexpected key `%s`.", key);
+ }
+
+ /* Continue iterating if everything is good so far. */
+ return data->status == BT_PARAM_VALIDATION_STATUS_OK;
+}
+
+static
+enum bt_param_validation_status validate_map_value(
+ const struct bt_param_validation_map_value_descr *descr,
+ const bt_value *map,
+ struct bt_param_validation_context *ctx) {
+ enum bt_param_validation_status status;
+ struct validate_map_value_data data;
+ bt_value_map_foreach_entry_const_status foreach_entry_status;
+ GPtrArray *available_keys = NULL;
+ const struct bt_param_validation_map_value_entry_descr *descr_iter;
+ guint i;
+
+ BT_ASSERT(bt_value_get_type(map) == BT_VALUE_TYPE_MAP);
+
+ available_keys = g_ptr_array_new();
+ if (!available_keys) {
+ status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
+ goto end;
+ }
+
+ for (descr_iter = descr->entries; descr_iter->key; descr_iter++) {
+ g_ptr_array_add(available_keys, (gpointer) descr_iter);
+ }
+
+ /* Initialize `status` to OK, in case the map is empty. */
+ data.status = BT_PARAM_VALIDATION_STATUS_OK;
+ data.available_keys = available_keys;
+ data.ctx = ctx;
+
+ foreach_entry_status = bt_value_map_foreach_entry_const(map,
+ validate_map_value_entry, &data);
+ if (foreach_entry_status == BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_MEMORY_ERROR) {
+ status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
+ goto end;
+ }
+
+ if (data.status != BT_PARAM_VALIDATION_STATUS_OK) {
+ status = data.status;
+ goto end;
+ }
+
+ for (i = 0; i < data.available_keys->len; i++) {
+ const struct bt_param_validation_map_value_entry_descr *entry =
+ g_ptr_array_index(data.available_keys, i);
+
+ if (!entry->is_optional) {
+ status = bt_param_validation_error(ctx,
+ "missing mandatory entry `%s`",
+ entry->key);
+ goto end;
+ }
+ }
+
+ status = BT_PARAM_VALIDATION_STATUS_OK;
+
+end:
+ g_ptr_array_free(available_keys, TRUE);
+ return status;
+}
+
+static
+enum bt_param_validation_status validate_array_value(
+ const struct bt_param_validation_array_value_descr *descr,
+ const bt_value *array,
+ struct bt_param_validation_context *ctx) {
+ enum bt_param_validation_status status;
+ uint64_t i;
+
+ BT_ASSERT(bt_value_get_type(array) == BT_VALUE_TYPE_ARRAY);
+
+ if (bt_value_array_get_length(array) < descr->min_length) {
+ status = bt_param_validation_error(ctx,
+ "array is smaller than the minimum length: "
+ "array-length=%" PRIu64 ", min-length=%" PRIu64,
+ bt_value_array_get_length(array),
+ descr->min_length);
+ goto end;
+ }
+
+ if (bt_value_array_get_length(array) > descr->max_length) {
+ status = bt_param_validation_error(ctx,
+ "array is larger than the maximum length: "
+ "array-length=%" PRIu64 ", max-length=%" PRIu64,
+ bt_value_array_get_length(array),
+ descr->max_length);
+ goto end;
+ }
+
+ for (i = 0; i < bt_value_array_get_length(array); i++) {
+ const bt_value *element =
+ bt_value_array_borrow_element_by_index_const(array, i);
+
+ validate_ctx_push_array_scope(ctx, i);
+
+ status = validate_value(element, descr->element_type, ctx);
+
+ validate_ctx_pop_scope(ctx);
+
+ if (status != BT_PARAM_VALIDATION_STATUS_OK) {
+ goto end;
+ }
+ }
+
+ status = BT_PARAM_VALIDATION_STATUS_OK;
+
+end:
+ return status;
+}
+
+static
+enum bt_param_validation_status validate_string_value(
+ const struct bt_param_validation_string_value_descr *descr,
+ const bt_value *string,
+ struct bt_param_validation_context *ctx) {
+ enum bt_param_validation_status status;
+ const char *s = bt_value_string_get(string);
+ gchar *joined_choices = NULL;
+
+ BT_ASSERT(bt_value_get_type(string) == BT_VALUE_TYPE_STRING);
+
+ if (descr->choices) {
+ const char **choice;
+
+ for (choice = descr->choices; *choice; choice++) {
+ if (strcmp(s, *choice) == 0) {
+ break;
+ }
+ }
+
+ if (!*choice) {
+ /*
+ * g_strjoinv takes a gchar **, but it doesn't modify
+ * the array of the strings (yet).
+ */
+ joined_choices = g_strjoinv(", ", (gchar **) descr->choices);
+ if (!joined_choices) {
+ status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
+ goto end;
+ }
+
+ status = bt_param_validation_error(ctx,
+ "string is not amongst the available choices: "
+ "string=%s, choices=[%s]", s, joined_choices);
+ goto end;
+ }
+ }
+
+ status = BT_PARAM_VALIDATION_STATUS_OK;
+end:
+ g_free(joined_choices);
+
+ return status;
+}
+
+static
+enum bt_param_validation_status validate_value(
+ const bt_value *value,
+ const struct bt_param_validation_value_descr *descr,
+ struct bt_param_validation_context *ctx) {
+ enum bt_param_validation_status status;
+
+ /* If there is a custom validation func, we call it and ignore the rest. */
+ if (descr->validation_func) {
+ status = descr->validation_func(value, ctx);
+
+ if (status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR) {
+ BT_ASSERT(ctx->error);
+ }
+
+ goto end;
+ }
+
+ if (bt_value_get_type(value) != descr->type) {
+ bt_param_validation_error(ctx,
+ "unexpected type: expected-type=%s, actual-type=%s",
+ bt_common_value_type_string(descr->type),
+ bt_common_value_type_string(bt_value_get_type(value)));
+ status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
+ goto end;
+ }
+
+ switch (bt_value_get_type(value)) {
+ case BT_VALUE_TYPE_MAP:
+ status = validate_map_value(&descr->map, value, ctx);
+ break;
+ case BT_VALUE_TYPE_ARRAY:
+ status = validate_array_value(&descr->array, value, ctx);
+ break;
+ case BT_VALUE_TYPE_STRING:
+ status = validate_string_value(&descr->string, value, ctx);
+ break;
+ default:
+ status = BT_PARAM_VALIDATION_STATUS_OK;
+ break;
+ }
+
+end:
+ return status;
+}
+
+enum bt_param_validation_status bt_param_validation_validate(
+ const bt_value *params,
+ const struct bt_param_validation_map_value_entry_descr *entries,
+ gchar **error) {
+ struct bt_param_validation_context ctx;
+ struct bt_param_validation_map_value_descr map_value_descr;
+ enum bt_param_validation_status status;
+
+ ctx.error = NULL;
+ ctx.scope_stack = g_array_new(FALSE, FALSE,
+ sizeof(struct validate_ctx_stack_element));
+ g_ptr_array_new_with_free_func(g_free);
+ if (!ctx.scope_stack) {
+ status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
+ goto end;
+ }
+
+ map_value_descr.entries = entries;
+
+ status = validate_map_value(&map_value_descr, params, &ctx);
+
+end:
+ *error = ctx.error;
+ ctx.error = NULL;
+
+ return status;
+}
--- /dev/null
+#ifndef BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H
+#define BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H
+
+/*
+ * Copyright 2019 EfficiOS Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <babeltrace2/babeltrace.h>
+#include <glib.h>
+
+#include <stdio.h> /* For __MINGW_PRINTF_FORMAT. */
+
+#ifdef __MINGW_PRINTF_FORMAT
+# define BT_PRINTF_FORMAT __MINGW_PRINTF_FORMAT
+#else
+# define BT_PRINTF_FORMAT printf
+#endif
+
+struct bt_param_validation_context;
+struct bt_param_validation_value_descr;
+
+#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END { NULL, 0, {} }
+
+struct bt_param_validation_map_value_descr {
+ const struct bt_param_validation_map_value_entry_descr *entries;
+};
+
+#define BT_PARAM_VALIDATION_INFINITE UINT64_MAX
+
+struct bt_param_validation_array_value_descr {
+ uint64_t min_length;
+ uint64_t max_length; /* Use BT_PARAM_VALIDATION_INFINITE if there's no max. */
+ const struct bt_param_validation_value_descr *element_type;
+};
+
+struct bt_param_validation_string_value_descr {
+ /* NULL-terminated array of choices. Unused if NULL. */
+ const char **choices;
+};
+
+enum bt_param_validation_status {
+ BT_PARAM_VALIDATION_STATUS_OK = 0,
+ BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR = -1,
+ BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR = -2,
+};
+
+typedef enum bt_param_validation_status
+ (bt_param_validation_func)(const bt_value *value,
+ struct bt_param_validation_context *);
+
+struct bt_param_validation_value_descr {
+ bt_value_type type;
+
+ /* Additional checks dependent on the type. */
+ union {
+ struct bt_param_validation_array_value_descr array;
+ struct bt_param_validation_map_value_descr map;
+ struct bt_param_validation_string_value_descr string;
+ };
+
+ /*
+ * If set, call this function, which is responsible of validating the
+ * value. The other fields are ignored.
+ *
+ * If validation fails, this function must call
+ * `bt_param_validation_error` with the provided context
+ * to set the error string.
+ */
+ bt_param_validation_func *validation_func;
+};
+
+#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL true
+#define BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY false
+
+struct bt_param_validation_map_value_entry_descr {
+ const char *key;
+ bool is_optional;
+
+ const struct bt_param_validation_value_descr value_descr;
+};
+
+enum bt_param_validation_status bt_param_validation_validate(
+ const bt_value *params,
+ const struct bt_param_validation_map_value_entry_descr *entries,
+ gchar **error);
+
+__attribute__((format(BT_PRINTF_FORMAT, 2, 3)))
+enum bt_param_validation_status bt_param_validation_error(
+ struct bt_param_validation_context *ctx,
+ const char *format, ...);
+
+#endif /* BABELTRACE_PLUGINS_COMMON_PARAM_VALIDATION_PARAM_VALIDATION_H */
-SUBDIRS = utils lib bitfield ctf-writer plugins argpar
+SUBDIRS = \
+ utils \
+ lib \
+ bitfield \
+ ctf-writer \
+ plugins \
+ argpar \
+ param-validation
# Directories added to EXTRA_DIST will be recursively copied to the distribution.
EXTRA_DIST = $(srcdir)/data \
--- /dev/null
+AM_CPPFLAGS += -I$(top_srcdir)/tests/utils
+
+noinst_PROGRAMS = test_param_validation
+test_param_validation_SOURCES = test_param_validation.c
+test_param_validation_LDADD = \
+ $(top_builddir)/src/param-parse/libbabeltrace2-param-parse.la \
+ $(top_builddir)/src/plugins/common/param-validation/libbabeltrace2-param-validation.la \
+ $(top_builddir)/src/lib/libbabeltrace2.la \
+ $(top_builddir)/tests/utils/tap/libtap.la
--- /dev/null
+/*
+ * Copyright (c) EfficiOS Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "tap/tap.h"
+#include "param-parse/param-parse.h"
+#include "plugins/common/param-validation/param-validation.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static
+enum bt_param_validation_status run_test(
+ const char *params_str,
+ const struct bt_param_validation_map_value_entry_descr *entries,
+ const char *test_name,
+ const char *expected_error)
+{
+ GString *err = g_string_new(NULL);
+ const bt_value *params;
+ enum bt_param_validation_status status;
+ gchar *validate_error = NULL;
+
+ if (!err) {
+ fprintf(stderr, "Failed to allocated a GString.\n");
+ abort();
+ }
+
+ params = bt_param_parse(params_str, err);
+
+ if (!params) {
+ fprintf(stderr, "Could not parse params: `%s`, %s\n",
+ params_str, err->str);
+ abort();
+ }
+
+ status = bt_param_validation_validate(params, entries, &validate_error);
+
+ if (expected_error) {
+ const char *fmt;
+
+ /* We expect a failure. */
+ ok(status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR,
+ "%s: validation fails", test_name);
+ ok(validate_error, "%s: error string is not NULL", test_name);
+
+ fmt = "%s: error string contains expected string";
+ if (validate_error && strstr(validate_error, expected_error)) {
+ pass(fmt, test_name);
+ } else {
+ fail(fmt, test_name);
+ diag("could not find `%s` in `%s`", expected_error, validate_error);
+ }
+
+ g_free(validate_error);
+ } else {
+ /* We expect a success. */
+ ok(status == BT_PARAM_VALIDATION_STATUS_OK, "%s: validation succeeds", test_name);
+ ok(!validate_error, "%s: error string is NULL", test_name);
+ }
+
+ bt_value_put_ref(params);
+ g_string_free(err, TRUE);
+
+ return status;
+}
+
+static
+void test_map_valid(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ { "fenouil", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL, { .type = BT_VALUE_TYPE_STRING } },
+ { "panais", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_OPTIONAL, { .type = BT_VALUE_TYPE_BOOL } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=2,fenouil=\"miam\"", entries, "valid map", NULL);
+}
+
+static
+void test_map_missing_key(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ { "tomate", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=2", entries, "missing key in map",
+ "Error validating parameters: missing mandatory entry `tomate`");
+}
+
+static
+void test_map_unexpected_key(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("tomate=2", entries, "unexpected key in map", "unexpected key `tomate`");
+}
+
+static
+void test_map_invalid_entry_value_type(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carottes", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carottes=\"orange\"", entries, "map entry with unexpected type",
+ "Error validating parameter `carottes`: unexpected type: expected-type=SIGNED_INTEGER, actual-type=STRING");
+}
+
+static
+void test_nested_error(void)
+{
+ const struct bt_param_validation_map_value_entry_descr poireau_entries[] = {
+ { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_SIGNED_INTEGER } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END,
+ };
+
+ const struct bt_param_validation_map_value_entry_descr carottes_elem_entries[] = {
+ { "poireau", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_MAP, .map = {
+ .entries = poireau_entries,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END,
+ };
+
+ const struct bt_param_validation_value_descr carottes_elem = {
+ .type = BT_VALUE_TYPE_MAP,
+ .map = {
+ .entries = carottes_elem_entries,
+ }
+ };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carottes", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 0,
+ .max_length = BT_PARAM_VALIDATION_INFINITE,
+ .element_type = &carottes_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carottes=[{poireau={navet=7}}, {poireau={}}]", entries, "error nested in maps and arrays",
+ "Error validating parameter `carottes[1].poireau`: missing mandatory entry `navet`");
+}
+
+static
+void test_array_valid(void)
+{
+ const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 2,
+ .max_length = 22,
+ .element_type = &carotte_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=[true, false, true]", entries, "valid array", NULL);
+}
+
+static
+void test_array_empty_valid(void)
+{
+ const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 0,
+ .max_length = 2,
+ .element_type = &carotte_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=[]", entries, "valid empty array", NULL);
+}
+
+static
+void test_array_invalid_too_small(void)
+{
+ const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 1,
+ .max_length = 100,
+ .element_type = &carotte_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=[]", entries, "array too small",
+ "Error validating parameter `carotte`: array is smaller than the minimum length: array-length=0, min-length=1");
+}
+
+static
+void test_array_invalid_too_large(void)
+{
+ const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 2,
+ .max_length = 2,
+ .element_type = &carotte_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=[true, false, false]", entries, "array too large",
+ "Error validating parameter `carotte`: array is larger than the maximum length: array-length=3, max-length=2");
+}
+
+static
+void test_array_invalid_elem_type(void)
+{
+ const struct bt_param_validation_value_descr carotte_elem = { .type = BT_VALUE_TYPE_BOOL, {} };
+
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "carotte", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_ARRAY, .array = {
+ .min_length = 3,
+ .max_length = 3,
+ .element_type = &carotte_elem,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("carotte=[true, false, 2]", entries, "array with invalid element type",
+ "Error validating parameter `carotte[2]`: unexpected type: expected-type=BOOL, actual-type=SIGNED_INTEGER");
+}
+
+static
+void test_string_valid_without_choices(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { .type = BT_VALUE_TYPE_STRING, { } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("haricot=\"vert\"", entries, "valid string without choices", NULL);
+}
+
+static
+void test_string_valid_with_choices(void)
+{
+ const char *haricot_choices[] = {"vert", "jaune", "rouge", NULL};
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_STRING, .string = {
+ .choices = haricot_choices,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("haricot=\"jaune\"", entries, "valid string with choices", NULL);
+}
+
+static
+void test_string_invalid_choice(void)
+{
+ const char *haricot_choices[] = {"vert", "jaune", "rouge", NULL};
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "haricot", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, { BT_VALUE_TYPE_STRING, .string = {
+ .choices = haricot_choices,
+ } } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("haricot=\"violet\"", entries, "string with invalid choice",
+ "Error validating parameter `haricot`: string is not amongst the available choices: string=violet, choices=[vert, jaune, rouge]");
+}
+
+static
+enum bt_param_validation_status custom_validation_func_valid(
+ const bt_value *value,
+ struct bt_param_validation_context *context)
+{
+ ok(bt_value_get_type(value) == BT_VALUE_TYPE_UNSIGNED_INTEGER,
+ "type of value passed to custom function is as expected");
+ ok(bt_value_integer_unsigned_get(value) == 1234,
+ "value passed to custom function is as expected");
+ return BT_PARAM_VALIDATION_STATUS_OK;
+}
+
+static
+void test_custom_validation_func_valid(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, {
+ .validation_func = custom_validation_func_valid,
+ } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("navet=+1234", entries, "custom validation function with valid value", NULL);
+}
+
+static
+enum bt_param_validation_status custom_validation_func_invalid(
+ const bt_value *value,
+ struct bt_param_validation_context *context)
+{
+ return bt_param_validation_error(context, "wrooooong");
+}
+
+static
+void test_custom_validation_func_invalid(void)
+{
+ const struct bt_param_validation_map_value_entry_descr entries[] = {
+ { "navet", BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_MANDATORY, {
+ .validation_func = custom_validation_func_invalid,
+ } },
+ BT_PARAM_VALIDATION_MAP_VALUE_ENTRY_END
+ };
+
+ run_test("navet=+1234", entries, "custom validation function with invalid value",
+ "Error validating parameter `navet`: wrooooong");
+}
+
+int main(void)
+{
+ plan_tests(34);
+
+ test_map_valid();
+
+ test_map_missing_key();
+ test_map_unexpected_key();
+ test_map_invalid_entry_value_type();
+
+ test_array_valid();
+ test_array_empty_valid();
+
+ test_array_invalid_too_small();
+ test_array_invalid_too_large();
+ test_array_invalid_elem_type();
+
+ test_string_valid_without_choices();
+ test_string_valid_with_choices();
+
+ test_string_invalid_choice();
+
+ test_custom_validation_func_valid();
+ test_custom_validation_func_invalid();
+
+ test_nested_error();
+
+ return exit_status();
+}