qapi: Flat unions with arbitrary discriminator
Instead of the rather verbose syntax that distinguishes base and
subclass fields...
  { "type": "file",
    "read-only": true,
    "data": {
        "filename": "test"
    } }
...we can now have both in the same namespace, allowing a more direct
mapping of the command line, and moving fields between the common base
and subclasses without breaking the API:
  { "driver": "file",
    "read-only": true,
    "filename": "test" }
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
			
			
This commit is contained in:
		
							parent
							
								
									761d524dbc
								
							
						
					
					
						commit
						50f2bdc75c
					
				| 
						 | 
					@ -103,6 +103,28 @@ And it looks like this on the wire:
 | 
				
			||||||
   "data" : { "backing-file": "/some/place/my-image",
 | 
					   "data" : { "backing-file": "/some/place/my-image",
 | 
				
			||||||
              "lazy-refcounts": true } }
 | 
					              "lazy-refcounts": true } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Flat union types avoid the nesting on the wire. They are used whenever a
 | 
				
			||||||
 | 
					specific field of the base type is declared as the discriminator ('type' is
 | 
				
			||||||
 | 
					then no longer generated). The discriminator must always be a string field.
 | 
				
			||||||
 | 
					The above example can then be modified as follows:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 { 'type': 'BlockdevCommonOptions',
 | 
				
			||||||
 | 
					   'data': { 'driver': 'str', 'readonly': 'bool' } }
 | 
				
			||||||
 | 
					 { 'union': 'BlockdevOptions',
 | 
				
			||||||
 | 
					   'base': 'BlockdevCommonOptions',
 | 
				
			||||||
 | 
					   'discriminator': 'driver',
 | 
				
			||||||
 | 
					   'data': { 'raw': 'RawOptions',
 | 
				
			||||||
 | 
					             'qcow2': 'Qcow2Options' } }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Resulting in this JSON object:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 { "driver": "qcow2",
 | 
				
			||||||
 | 
					   "readonly": false,
 | 
				
			||||||
 | 
					   "backing-file": "/some/place/my-image",
 | 
				
			||||||
 | 
					   "lazy-refcounts": true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=== Commands ===
 | 
					=== Commands ===
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Commands are defined by using a list containing three members.  The first
 | 
					Commands are defined by using a list containing three members.  The first
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -154,7 +154,9 @@ def generate_union(expr):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = expr['union']
 | 
					    name = expr['union']
 | 
				
			||||||
    typeinfo = expr['data']
 | 
					    typeinfo = expr['data']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base = expr.get('base')
 | 
					    base = expr.get('base')
 | 
				
			||||||
 | 
					    discriminator = expr.get('discriminator')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ret = mcgen('''
 | 
					    ret = mcgen('''
 | 
				
			||||||
struct %(name)s
 | 
					struct %(name)s
 | 
				
			||||||
| 
						 | 
					@ -177,8 +179,13 @@ struct %(name)s
 | 
				
			||||||
''')
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if base:
 | 
					    if base:
 | 
				
			||||||
        struct = find_struct(base)
 | 
					        base_fields = find_struct(base)['data']
 | 
				
			||||||
        ret += generate_struct_fields(struct['data'])
 | 
					        if discriminator:
 | 
				
			||||||
 | 
					            base_fields = base_fields.copy()
 | 
				
			||||||
 | 
					            del base_fields[discriminator]
 | 
				
			||||||
 | 
					        ret += generate_struct_fields(base_fields)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        assert not discriminator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ret += mcgen('''
 | 
					    ret += mcgen('''
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,30 @@ import os
 | 
				
			||||||
import getopt
 | 
					import getopt
 | 
				
			||||||
import errno
 | 
					import errno
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_visit_struct_fields(field_prefix, members):
 | 
					def generate_visit_struct_fields(name, field_prefix, fn_prefix, members):
 | 
				
			||||||
 | 
					    substructs = []
 | 
				
			||||||
    ret = ''
 | 
					    ret = ''
 | 
				
			||||||
 | 
					    full_name = name if not fn_prefix else "%s_%s" % (name, fn_prefix)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for argname, argentry, optional, structured in parse_args(members):
 | 
				
			||||||
 | 
					        if structured:
 | 
				
			||||||
 | 
					            if not fn_prefix:
 | 
				
			||||||
 | 
					                nested_fn_prefix = argname
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                nested_fn_prefix = "%s_%s" % (fn_prefix, argname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            nested_field_prefix = "%s%s." % (field_prefix, argname)
 | 
				
			||||||
 | 
					            ret += generate_visit_struct_fields(name, nested_field_prefix,
 | 
				
			||||||
 | 
					                                                nested_fn_prefix, argentry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret += mcgen('''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void visit_type_%(full_name)s_fields(Visitor *m, %(name)s ** obj, Error **errp)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Error *err = NULL;
 | 
				
			||||||
 | 
					''',
 | 
				
			||||||
 | 
					        name=name, full_name=full_name)
 | 
				
			||||||
 | 
					    push_indent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for argname, argentry, optional, structured in parse_args(members):
 | 
					    for argname, argentry, optional, structured in parse_args(members):
 | 
				
			||||||
        if optional:
 | 
					        if optional:
 | 
				
			||||||
| 
						 | 
					@ -31,7 +53,7 @@ if (obj && (*obj)->%(prefix)shas_%(c_name)s) {
 | 
				
			||||||
            push_indent()
 | 
					            push_indent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if structured:
 | 
					        if structured:
 | 
				
			||||||
            ret += generate_visit_struct_body(field_prefix + argname, argname, argentry)
 | 
					            ret += generate_visit_struct_body(full_name, argname, argentry)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            ret += mcgen('''
 | 
					            ret += mcgen('''
 | 
				
			||||||
visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
 | 
					visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
 | 
				
			||||||
| 
						 | 
					@ -47,6 +69,12 @@ visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s",
 | 
				
			||||||
visit_end_optional(m, &err);
 | 
					visit_end_optional(m, &err);
 | 
				
			||||||
''')
 | 
					''')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pop_indent()
 | 
				
			||||||
 | 
					    ret += mcgen('''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    error_propagate(errp, err);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					''')
 | 
				
			||||||
    return ret
 | 
					    return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,8 +84,9 @@ if (!error_is_set(errp)) {
 | 
				
			||||||
''')
 | 
					''')
 | 
				
			||||||
    push_indent()
 | 
					    push_indent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    full_name = name if not field_prefix else "%s_%s" % (field_prefix, name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if len(field_prefix):
 | 
					    if len(field_prefix):
 | 
				
			||||||
        field_prefix = field_prefix + "."
 | 
					 | 
				
			||||||
        ret += mcgen('''
 | 
					        ret += mcgen('''
 | 
				
			||||||
Error **errp = &err; /* from outer scope */
 | 
					Error **errp = &err; /* from outer scope */
 | 
				
			||||||
Error *err = NULL;
 | 
					Error *err = NULL;
 | 
				
			||||||
| 
						 | 
					@ -74,19 +103,13 @@ visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
 | 
				
			||||||
    ret += mcgen('''
 | 
					    ret += mcgen('''
 | 
				
			||||||
if (!err) {
 | 
					if (!err) {
 | 
				
			||||||
    if (!obj || *obj) {
 | 
					    if (!obj || *obj) {
 | 
				
			||||||
''')
 | 
					        visit_type_%(name)s_fields(m, obj, &err);
 | 
				
			||||||
    push_indent()
 | 
					        error_propagate(errp, err);
 | 
				
			||||||
    push_indent()
 | 
					        err = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					''',
 | 
				
			||||||
 | 
					        name=full_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ret += generate_visit_struct_fields(field_prefix, members)
 | 
					 | 
				
			||||||
    pop_indent()
 | 
					 | 
				
			||||||
    ret += mcgen('''
 | 
					 | 
				
			||||||
    error_propagate(errp, err);
 | 
					 | 
				
			||||||
    err = NULL;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
''')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pop_indent()
 | 
					 | 
				
			||||||
    pop_indent()
 | 
					    pop_indent()
 | 
				
			||||||
    ret += mcgen('''
 | 
					    ret += mcgen('''
 | 
				
			||||||
        /* Always call end_struct if start_struct succeeded.  */
 | 
					        /* Always call end_struct if start_struct succeeded.  */
 | 
				
			||||||
| 
						 | 
					@ -98,7 +121,9 @@ if (!err) {
 | 
				
			||||||
    return ret
 | 
					    return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_visit_struct(name, members):
 | 
					def generate_visit_struct(name, members):
 | 
				
			||||||
    ret = mcgen('''
 | 
					    ret = generate_visit_struct_fields(name, "", "", members)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ret += mcgen('''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 | 
					void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -157,9 +182,17 @@ def generate_visit_union(expr):
 | 
				
			||||||
    members = expr['data']
 | 
					    members = expr['data']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base = expr.get('base')
 | 
					    base = expr.get('base')
 | 
				
			||||||
 | 
					    discriminator = expr.get('discriminator')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ret = generate_visit_enum('%sKind' % name, members.keys())
 | 
					    ret = generate_visit_enum('%sKind' % name, members.keys())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if base:
 | 
				
			||||||
 | 
					        base_fields = find_struct(base)['data']
 | 
				
			||||||
 | 
					        if discriminator:
 | 
				
			||||||
 | 
					            base_fields = base_fields.copy()
 | 
				
			||||||
 | 
					            del base_fields[discriminator]
 | 
				
			||||||
 | 
					        ret += generate_visit_struct_fields(name, "", "", base_fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ret += mcgen('''
 | 
					    ret += mcgen('''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 | 
					void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 | 
				
			||||||
| 
						 | 
					@ -179,23 +212,34 @@ void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **
 | 
				
			||||||
    push_indent()
 | 
					    push_indent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if base:
 | 
					    if base:
 | 
				
			||||||
        struct = find_struct(base)
 | 
					        ret += mcgen('''
 | 
				
			||||||
        push_indent()
 | 
					    visit_type_%(name)s_fields(m, obj, &err);
 | 
				
			||||||
        ret += generate_visit_struct_fields("", struct['data'])
 | 
					''',
 | 
				
			||||||
        pop_indent()
 | 
					            name=name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pop_indent()
 | 
					    pop_indent()
 | 
				
			||||||
    ret += mcgen('''
 | 
					    ret += mcgen('''
 | 
				
			||||||
        visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
 | 
					        visit_type_%(name)sKind(m, &(*obj)->kind, "%(type)s", &err);
 | 
				
			||||||
        if (!err) {
 | 
					        if (!err) {
 | 
				
			||||||
            switch ((*obj)->kind) {
 | 
					            switch ((*obj)->kind) {
 | 
				
			||||||
''',
 | 
					''',
 | 
				
			||||||
                 name=name)
 | 
					                 name=name, type="type" if not discriminator else discriminator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for key in members:
 | 
					    for key in members:
 | 
				
			||||||
 | 
					        if not discriminator:
 | 
				
			||||||
 | 
					            fmt = 'visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            fmt = '''visit_start_implicit_struct(m, (void**) &(*obj)->%(c_name)s, sizeof(%(c_type)s), &err);
 | 
				
			||||||
 | 
					                if (!err) {
 | 
				
			||||||
 | 
					                    visit_type_%(c_type)s_fields(m, &(*obj)->%(c_name)s, &err);
 | 
				
			||||||
 | 
					                    error_propagate(errp, err);
 | 
				
			||||||
 | 
					                    err = NULL;
 | 
				
			||||||
 | 
					                    visit_end_implicit_struct(m, &err);
 | 
				
			||||||
 | 
					                }'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret += mcgen('''
 | 
					        ret += mcgen('''
 | 
				
			||||||
            case %(abbrev)s_KIND_%(enum)s:
 | 
					            case %(abbrev)s_KIND_%(enum)s:
 | 
				
			||||||
                visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
 | 
					                ''' + fmt + '''
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
''',
 | 
					''',
 | 
				
			||||||
                abbrev = de_camel_case(name).upper(),
 | 
					                abbrev = de_camel_case(name).upper(),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue