qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Victor Toso <victortoso@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Markus Armbruster" <armbru@redhat.com>,
	"John Snow" <jsnow@redhat.com>,
	"Daniel P . Berrangé" <berrange@redhat.com>,
	"Andrea Bolognani" <abologna@redhat.com>
Subject: [PATCH v2 08/11] qapi: golang: Generate qapi's event types in Go
Date: Mon, 16 Oct 2023 17:27:01 +0200	[thread overview]
Message-ID: <20231016152704.221611-9-victortoso@redhat.com> (raw)
In-Reply-To: <20231016152704.221611-1-victortoso@redhat.com>

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 | 122 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 119 insertions(+), 3 deletions(-)

diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index bc6206797a..81b320d6dd 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -215,6 +215,54 @@
 }}
 """
 
+TEMPLATE_EVENT = """
+type Timestamp struct {{
+\tSeconds      int64 `json:"seconds"`
+\tMicroseconds int64 `json:"microseconds"`
+}}
+
+type Event interface {{
+\tGetName() string
+\tGetTimestamp() Timestamp
+}}
+
+func MarshalEvent(e Event) ([]byte, error) {{
+\tm := make(map[string]any)
+\tm["event"] = e.GetName()
+\tm["timestamp"] = e.GetTimestamp()
+\tif bytes, err := json.Marshal(e); err != nil {{
+\t\treturn []byte{{}}, err
+\t}} else if len(bytes) > 2 {{
+\t\tm["data"] = e
+\t}}
+\treturn json.Marshal(m)
+}}
+
+func UnmarshalEvent(data []byte) (Event, error) {{
+\tbase := struct {{
+\t\tName             string    `json:"event"`
+\t\tMessageTimestamp Timestamp `json:"timestamp"`
+\t}}{{}}
+\tif err := json.Unmarshal(data, &base); err != nil {{
+\t\treturn nil, fmt.Errorf("Failed to decode event: %s", string(data))
+\t}}
+
+\tswitch base.Name {{{cases}
+\t}}
+\treturn nil, errors.New("Failed to recognize event")
+}}
+"""
+
+TEMPLATE_EVENT_METHODS = """
+func (s *{type_name}) GetName() string {{
+\treturn "{name}"
+}}
+
+func (s *{type_name}) GetTimestamp() Timestamp {{
+\treturn s.MessageTimestamp
+}}
+"""
+
 
 def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
     vis = QAPISchemaGenGolangVisitor(prefix)
@@ -238,7 +286,7 @@ 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:]
 
@@ -253,6 +301,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
 
 
@@ -608,6 +661,15 @@ def qapi_to_golang_struct(
 
     discriminator = None if not variants else variants.tag_member.name
     fields, with_nullable = recursive_base(self, base, discriminator)
+    if info.defn_meta == "event":
+        fields.insert(
+            0,
+            {
+                "name": "MessageTimestamp",
+                "type": "Timestamp",
+                "tag": """`json:"-"`""",
+            },
+        )
 
     if members:
         for member in members:
@@ -651,7 +713,7 @@ def qapi_to_golang_struct(
                 fields.append(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, ident)
     if with_nullable:
         content += struct_with_nullable_generate_marshal(
@@ -825,6 +887,28 @@ def generate_template_alternate(
     return content
 
 
+def generate_template_event(events: dict[str, Tuple[str, str]]) -> str:
+    cases = ""
+    content = ""
+    for name in sorted(events):
+        case_type, gocode = events[name]
+        content += gocode
+        cases += f"""
+\tcase "{name}":
+\t\tevent := struct {{
+\t\t\tData {case_type} `json:"data"`
+\t\t}}{{}}
+
+\t\tif err := json.Unmarshal(data, &event); err != nil {{
+\t\t\treturn nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
+\t\t}}
+\t\tevent.Data.MessageTimestamp = base.MessageTimestamp
+\t\treturn &event.Data, nil
+"""
+    content += TEMPLATE_EVENT.format(cases=cases)
+    return content
+
+
 def generate_content_from_dict(data: dict[str, str]) -> str:
     content = ""
 
@@ -841,12 +925,14 @@ def __init__(self, _: str):
         types = (
             "alternate",
             "enum",
+            "event",
             "helper",
             "struct",
             "union",
         )
         self.target = dict.fromkeys(types, "")
         self.schema: QAPISchema
+        self.events: dict[str, Tuple[str, str]] = {}
         self.golang_package_name = "qapi"
         self.enums: dict[str, str] = {}
         self.alternates: dict[str, str] = {}
@@ -901,6 +987,7 @@ def visit_end(self) -> None:
         self.target["alternate"] += generate_content_from_dict(self.alternates)
         self.target["struct"] += generate_content_from_dict(self.structs)
         self.target["union"] += generate_content_from_dict(self.unions)
+        self.target["event"] += generate_template_event(self.events)
 
     def visit_object_type(
         self,
@@ -1027,7 +1114,36 @@ def visit_event(
         arg_type: Optional[QAPISchemaObjectType],
         boxed: bool,
     ) -> None:
-        pass
+        assert name == info.defn_name
+        assert name not in self.events
+        type_name = qapi_to_go_type_name(name, info.defn_meta)
+
+        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: List[dict[str:str]] = []
+            args.append(
+                {
+                    "name": "MessageTimestamp",
+                    "type": "Timestamp",
+                    "tag": """`json:"-"`""",
+                }
+            )
+            content = generate_struct_type(type_name, args)
+
+        content += TEMPLATE_EVENT_METHODS.format(
+            name=name, type_name=type_name
+        )
+        self.events[name] = (type_name, content)
 
     def write(self, output_dir: str) -> None:
         for module_name, content in self.target.items():
-- 
2.41.0



  parent reply	other threads:[~2023-10-16 15:28 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-10-16 15:26 [PATCH v2 00/11] qapi-go: add generator for Golang interface Victor Toso
2023-10-16 15:26 ` [PATCH v2 01/11] qapi: re-establish linting baseline Victor Toso
2023-10-16 15:26 ` [PATCH v2 02/11] scripts: qapi: black format main.py Victor Toso
2023-10-18 11:00   ` Markus Armbruster
2023-10-18 11:13     ` Daniel P. Berrangé
2023-10-18 15:23     ` Victor Toso
2023-10-19  5:42       ` Markus Armbruster
2023-10-19  7:30         ` Daniel P. Berrangé
2023-10-16 15:26 ` [PATCH v2 03/11] qapi: golang: Generate qapi's enum types in Go Victor Toso
2023-10-16 15:26 ` [PATCH v2 04/11] qapi: golang: Generate qapi's alternate " Victor Toso
2023-11-06 15:28   ` Andrea Bolognani
2023-11-06 15:52     ` Victor Toso
2023-11-06 16:12       ` Andrea Bolognani
2023-11-09 17:34   ` Andrea Bolognani
2023-11-09 19:01     ` Victor Toso
2023-11-10  9:58       ` Andrea Bolognani
2023-11-10 15:43         ` Victor Toso
2023-10-16 15:26 ` [PATCH v2 05/11] qapi: golang: Generate qapi's struct " Victor Toso
2023-10-16 15:26 ` [PATCH v2 06/11] qapi: golang: structs: Address 'null' members Victor Toso
2023-10-16 15:27 ` [PATCH v2 07/11] qapi: golang: Generate qapi's union types in Go Victor Toso
2023-11-09 17:29   ` Andrea Bolognani
2023-11-09 18:35     ` Victor Toso
2023-11-10  9:54       ` Andrea Bolognani
2023-11-10 15:45         ` Victor Toso
2023-10-16 15:27 ` Victor Toso [this message]
2023-11-09 17:59   ` [PATCH v2 08/11] qapi: golang: Generate qapi's event " Andrea Bolognani
2023-11-09 19:13     ` Victor Toso
2023-11-10  9:52       ` Andrea Bolognani
2023-10-16 15:27 ` [PATCH v2 09/11] qapi: golang: Generate qapi's command " Victor Toso
2023-10-16 15:27 ` [PATCH v2 10/11] qapi: golang: Add CommandResult type to Go Victor Toso
2023-11-09 18:24   ` Andrea Bolognani
2023-11-09 19:31     ` Victor Toso
2023-11-10  9:46       ` Andrea Bolognani
2023-10-16 15:27 ` [PATCH v2 11/11] docs: add notes on Golang code generator Victor Toso
2023-10-18 11:47   ` Markus Armbruster
2023-10-18 16:21     ` Victor Toso
2023-10-19  6:56       ` Markus Armbruster
2023-10-27 17:33 ` [PATCH v2 00/11] qapi-go: add generator for Golang interface Victor Toso
2023-10-31 16:42   ` Andrea Bolognani
2023-11-03 18:34     ` Andrea Bolognani
2023-11-06 12:00       ` Victor Toso
2023-11-09 18:35 ` Andrea Bolognani
2023-11-09 19:03   ` Victor Toso

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231016152704.221611-9-victortoso@redhat.com \
    --to=victortoso@redhat.com \
    --cc=abologna@redhat.com \
    --cc=armbru@redhat.com \
    --cc=berrange@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).