Currently the QObjectInputVisitor assumes that all scalar values are directly represented as their final types. ie it assumes an 'int' is using QInt, and a 'bool' is using QBool.
This adds an alternative constructor for QObjectInputVisitor that will set it up such that it expects a QString for all scalar types instead. This makes it possible to use QObjectInputVisitor with a QDict produced from QemuOpts, where everything is in string format. Reviewed-by: Kevin Wolf <kw...@redhat.com> Reviewed-by: Marc-André Lureau <marcandre.lur...@redhat.com> Signed-off-by: Daniel P. Berrange <berra...@redhat.com> --- include/qapi/qobject-input-visitor.h | 32 +++++- qapi/qobject-input-visitor.c | 128 ++++++++++++++++++++++ tests/test-qobject-input-visitor.c | 203 ++++++++++++++++++++++++++++++++++- 3 files changed, 355 insertions(+), 8 deletions(-) diff --git a/include/qapi/qobject-input-visitor.h b/include/qapi/qobject-input-visitor.h index cde328d..a17a453 100644 --- a/include/qapi/qobject-input-visitor.h +++ b/include/qapi/qobject-input-visitor.h @@ -19,12 +19,36 @@ typedef struct QObjectInputVisitor QObjectInputVisitor; -/* - * Return a new input visitor that converts a QObject to a QAPI object. +/** + * Create a new input visitor that converts @obj to a QAPI object. + * + * Any scalar values in the @obj input data structure should be in the + * required type already. i.e. if visiting a bool, the value should + * already be a QBool instance. * - * Set @strict to reject a parse that doesn't consume all keys of a - * dictionary; otherwise excess input is ignored. + * If @strict is set to true, then an error will be reported if any + * dict keys are not consumed during visitation. If @strict is false + * then extra dict keys are silently ignored. + * + * The returned input visitor should be released by calling + * visit_free() when no longer required. */ Visitor *qobject_input_visitor_new(QObject *obj, bool strict); +/** + * Create a new input visitor that converts a QObject to a QAPI object. + * + * Any scalar values in the @obj input data structure should always be + * represented as strings. i.e. if visiting a boolean, the value should + * be a QString whose contents represent a valid boolean. + * + * The visitor always operates in strict mode, requiring all dict keys + * to be consumed during visitation. An error will be reported if this + * does not happen. + * + * The returned input visitor should be released by calling + * visit_free() when no longer required. + */ +Visitor *qobject_string_input_visitor_new(QObject *obj); + #endif diff --git a/qapi/qobject-input-visitor.c b/qapi/qobject-input-visitor.c index 5ff3db3..07ce525 100644 --- a/qapi/qobject-input-visitor.c +++ b/qapi/qobject-input-visitor.c @@ -20,6 +20,7 @@ #include "qemu-common.h" #include "qapi/qmp/types.h" #include "qapi/qmp/qerror.h" +#include "qemu/cutils.h" #define QIV_STACK_SIZE 1024 @@ -263,6 +264,29 @@ static void qobject_input_type_int64(Visitor *v, const char *name, int64_t *obj, *obj = qint_get_int(qint); } +static void qobject_input_type_int64_str(Visitor *v, const char *name, + int64_t *obj, Error **errp) +{ + QObjectInputVisitor *qiv = to_qiv(v); + QString *qstr = qobject_to_qstring(qobject_input_get_object(qiv, name, + true)); + uint64_t ret; + Error *local_err = NULL; + + if (!qstr || !qstr->string) { + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + parse_option_number(name, qstr->string, &ret, &local_err); + if (local_err) { + error_propagate(errp, local_err); + } else { + *obj = ret; + } +} + static void qobject_input_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp) { @@ -279,6 +303,22 @@ static void qobject_input_type_uint64(Visitor *v, const char *name, *obj = qint_get_int(qint); } +static void qobject_input_type_uint64_str(Visitor *v, const char *name, + uint64_t *obj, Error **errp) +{ + QObjectInputVisitor *qiv = to_qiv(v); + QString *qstr = qobject_to_qstring(qobject_input_get_object(qiv, name, + true)); + + if (!qstr || !qstr->string) { + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + parse_option_number(name, qstr->string, obj, errp); +} + static void qobject_input_type_bool(Visitor *v, const char *name, bool *obj, Error **errp) { @@ -294,6 +334,22 @@ static void qobject_input_type_bool(Visitor *v, const char *name, bool *obj, *obj = qbool_get_bool(qbool); } +static void qobject_input_type_bool_str(Visitor *v, const char *name, bool *obj, + Error **errp) +{ + QObjectInputVisitor *qiv = to_qiv(v); + QString *qstr = qobject_to_qstring(qobject_input_get_object(qiv, name, + true)); + + if (!qstr || !qstr->string) { + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + parse_option_bool(name, qstr->string, obj, errp); +} + static void qobject_input_type_str(Visitor *v, const char *name, char **obj, Error **errp) { @@ -335,6 +391,30 @@ static void qobject_input_type_number(Visitor *v, const char *name, double *obj, "number"); } +static void qobject_input_type_number_str(Visitor *v, const char *name, + double *obj, Error **errp) +{ + QObjectInputVisitor *qiv = to_qiv(v); + QString *qstr = qobject_to_qstring(qobject_input_get_object(qiv, name, + true)); + char *endp; + + if (!qstr || !qstr->string) { + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + errno = 0; + *obj = strtod(qstr->string, &endp); + if (errno == 0 && endp != qstr->string && *endp == '\0') { + return; + } + + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "number"); +} + static void qobject_input_type_any(Visitor *v, const char *name, QObject **obj, Error **errp) { @@ -356,6 +436,22 @@ static void qobject_input_type_null(Visitor *v, const char *name, Error **errp) } } +static void qobject_input_type_size_str(Visitor *v, const char *name, + uint64_t *obj, Error **errp) +{ + QObjectInputVisitor *qiv = to_qiv(v); + QString *qstr = qobject_to_qstring(qobject_input_get_object(qiv, name, + true)); + + if (!qstr || !qstr->string) { + error_setg(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + parse_option_size(name, qstr->string, obj, errp); +} + static void qobject_input_optional(Visitor *v, const char *name, bool *present) { QObjectInputVisitor *qiv = to_qiv(v); @@ -413,3 +509,35 @@ Visitor *qobject_input_visitor_new(QObject *obj, bool strict) return &v->visitor; } + +Visitor *qobject_string_input_visitor_new(QObject *obj) +{ + QObjectInputVisitor *v; + + v = g_malloc0(sizeof(*v)); + + v->visitor.type = VISITOR_INPUT; + v->visitor.start_struct = qobject_input_start_struct; + v->visitor.check_struct = qobject_input_check_struct; + v->visitor.end_struct = qobject_input_pop; + v->visitor.start_list = qobject_input_start_list; + v->visitor.next_list = qobject_input_next_list; + v->visitor.end_list = qobject_input_pop; + v->visitor.start_alternate = qobject_input_start_alternate; + v->visitor.type_int64 = qobject_input_type_int64_str; + v->visitor.type_uint64 = qobject_input_type_uint64_str; + v->visitor.type_bool = qobject_input_type_bool_str; + v->visitor.type_str = qobject_input_type_str; + v->visitor.type_number = qobject_input_type_number_str; + v->visitor.type_any = qobject_input_type_any; + v->visitor.type_null = qobject_input_type_null; + v->visitor.type_size = qobject_input_type_size_str; + v->visitor.optional = qobject_input_optional; + v->visitor.free = qobject_input_free; + v->strict = true; + + v->root = obj; + qobject_incref(obj); + + return &v->visitor; +} diff --git a/tests/test-qobject-input-visitor.c b/tests/test-qobject-input-visitor.c index 0e65e63..5d0c2bd 100644 --- a/tests/test-qobject-input-visitor.c +++ b/tests/test-qobject-input-visitor.c @@ -41,6 +41,7 @@ static void visitor_input_teardown(TestInputVisitorData *data, function so that the JSON string used by the tests are kept in the test functions (and not in main()). */ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data, + bool strict, bool autocast, const char *json_string, va_list *ap) { @@ -49,11 +50,31 @@ static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data, data->obj = qobject_from_jsonv(json_string, ap); g_assert(data->obj); - data->qiv = qobject_input_visitor_new(data->obj, false); + if (autocast) { + assert(strict); + data->qiv = qobject_string_input_visitor_new(data->obj); + } else { + data->qiv = qobject_input_visitor_new(data->obj, strict); + } g_assert(data->qiv); return data->qiv; } +static GCC_FMT_ATTR(4, 5) +Visitor *visitor_input_test_init_full(TestInputVisitorData *data, + bool strict, bool autocast, + const char *json_string, ...) +{ + Visitor *v; + va_list ap; + + va_start(ap, json_string); + v = visitor_input_test_init_internal(data, strict, autocast, + json_string, &ap); + va_end(ap); + return v; +} + static GCC_FMT_ATTR(2, 3) Visitor *visitor_input_test_init(TestInputVisitorData *data, const char *json_string, ...) @@ -62,7 +83,8 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data, va_list ap; va_start(ap, json_string); - v = visitor_input_test_init_internal(data, json_string, &ap); + v = visitor_input_test_init_internal(data, true, false, + json_string, &ap); va_end(ap); return v; } @@ -77,7 +99,8 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data, static Visitor *visitor_input_test_init_raw(TestInputVisitorData *data, const char *json_string) { - return visitor_input_test_init_internal(data, json_string, NULL); + return visitor_input_test_init_internal(data, true, false, + json_string, NULL); } static void test_visitor_in_int(TestInputVisitorData *data, @@ -109,6 +132,45 @@ static void test_visitor_in_int_overflow(TestInputVisitorData *data, error_free_or_abort(&err); } +static void test_visitor_in_int_autocast(TestInputVisitorData *data, + const void *unused) +{ + int64_t res = 0, value = -42; + Error *err = NULL; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, + "%" PRId64, value); + visit_type_int(v, NULL, &res, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_int_str_autocast(TestInputVisitorData *data, + const void *unused) +{ + int64_t res = 0, value = -42; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, + "\"-42\""); + + visit_type_int(v, NULL, &res, &error_abort); + g_assert_cmpint(res, ==, value); +} + +static void test_visitor_in_int_str_noautocast(TestInputVisitorData *data, + const void *unused) +{ + int64_t res = 0; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"-42\""); + + visit_type_int(v, NULL, &res, &err); + error_free_or_abort(&err); +} + static void test_visitor_in_bool(TestInputVisitorData *data, const void *unused) { @@ -121,6 +183,44 @@ static void test_visitor_in_bool(TestInputVisitorData *data, g_assert_cmpint(res, ==, true); } +static void test_visitor_in_bool_autocast(TestInputVisitorData *data, + const void *unused) +{ + bool res = false; + Error *err = NULL; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, "true"); + + visit_type_bool(v, NULL, &res, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_bool_str_autocast(TestInputVisitorData *data, + const void *unused) +{ + bool res = false; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, "\"yes\""); + + visit_type_bool(v, NULL, &res, &error_abort); + g_assert_cmpint(res, ==, true); +} + +static void test_visitor_in_bool_str_noautocast(TestInputVisitorData *data, + const void *unused) +{ + bool res = false; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"true\""); + + visit_type_bool(v, NULL, &res, &err); + error_free_or_abort(&err); +} + static void test_visitor_in_number(TestInputVisitorData *data, const void *unused) { @@ -133,6 +233,69 @@ static void test_visitor_in_number(TestInputVisitorData *data, g_assert_cmpfloat(res, ==, value); } +static void test_visitor_in_number_autocast(TestInputVisitorData *data, + const void *unused) +{ + double res = 0, value = 3.14; + Error *err = NULL; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, "%f", value); + + visit_type_number(v, NULL, &res, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_number_str_autocast(TestInputVisitorData *data, + const void *unused) +{ + double res = 0, value = 3.14; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, "\"3.14\""); + + visit_type_number(v, NULL, &res, &error_abort); + g_assert_cmpfloat(res, ==, value); +} + +static void test_visitor_in_number_str_noautocast(TestInputVisitorData *data, + const void *unused) +{ + double res = 0; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"3.14\""); + + visit_type_number(v, NULL, &res, &err); + error_free_or_abort(&err); +} + +static void test_visitor_in_size_str_autocast(TestInputVisitorData *data, + const void *unused) +{ + uint64_t res, value = 500 * 1024 * 1024; + Visitor *v; + + v = visitor_input_test_init_full(data, true, true, "\"500M\""); + + visit_type_size(v, NULL, &res, &error_abort); + g_assert_cmpfloat(res, ==, value); +} + +static void test_visitor_in_size_str_noautocast(TestInputVisitorData *data, + const void *unused) +{ + uint64_t res = 0; + Visitor *v; + Error *err = NULL; + + v = visitor_input_test_init(data, "\"500M\""); + + visit_type_size(v, NULL, &res, &err); + error_free_or_abort(&err); +} + static void test_visitor_in_string(TestInputVisitorData *data, const void *unused) { @@ -289,7 +452,8 @@ static void test_visitor_in_null(TestInputVisitorData *data, * when input is not null. */ - v = visitor_input_test_init(data, "{ 'a': null, 'b': '' }"); + v = visitor_input_test_init_full(data, false, false, + "{ 'a': null, 'b': '' }"); visit_start_struct(v, NULL, NULL, 0, &error_abort); visit_type_null(v, "a", &error_abort); visit_type_str(v, "a", &tmp, &err); @@ -841,10 +1005,41 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_int); input_visitor_test_add("/visitor/input/int_overflow", &in_visitor_data, test_visitor_in_int_overflow); + input_visitor_test_add("/visitor/input/int_autocast", + &in_visitor_data, test_visitor_in_int_autocast); + input_visitor_test_add("/visitor/input/int_str_autocast", + &in_visitor_data, test_visitor_in_int_str_autocast); + input_visitor_test_add("/visitor/input/int_str_noautocast", + &in_visitor_data, + test_visitor_in_int_str_noautocast); input_visitor_test_add("/visitor/input/bool", &in_visitor_data, test_visitor_in_bool); + input_visitor_test_add("/visitor/input/bool_autocast", + &in_visitor_data, + test_visitor_in_bool_autocast); + input_visitor_test_add("/visitor/input/bool_str_autocast", + &in_visitor_data, + test_visitor_in_bool_str_autocast); + input_visitor_test_add("/visitor/input/bool_str_noautocast", + &in_visitor_data, + test_visitor_in_bool_str_noautocast); input_visitor_test_add("/visitor/input/number", &in_visitor_data, test_visitor_in_number); + input_visitor_test_add("/visitor/input/number_autocast", + &in_visitor_data, + test_visitor_in_number_autocast); + input_visitor_test_add("/visitor/input/number_str_autocast", + &in_visitor_data, + test_visitor_in_number_str_autocast); + input_visitor_test_add("/visitor/input/number_str_noautocast", + &in_visitor_data, + test_visitor_in_number_str_noautocast); + input_visitor_test_add("/visitor/input/size_str_autocast", + &in_visitor_data, + test_visitor_in_size_str_autocast); + input_visitor_test_add("/visitor/input/size_str_noautocast", + &in_visitor_data, + test_visitor_in_size_str_noautocast); input_visitor_test_add("/visitor/input/string", &in_visitor_data, test_visitor_in_string); input_visitor_test_add("/visitor/input/enum", -- 2.7.4