keyval: Support lists
Additionally permit non-negative integers as key components. A dictionary's keys must either be all integers or none. If all keys are integers, convert the dictionary to a list. The set of keys must be [0,N]. Examples: * list.1=goner,list.0=null,list.1=eins,list.2=zwei is equivalent to JSON [ "null", "eins", "zwei" ] * a.b.c=1,a.b.0=2 is inconsistent: a.b.c clashes with a.b.0 * list.0=null,list.2=eins,list.2=zwei has a hole: list.1 is missing Similar design flaw as for objects: there is no way to denote an empty list. While interpreting "key absent" as empty list seems natural (removing a list member from the input string works when there are multiple ones, so why not when there's just one), it doesn't work: "key absent" already means "optional list absent", which isn't the same as "empty list present". Update the keyval object visitor to use this a.0 syntax in error messages rather than the usual a[0]. Signed-off-by: Markus Armbruster <armbru@redhat.com> Message-Id: <1488317230-26248-25-git-send-email-armbru@redhat.com> [Off-by-one fix squashed in, as per Kevin's review] Reviewed-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
parent
79f7598164
commit
0b2c1beea4
3 changed files with 298 additions and 13 deletions
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/option.h"
|
||||
|
|
@ -183,6 +184,72 @@ static void test_keyval_parse(void)
|
|||
g_assert(!qdict);
|
||||
}
|
||||
|
||||
static void check_list012(QList *qlist)
|
||||
{
|
||||
static const char *expected[] = { "null", "eins", "zwei" };
|
||||
int i;
|
||||
QString *qstr;
|
||||
|
||||
g_assert(qlist);
|
||||
for (i = 0; i < ARRAY_SIZE(expected); i++) {
|
||||
qstr = qobject_to_qstring(qlist_pop(qlist));
|
||||
g_assert(qstr);
|
||||
g_assert_cmpstr(qstring_get_str(qstr), ==, expected[i]);
|
||||
QDECREF(qstr);
|
||||
}
|
||||
g_assert(qlist_empty(qlist));
|
||||
}
|
||||
|
||||
static void test_keyval_parse_list(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
QDict *qdict, *sub_qdict;
|
||||
|
||||
/* Root can't be a list */
|
||||
qdict = keyval_parse("0=1", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* List elements need not be in order */
|
||||
qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Multiple indexes, last one wins */
|
||||
qdict = keyval_parse("list.1=goner,list.0=null,list.1=eins,list.2=zwei",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* List at deeper nesting */
|
||||
qdict = keyval_parse("a.list.1=eins,a.list.0=null,a.list.2=zwei",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
sub_qdict = qdict_get_qdict(qdict, "a");
|
||||
g_assert_cmpint(qdict_size(sub_qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(sub_qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Inconsistent dotted keys: both list and dictionary */
|
||||
qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Missing list indexes */
|
||||
qdict = keyval_parse("list.2=lonely", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_bool(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
|
|
@ -459,6 +526,59 @@ static void test_keyval_visit_dict(void)
|
|||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_list(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
char *s;
|
||||
|
||||
qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, &error_abort);
|
||||
/* TODO empty list */
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_list(v, "a", NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "");
|
||||
g_free(s);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "I");
|
||||
g_free(s);
|
||||
visit_start_list(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "II");
|
||||
g_free(s);
|
||||
visit_check_list(v, &error_abort);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_list(v, &error_abort);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
qdict = keyval_parse("a.0=,b.0.0=head", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_list(v, "a", NULL, 0, &error_abort);
|
||||
visit_check_list(v, &err); /* a[0] unexpected */
|
||||
error_free_or_abort(&err);
|
||||
visit_end_list(v, NULL);
|
||||
visit_start_list(v, "b", NULL, 0, &error_abort);
|
||||
visit_start_list(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "head");
|
||||
g_free(s);
|
||||
visit_type_str(v, NULL, &s, &err); /* b[0][1] missing */
|
||||
error_free_or_abort(&err);
|
||||
visit_end_list(v, NULL);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_optional(void)
|
||||
{
|
||||
Visitor *v;
|
||||
|
|
@ -492,10 +612,12 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/keyval/keyval_parse", test_keyval_parse);
|
||||
g_test_add_func("/keyval/keyval_parse/list", test_keyval_parse_list);
|
||||
g_test_add_func("/keyval/visit/bool", test_keyval_visit_bool);
|
||||
g_test_add_func("/keyval/visit/number", test_keyval_visit_number);
|
||||
g_test_add_func("/keyval/visit/size", test_keyval_visit_size);
|
||||
g_test_add_func("/keyval/visit/dict", test_keyval_visit_dict);
|
||||
g_test_add_func("/keyval/visit/list", test_keyval_visit_list);
|
||||
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
|
||||
g_test_run();
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue