[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
- [RFC PATCH v2 0/8] qapi: add generator for Golang interface, Victor Toso, 2022/06/17
- [RFC PATCH v2 2/8] qapi: golang: Generate qapi's alternate types in Go, Victor Toso, 2022/06/17
- [RFC PATCH v2 3/8] qapi: golang: Generate qapi's struct types in Go, Victor Toso, 2022/06/17
- [RFC PATCH v2 4/8] qapi: golang: Generate qapi's union types in Go,
Victor Toso <=
- [RFC PATCH v2 5/8] qapi: golang: Generate qapi's event types in Go, Victor Toso, 2022/06/17
- [RFC PATCH v2 6/8] qapi: golang: Generate qapi's command types in Go, Victor Toso, 2022/06/17
- [RFC PATCH v2 8/8] qapi: golang: document skip function visit_array_types, Victor Toso, 2022/06/17
- [RFC PATCH v2 7/8] qapi: golang: Add CommandResult type to Go, Victor Toso, 2022/06/17
- [RFC PATCH v2 1/8] qapi: golang: Generate qapi's enum types in Go, Victor Toso, 2022/06/17
- Re: [RFC PATCH v2 0/8] qapi: add generator for Golang interface, Markus Armbruster, 2022/06/27