From: "Daniel P. Berrangé" <berrange@redhat.com>
To: Victor Toso <victortoso@redhat.com>
Cc: qemu-devel@nongnu.org, Markus Armbruster <armbru@redhat.com>,
John Snow <jsnow@redhat.com>
Subject: Re: [PATCH v1 7/9] qapi: golang: Generate qapi's command types in Go
Date: Thu, 28 Sep 2023 15:32:54 +0100 [thread overview]
Message-ID: <ZRWOlrwq5tS3oh9W@redhat.com> (raw)
In-Reply-To: <20230927112544.85011-8-victortoso@redhat.com>
On Wed, Sep 27, 2023 at 01:25:42PM +0200, Victor Toso wrote:
> This patch handles QAPI command types and generates data structures in
> Go that decodes from QMP JSON Object to Go data structure and vice
> versa.
>
> Similar to Event, this patch adds a Command interface and two helper
> functions MarshalCommand and UnmarshalCommand.
>
> Example:
> qapi:
> | { 'command': 'set_password',
> | 'boxed': true,
> | 'data': 'SetPasswordOptions' }
>
> go:
> | type SetPasswordCommand struct {
> | SetPasswordOptions
> | CommandId string `json:"-"`
IIUC, you renamed that to MessageId in the code now.
> | }
Overall, I'm not entirely convinced that we will want to
have the SetPasswordCommand struct wrappers, byut it is
hard to say, as what we're missing still is the eventual
application facing API.
eg something that ultimately looks more like this:
qemu = qemu.QMPConnection()
qemu.Dial("/path/to/unix/socket.sock")
qemu.VncConnectedEvent(func(ev *VncConnectedEvent) {
fmt.Printf("VNC client %s connected\n", ev.Client.Host)
})
resp, err := qemu.SetPassword(SetPasswordArguments{
protocol: "vnc",
password: "123456",
})
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot set passwd: %s", err)
}
..do something wit resp.... (well SetPassword has no response, but other cmmands do)
It isn't clear that the SetPasswordCommand struct will be
needed internally for the impl if QMPCommand.
>
> usage:
> | input := `{"execute":"set_password",` +
> | `"arguments":{"protocol":"vnc",` +
> | `"password":"secret"}}`
> |
> | c, err := UnmarshalCommand([]byte(input))
> | if err != nil {
> | panic(err)
> | }
> |
> | if c.GetName() == `set_password` {
> | m := c.(*SetPasswordCommand)
> | // m.Password == "secret"
> | }
>
> Signed-off-by: Victor Toso <victortoso@redhat.com>
> ---
> scripts/qapi/golang.py | 97 ++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 94 insertions(+), 3 deletions(-)
>
> diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
> index ff3b1dd020..52a9124641 100644
> --- a/scripts/qapi/golang.py
> +++ b/scripts/qapi/golang.py
> @@ -246,6 +246,51 @@
> }}
> '''
>
> +TEMPLATE_COMMAND_METHODS = '''
> +func (c *{type_name}) GetName() string {{
> + return "{name}"
> +}}
> +
> +func (s *{type_name}) GetId() string {{
> + return s.MessageId
> +}}
> +'''
> +
> +TEMPLATE_COMMAND = '''
> +type Command interface {{
> + GetId() string
> + GetName() string
> +}}
> +
> +func MarshalCommand(c Command) ([]byte, error) {{
> + m := make(map[string]any)
> + m["execute"] = c.GetName()
> + if id := c.GetId(); len(id) > 0 {{
> + m["id"] = id
> + }}
> + if bytes, err := json.Marshal(c); err != nil {{
> + return []byte{{}}, err
> + }} else if len(bytes) > 2 {{
> + m["arguments"] = c
> + }}
> + return json.Marshal(m)
> +}}
> +
> +func UnmarshalCommand(data []byte) (Command, error) {{
> + base := struct {{
> + MessageId string `json:"id,omitempty"`
> + Name string `json:"execute"`
> + }}{{}}
> + if err := json.Unmarshal(data, &base); err != nil {{
> + return nil, fmt.Errorf("Failed to decode command: %s", string(data))
> + }}
> +
> + switch base.Name {{
> + {cases}
> + }}
> + return nil, errors.New("Failed to recognize command")
> +}}
> +'''
>
> def gen_golang(schema: QAPISchema,
> output_dir: str,
> @@ -282,7 +327,7 @@ def qapi_to_go_type_name(name: str,
>
> name += ''.join(word.title() for word in words[1:])
>
> - types = ["event"]
> + types = ["event", "command"]
> if meta in types:
> name = name[:-3] if name.endswith("Arg") else name
> name += meta.title().replace(" ", "")
> @@ -521,6 +566,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}'''
> + elif info.defn_meta == "command":
> + fields += f'''\tMessageId string `json:"-"`\n{fields}'''
>
> if members:
> for member in members:
> @@ -719,16 +766,36 @@ def generate_template_event(events: dict[str, str]) -> str:
> '''
> return TEMPLATE_EVENT.format(cases=cases)
>
> +def generate_template_command(commands: dict[str, str]) -> str:
> + cases = ""
> + for name in sorted(commands):
> + case_type = commands[name]
> + cases += f'''
> +case "{name}":
> + command := struct {{
> + Args {case_type} `json:"arguments"`
> + }}{{}}
> +
> + if err := json.Unmarshal(data, &command); err != nil {{
> + return nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
> + }}
> + command.Args.MessageId = base.MessageId
> + return &command.Args, nil
> +'''
> + content = TEMPLATE_COMMAND.format(cases=cases)
> + return content
> +
>
> class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
>
> def __init__(self, _: str):
> super().__init__()
> - types = ["alternate", "enum", "event", "helper", "struct", "union"]
> + types = ["alternate", "command", "enum", "event", "helper", "struct", "union"]
> self.target = {name: "" for name in types}
> self.objects_seen = {}
> self.schema = None
> self.events = {}
> + self.commands = {}
> self.golang_package_name = "qapi"
> self.accept_null_types = []
>
> @@ -756,6 +823,7 @@ def visit_begin(self, schema):
> def visit_end(self):
> self.schema = None
> self.target["event"] += generate_template_event(self.events)
> + self.target["command"] += generate_template_command(self.commands)
>
> def visit_object_type(self: QAPISchemaGenGolangVisitor,
> name: str,
> @@ -853,7 +921,30 @@ def visit_command(self,
> allow_oob: bool,
> allow_preconfig: bool,
> coroutine: bool) -> None:
> - pass
> + assert name == info.defn_name
> +
> + type_name = qapi_to_go_type_name(name, info.defn_meta)
> + self.commands[name] = type_name
> +
> + content = ""
> + if boxed or not arg_type or not qapi_name_is_object(arg_type.name):
> + args = "" if not arg_type else "\n" + arg_type.name
> + args += '''\n\tMessageId string `json:"-"`'''
> + content = generate_struct_type(type_name, args)
> + else:
> + assert 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)
> +
> + content += TEMPLATE_COMMAND_METHODS.format(name=name,
> + type_name=type_name)
> + self.target["command"] += content
>
> def visit_event(self, name, info, ifcond, features, arg_type, boxed):
> assert name == info.defn_name
> --
> 2.41.0
>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
next prev parent reply other threads:[~2023-09-28 14:33 UTC|newest]
Thread overview: 44+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-09-27 11:25 [PATCH v1 0/9] qapi-go: add generator for Golang interface Victor Toso
2023-09-27 11:25 ` [PATCH v1 1/9] qapi: golang: Generate qapi's enum types in Go Victor Toso
2023-09-28 13:52 ` Daniel P. Berrangé
2023-09-28 14:20 ` Markus Armbruster
2023-09-28 14:34 ` Daniel P. Berrangé
2023-09-29 12:07 ` Victor Toso
2023-10-02 19:07 ` John Snow
2023-10-02 20:09 ` John Snow
2023-10-04 12:43 ` Victor Toso
2023-10-04 16:23 ` John Snow
2023-10-04 12:28 ` Victor Toso
2023-09-27 11:25 ` [PATCH v1 2/9] qapi: golang: Generate qapi's alternate " Victor Toso
2023-09-28 14:51 ` Daniel P. Berrangé
2023-09-29 12:23 ` Victor Toso
2023-09-29 12:37 ` Daniel P. Berrangé
2023-10-02 21:48 ` John Snow
2023-10-04 17:01 ` Victor Toso
2023-10-02 20:36 ` John Snow
2023-10-04 16:46 ` Victor Toso
2023-09-27 11:25 ` [PATCH v1 3/9] qapi: golang: Generate qapi's struct " Victor Toso
2023-09-28 14:06 ` Daniel P. Berrangé
2023-09-29 13:29 ` Victor Toso
2023-09-29 13:33 ` Daniel P. Berrangé
2023-09-27 11:25 ` [PATCH v1 4/9] qapi: golang: structs: Address 'null' members Victor Toso
2023-09-27 11:25 ` [PATCH v1 5/9] qapi: golang: Generate qapi's union types in Go Victor Toso
2023-09-28 14:21 ` Daniel P. Berrangé
2023-09-29 13:41 ` Victor Toso
2023-10-11 13:27 ` Victor Toso
2023-09-27 11:25 ` [PATCH v1 6/9] qapi: golang: Generate qapi's event " Victor Toso
2023-09-27 11:25 ` [PATCH v1 7/9] qapi: golang: Generate qapi's command " Victor Toso
2023-09-28 14:32 ` Daniel P. Berrangé [this message]
2023-09-29 13:53 ` Victor Toso
2023-10-14 14:26 ` Victor Toso
2023-09-27 11:25 ` [PATCH v1 8/9] qapi: golang: Add CommandResult type to Go Victor Toso
2023-09-28 15:03 ` Daniel P. Berrangé
2023-09-29 13:55 ` Victor Toso
2023-09-27 11:25 ` [PATCH v1 9/9] docs: add notes on Golang code generator Victor Toso
2023-09-28 13:22 ` Daniel P. Berrangé
2023-09-29 12:00 ` Victor Toso
2023-09-27 11:38 ` [PATCH v1 0/9] qapi-go: add generator for Golang interface Victor Toso
2023-09-28 13:40 ` Daniel P. Berrangé
2023-09-28 13:54 ` Daniel P. Berrangé
2023-09-29 14:08 ` Victor Toso
2023-09-29 14:17 ` 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=ZRWOlrwq5tS3oh9W@redhat.com \
--to=berrange@redhat.com \
--cc=armbru@redhat.com \
--cc=jsnow@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=victortoso@redhat.com \
/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).