This patch adds a struct type in Go that will handle return values
for QAPI's command types.

The return value of a Command is, encouraged to be, QAPI's complex
types or an Array of those.

Every Command has a underlying CommandResult. The EmptyCommandReturn
is for those that don't expect any data e.g: `{ "return": {} }`.

All CommandReturn types implement the CommandResult interface.

Example:
qapi:
    | { 'command': 'query-sev', 'returns': 'SevInfo',
    |   'if': 'TARGET_I386' }

go:
    | type QuerySevCommandReturn struct {
    |     MessageId string     `json:"id,omitempty"`
    |     Result    *SevInfo   `json:"return"`
    |     Error     *QapiError `json:"error,omitempty"`
    | }

usage:
    | // One can use QuerySevCommandReturn directly or
    | // command's interface GetReturnType() instead.
    |
    | input := `{ "return": { "enabled": true, "api-major" : 0,` +
    |                        `"api-minor" : 0, "build-id" : 0,` +
    |                        `"policy" : 0, "state" : "running",` +
    |                        `"handle" : 1 } } `
    |
    | ret := QuerySevCommandReturn{}
    | err := json.Unmarshal([]byte(input), &ret)
    | if ret.Error != nil {
    |     // Handle command failure {"error": { ...}}
    | } else if ret.Result != nil {
    |     // ret.Result.Enable == true
    | }

Signed-off-by: Victor Toso <victort...@redhat.com>
---
 scripts/qapi/golang.py | 104 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 92 insertions(+), 12 deletions(-)

diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index 624bc2af4d..6471ddeb52 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -39,6 +39,15 @@
 """
 
 TEMPLATE_HELPER = """
+type QAPIError struct {
+\tClass       string `json:"class"`
+\tDescription string `json:"desc"`
+}
+
+func (err *QAPIError) Error() string {
+\treturn err.Description
+}
+
 // Creates a decoder that errors on unknown Fields
 // Returns nil if successfully decoded @from payload to @into type
 // Returns error if failed to decode @from payload to @into type
@@ -271,12 +280,17 @@
 func (s *{type_name}) GetId() string {{
 \treturn s.MessageId
 }}
+
+func (s *{type_name}) GetReturnType() CommandReturn {{
+\treturn &{cmd_ret_name}{{}}
+}}
 """
 
 TEMPLATE_COMMAND = """
 type Command interface {{
 \tGetId() string
 \tGetName() string
+\tGetReturnType() CommandReturn
 }}
 
 func MarshalCommand(c Command) ([]byte, error) {{
@@ -308,6 +322,37 @@
 }}
 """
 
+TEMPLATE_COMMAND_RETURN = """
+type CommandReturn interface {
+\tGetId() string
+\tGetCommandName() string
+\tGetError() error
+}
+"""
+
+TEMPLATE_COMMAND_RETURN_METHODS = """
+func (r *{cmd_ret_name}) GetCommandName() string {{
+\treturn "{name}"
+}}
+
+func (r *{cmd_ret_name}) GetId() string {{
+\treturn r.MessageId
+}}
+
+func (r *{cmd_ret_name}) GetError() error {{
+\treturn r.Error
+}}{marshal_empty}
+"""
+
+TEMPLATE_COMMAND_RETURN_MARSHAL_EMPTY = """
+func (r {cmd_ret_name}) MarshalJSON() ([]byte, error) {{
+\tif r.Error != nil {{
+\t\ttype Alias {cmd_ret_name}
+\t\treturn json.Marshal(Alias(r))
+\t}}
+\treturn []byte(`{{"return":{{}}}}`), nil
+}}"""
+
 
 def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
     vis = QAPISchemaGenGolangVisitor(prefix)
@@ -346,7 +391,7 @@ def qapi_to_go_type_name(name: str, meta: Optional[str] = 
None) -> str:
 
     name += "".join(word.title() for word in words[1:])
 
-    types = ["event", "command"]
+    types = ["event", "command", "command return"]
     if meta in types:
         name = name[:-3] if name.endswith("Arg") else name
         name += meta.title().replace(" ", "")
@@ -943,18 +988,19 @@ def generate_template_command(commands: dict[str, 
Tuple[str, str]]) -> str:
         case_type, gocode = commands[name]
         content += gocode
         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
+\tcase "{name}":
+\t\tcommand := struct {{
+\t\t\tArgs {case_type} `json:"arguments"`
+\t\t}}{{}}
+
+\t\tif err := json.Unmarshal(data, &command); err != nil {{
+\t\t\treturn nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
+\t\t}}
+\t\tcommand.Args.MessageId = base.MessageId
+\t\treturn &command.Args, nil
 """
     content += TEMPLATE_COMMAND.format(cases=cases)
+    content += TEMPLATE_COMMAND_RETURN
     return content
 
 
@@ -1182,6 +1228,34 @@ def visit_command(
 
         type_name = qapi_to_go_type_name(name, info.defn_meta)
 
+        cmd_ret_name = qapi_to_go_type_name(name, "command return")
+        marshal_empty = TEMPLATE_COMMAND_RETURN_MARSHAL_EMPTY.format(
+            cmd_ret_name=cmd_ret_name
+        )
+        retargs: List[dict[str:str]] = [
+            {
+                "name": "MessageId",
+                "type": "string",
+                "tag": """`json:"id,omitempty"`""",
+            },
+            {
+                "name": "Error",
+                "type": "*QAPIError",
+                "tag": """`json:"error,omitempty"`""",
+            },
+        ]
+        if ret_type:
+            marshal_empty = ""
+            ret_type_name = qapi_schema_type_to_go_type(ret_type.name)
+            isptr = "*" if ret_type_name[0] not in "*[" else ""
+            retargs.append(
+                {
+                    "name": "Result",
+                    "type": f"{isptr}{ret_type_name}",
+                    "tag": """`json:"return"`""",
+                }
+            )
+
         content = ""
         if boxed or not arg_type or not qapi_name_is_object(arg_type.name):
             args: List[dict[str:str]] = []
@@ -1213,7 +1287,13 @@ def visit_command(
             )
 
         content += TEMPLATE_COMMAND_METHODS.format(
-            name=name, type_name=type_name
+            name=name, type_name=type_name, cmd_ret_name=cmd_ret_name
+        )
+        content += generate_struct_type(cmd_ret_name, retargs)
+        content += TEMPLATE_COMMAND_RETURN_METHODS.format(
+            name=name,
+            cmd_ret_name=cmd_ret_name,
+            marshal_empty=marshal_empty,
         )
         self.commands[name] = (type_name, content)
 
-- 
2.41.0


Reply via email to