qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC PATCH v2 4/8] qapi: golang: Generate qapi's union types in Go


From: Victor Toso
Subject: [RFC PATCH v2 4/8] qapi: golang: Generate qapi's union types in Go
Date: Fri, 17 Jun 2022 14:19:28 +0200

This patch handles QAPI union types and generates the equivalent data
structures and methods in Go to handle it.

At the moment of this writing, it generates 38 structures.

The QAPI union type has two types of fields: The @base and the
@variants members. The @base fields can be considered common members
for the union while only one field maximum is set for the @variants.

In the QAPI specification, it defines a @discriminator field, which is
an Enum type. The purpose of the  @discriminator is to identify which
@variant type is being used. The @discriminator is not used in the
generated union Go structs as it suffices to check which one of the
@variants fields were set.

The union types implement the Marshaler and Unmarshaler interfaces to
seamless decode from JSON objects to Golang structs and vice versa.

qapi:
  | { 'union': 'SetPasswordOptions',
  |   'base': { 'protocol': 'DisplayProtocol',
  |             'password': 'str',
  |             '*connected': 'SetPasswordAction' },
  |   'discriminator': 'protocol',
  |   'data': { 'vnc': 'SetPasswordOptionsVnc' } }

go:
  | type SetPasswordOptions struct {
  |     // Base fields
  |     Password  string             `json:"password"`
  |     Connected *SetPasswordAction `json:"connected,omitempty"`
  |
  |     // Variants fields
  |     Vnc *SetPasswordOptionsVnc `json:"-"`
  | }

Signed-off-by: Victor Toso <victortoso@redhat.com>
---
 scripts/qapi/golang.py | 112 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 105 insertions(+), 7 deletions(-)

diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index 1ab0c0bb46..6c6a5cea97 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -53,7 +53,8 @@ class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
 
     def __init__(self, prefix: str):
         super().__init__()
-        self.target = {name: "" for name in ["alternate", "enum", "helper", 
"struct"]}
+        self.target = {name: "" for name in ["alternate", "enum", "helper", 
"struct",
+                                             "union"]}
         self.objects_seen = {}
         self.schema = None
         self.golang_package_name = "qapi"
@@ -79,10 +80,14 @@ def visit_object_type(self: QAPISchemaGenGolangVisitor,
                           members: List[QAPISchemaObjectTypeMember],
                           variants: Optional[QAPISchemaVariants]
                           ) -> None:
-        # Do not handle anything besides structs
+        # Do not handle anything besides struct and unions.
         if (name == self.schema.the_empty_object_type.name or
                 not isinstance(name, str) or
-                info.defn_meta not in ["struct"]):
+                info.defn_meta not in ["struct", "union"]):
+            return
+
+        # Base structs are embed
+        if qapi_name_is_base(name):
             return
 
         # Safety checks.
@@ -110,6 +115,10 @@ def visit_object_type(self: QAPISchemaGenGolangVisitor,
                                                              base,
                                                              members,
                                                              variants)
+        if info.defn_meta == "union":
+            self.target[info.defn_meta] += qapi_to_golang_methods_union(name,
+                                                                        info,
+                                                                        
variants)
 
     def visit_alternate_type(self: QAPISchemaGenGolangVisitor,
                              name: str,
@@ -311,14 +320,99 @@ def qapi_to_golang_struct(name: str,
             # Variant's are handled in the Marshal/Unmarshal methods
             fieldtag = '`json:"-"`'
             fields += f"\t{field} *{member_type}{fieldtag}\n"
-            member_type = qapi_schema_type_to_go_type(var.type.name)
-            # Variant's are handled in the Marshal/Unmarshal methods
-            fieldtag = '`json:"-"`'
-            fields += f"\t{field} *{member_type}{fieldtag}\n"
 
     return generate_struct_type(type_name, fields)
 
 
+def qapi_to_golang_methods_union(name: str,
+                                 info: Optional[QAPISourceInfo],
+                                 variants: Optional[QAPISchemaVariants]
+                                 ) -> str:
+
+    type_name = qapi_to_go_type_name(name, info.defn_meta)
+
+    driverCases = ""
+    checkFields = ""
+    if variants:
+        for var in variants.variants:
+            if var.type.is_implicit():
+                continue
+
+            field = qapi_to_field_name(var.name)
+            member_type = qapi_schema_type_to_go_type(var.type.name)
+
+            if len(checkFields) > 0:
+                checkFields += "\t} else "
+            checkFields += f'''if s.{field} != nil {{
+        driver = "{var.name}"
+        payload, err = json.Marshal(s.{field})
+'''
+            # for Unmarshal method
+            driverCases += f'''
+    case "{var.name}":
+        s.{field} = new({member_type})
+        if err := json.Unmarshal(data, s.{field}); err != nil {{
+            s.{field} = nil
+            return err
+        }}'''
+
+        checkFields += "}"
+
+    return f'''
+func (s {type_name}) MarshalJSON() ([]byte, error) {{
+    type Alias {type_name}
+    alias := Alias(s)
+    base, err := json.Marshal(alias)
+    if err != nil {{
+        return nil, err
+    }}
+
+    driver := ""
+    payload := []byte{{}}
+    {checkFields}
+
+    if err != nil {{
+        return nil, err
+    }}
+
+    if len(base) == len("{{}}") {{
+        base = []byte("")
+    }} else {{
+        base = append([]byte{{','}}, base[1:len(base)-1]...)
+    }}
+
+    if len(payload) == 0 || len(payload) == len("{{}}") {{
+        payload = []byte("")
+    }} else {{
+        payload = append([]byte{{','}}, payload[1:len(payload)-1]...)
+    }}
+
+    result := fmt.Sprintf(`{{"{variants.tag_member.name}":"%s"%s%s}}`, driver, 
base, payload)
+    return []byte(result), nil
+}}
+
+func (s *{type_name}) UnmarshalJSON(data []byte) error {{
+    type Alias {type_name}
+    peek := struct {{
+        Alias
+        Driver string `json:"{variants.tag_member.name}"`
+    }}{{}}
+
+
+    if err := json.Unmarshal(data, &peek); err != nil {{
+        return err
+    }}
+    *s = {type_name}(peek.Alias)
+
+    switch peek.Driver {{
+    {driverCases}
+    }}
+    // Unrecognizer drivers are silently ignored.
+    return nil
+}}
+'''
+
+
 def qapi_schema_type_to_go_type(type: str) -> str:
     schema_types_to_go = {
             'str': 'string', 'null': 'nil', 'bool': 'bool', 'number':
@@ -345,6 +439,10 @@ def qapi_to_field_name(name: str) -> str:
     return name.title().replace("_", "").replace("-", "")
 
 
+def qapi_name_is_base(name: str) -> bool:
+    return name.startswith("q_obj_") and name.endswith("-base")
+
+
 def qapi_to_go_type_name(name: str, meta: str) -> str:
     if name.startswith("q_obj_"):
         name = name[6:]
-- 
2.36.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]