diff mbox series

[v1,6/9] qapi: golang: Generate qapi's event types in Go

Message ID 20230927112544.85011-7-victortoso@redhat.com
State New
Headers show
Series qapi-go: add generator for Golang interface | expand

Commit Message

Victor Toso Sept. 27, 2023, 11:25 a.m. UTC
This patch handles QAPI event types and generates data structures in
Go that handles it.

We also define a Event interface and two helper functions MarshalEvent
and UnmarshalEvent.

Example:
qapi:
 | { 'event': 'MEMORY_DEVICE_SIZE_CHANGE',
 |   'data': { '*id': 'str', 'size': 'size', 'qom-path' : 'str'} }

go:
 | type MemoryDeviceSizeChangeEvent struct {
 |         MessageTimestamp Timestamp `json:"-"`
 |         Id               *string   `json:"id,omitempty"`
 |         Size             uint64    `json:"size"`
 |         QomPath          string    `json:"qom-path"`
 | }

usage:
 | input := `{"event":"MEMORY_DEVICE_SIZE_CHANGE",` +
 | `"timestamp":{"seconds":1588168529,"microseconds":201316},` +
 | `"data":{"id":"vm0","size":1073741824,"qom-path":"/machine/unattached/device[2]"}}`
 | e, err := UnmarshalEvent([]byte(input)
 | if err != nil {
 |     panic(err)
 | }
 | if e.GetName() == `MEMORY_DEVICE_SIZE_CHANGE` {
 |     m := e.(*MemoryDeviceSizeChangeEvent)
 |     // m.QomPath == "/machine/unattached/device[2]"
 | }

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

Patch

diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index 343c9c9b95..ff3b1dd020 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -197,6 +197,55 @@ 
 }}
 '''
 
+TEMPLATE_EVENT = '''
+type Timestamp struct {{
+    Seconds      int64 `json:"seconds"`
+    Microseconds int64 `json:"microseconds"`
+}}
+
+type Event interface {{
+    GetName()      string
+    GetTimestamp() Timestamp
+}}
+
+func MarshalEvent(e Event) ([]byte, error) {{
+    m := make(map[string]any)
+    m["event"] = e.GetName()
+    m["timestamp"] = e.GetTimestamp()
+    if bytes, err := json.Marshal(e); err != nil {{
+        return []byte{{}}, err
+    }} else if len(bytes) > 2 {{
+        m["data"] = e
+    }}
+    return json.Marshal(m)
+}}
+
+func UnmarshalEvent(data []byte) (Event, error) {{
+    base := struct {{
+        Name             string    `json:"event"`
+        MessageTimestamp Timestamp `json:"timestamp"`
+    }}{{}}
+    if err := json.Unmarshal(data, &base); err != nil {{
+        return nil, fmt.Errorf("Failed to decode event: %s", string(data))
+    }}
+
+    switch base.Name {{
+    {cases}
+    }}
+    return nil, errors.New("Failed to recognize event")
+}}
+'''
+
+TEMPLATE_EVENT_METHODS = '''
+func (s *{type_name}) GetName() string {{
+    return "{name}"
+}}
+
+func (s *{type_name}) GetTimestamp() Timestamp {{
+    return s.MessageTimestamp
+}}
+'''
+
 
 def gen_golang(schema: QAPISchema,
                output_dir: str,
@@ -217,7 +266,8 @@  def qapi_to_field_name(name: str) -> str:
 def qapi_to_field_name_enum(name: str) -> str:
     return name.title().replace("-", "")
 
-def qapi_to_go_type_name(name: str) -> str:
+def qapi_to_go_type_name(name: str,
+                         meta: Optional[str] = None) -> str:
     if qapi_name_is_object(name):
         name = name[6:]
 
@@ -232,6 +282,11 @@  def qapi_to_go_type_name(name: str) -> str:
 
     name += ''.join(word.title() for word in words[1:])
 
+    types = ["event"]
+    if meta in types:
+        name = name[:-3] if name.endswith("Arg") else name
+        name += meta.title().replace(" ", "")
+
     return name
 
 def qapi_schema_type_to_go_type(qapitype: str) -> str:
@@ -455,7 +510,7 @@  def recursive_base(self: QAPISchemaGenGolangVisitor,
 # Helper function that is used for most of QAPI types
 def qapi_to_golang_struct(self: QAPISchemaGenGolangVisitor,
                           name: str,
-                          _: Optional[QAPISourceInfo],
+                          info: Optional[QAPISourceInfo],
                           __: QAPISchemaIfCond,
                           ___: List[QAPISchemaFeature],
                           base: Optional[QAPISchemaObjectType],
@@ -464,6 +519,8 @@  def qapi_to_golang_struct(self: QAPISchemaGenGolangVisitor,
 
 
     fields, with_nullable = recursive_base(self, base)
+    if info.defn_meta == "event":
+        fields += f'''\tMessageTimestamp Timestamp `json:"-"`\n{fields}'''
 
     if members:
         for member in members:
@@ -485,7 +542,7 @@  def qapi_to_golang_struct(self: QAPISchemaGenGolangVisitor,
             fields += field
             with_nullable = True if nullable else with_nullable
 
-    type_name = qapi_to_go_type_name(name)
+    type_name = qapi_to_go_type_name(name, info.defn_meta)
     content = generate_struct_type(type_name, fields)
     if with_nullable:
         content += struct_with_nullable_generate_marshal(self,
@@ -644,15 +701,34 @@  def generate_template_alternate(self: QAPISchemaGenGolangVisitor,
                                                  unmarshal_check_fields=unmarshal_check_fields[1:])
     return content
 
+def generate_template_event(events: dict[str, str]) -> str:
+    cases = ""
+    for name in sorted(events):
+        case_type = events[name]
+        cases += f'''
+case "{name}":
+    event := struct {{
+        Data {case_type} `json:"data"`
+    }}{{}}
+
+    if err := json.Unmarshal(data, &event); err != nil {{
+        return nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
+    }}
+    event.Data.MessageTimestamp = base.MessageTimestamp
+    return &event.Data, nil
+'''
+    return TEMPLATE_EVENT.format(cases=cases)
+
 
 class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
 
     def __init__(self, _: str):
         super().__init__()
-        types = ["alternate", "enum", "helper", "struct", "union"]
+        types = ["alternate", "enum", "event", "helper", "struct", "union"]
         self.target = {name: "" for name in types}
         self.objects_seen = {}
         self.schema = None
+        self.events = {}
         self.golang_package_name = "qapi"
         self.accept_null_types = []
 
@@ -679,6 +755,7 @@  def visit_begin(self, schema):
 
     def visit_end(self):
         self.schema = None
+        self.target["event"] += generate_template_event(self.events)
 
     def visit_object_type(self: QAPISchemaGenGolangVisitor,
                           name: str,
@@ -779,7 +856,25 @@  def visit_command(self,
         pass
 
     def visit_event(self, name, info, ifcond, features, arg_type, boxed):
-        pass
+        assert name == info.defn_name
+        type_name = qapi_to_go_type_name(name, info.defn_meta)
+        self.events[name] = type_name
+
+        if isinstance(arg_type, QAPISchemaObjectType):
+            content = qapi_to_golang_struct(self,
+                                            name,
+                                            arg_type.info,
+                                            arg_type.ifcond,
+                                            arg_type.features,
+                                            arg_type.base,
+                                            arg_type.members,
+                                            arg_type.variants)
+        else:
+            args = '''MessageTimestamp Timestamp `json:"-"`'''
+            content = generate_struct_type(type_name, args)
+
+        content += TEMPLATE_EVENT_METHODS.format(name=name, type_name=type_name)
+        self.target["event"] += content
 
     def write(self, output_dir: str) -> None:
         for module_name, content in self.target.items():