diff mbox series

[RFC,v1,1/8] qapi: golang: Generate qapi's enum types in Go

Message ID 20220401224104.145961-2-victortoso@redhat.com (mailing list archive)
State New, archived
Headers show
Series qapi: add generator for Golang interface | expand

Commit Message

Victor Toso April 1, 2022, 10:40 p.m. UTC
This patch handles QAPI enum types and generates its equivalent in Go.

The highlights of this implementation are:

1. For each QAPI enum, we will define an int32 type in Go to be the
   assigned type of this specific enum

2. While in the Go codebase we can use the generated enum values, the
   specification requires that, on the wire, the enumeration type's
   value to be represented by its string name.

   For this reason, each Go type that represent's a QAPI enum will be
   implementing the Marshaler[0] and Unmarshaler[1] interfaces to
   seamless handle QMP's string to Go int32 value and vice-versa.

3. Naming: CamelCase will be used in any identifier that we want to
   export [2], which is everything in this patch.

[0] https://pkg.go.dev/encoding/json#Marshaler
[1] https://pkg.go.dev/encoding/json#Unmarshaler
[2] https://go.dev/ref/spec#Exported_identifiers

Signed-off-by: Victor Toso <victortoso@redhat.com>
---
 qapi/meson.build       |   1 +
 scripts/qapi/golang.py | 225 +++++++++++++++++++++++++++++++++++++++++
 scripts/qapi/main.py   |   2 +
 3 files changed, 228 insertions(+)
 create mode 100644 scripts/qapi/golang.py

Comments

Daniel P. Berrangé May 10, 2022, 10:06 a.m. UTC | #1
On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote:
> This patch handles QAPI enum types and generates its equivalent in Go.
> 
> The highlights of this implementation are:
> 
> 1. For each QAPI enum, we will define an int32 type in Go to be the
>    assigned type of this specific enum

I think there's a potential case to be made for considering
representation of enums as strings in Golang, as it more
gracefully degrades.

Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field.

This implementation has

type ShutdownCause int32

const (
        ShutdownCauseNone               ShutdownCause = iota
        ShutdownCauseHostError                        // An error prevents further use of guest
        ShutdownCauseHostQmpQuit                      // Reaction to the QMP command 'quit'
        ShutdownCauseHostQmpSystemReset               // Reaction to the QMP command 'system_reset'
        ShutdownCauseHostSignal                       // Reaction to a signal, such as SIGINT
        ShutdownCauseHostUi                           // Reaction to a UI event, like window close
        ShutdownCauseGuestShutdown                    // Guest shutdown/suspend request, via ACPI or other hardware-specific means
        ShutdownCauseGuestReset                       // Guest reset request, and command line turns that into a shutdown
        ShutdownCauseGuestPanic                       // Guest panicked, and command line turns that into a shutdown
        ShutdownCauseSubsystemReset                   // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot
)


and


type ShutdownEvent struct {
        Guest  bool          `json:"guest"`  // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10)
        Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0)
}

Consider that the application is compiled against the QAPI generated
API from QEMU 7.0. The application is believed to run happily against
7.1, because app doesn't need any of the functionality added in 7.1
QAPI.  But consider that QEMU 7.1 had introduced a new shutdown reason
value.

The application may want to know that the guest has shutdown, but does
NOT care about the  reason for the shutdown.

Since the ShutdownReason is implemented as an int though, the unmarshalling
for the reason field is going to fail.

If the enums were just represented as strings, then we can gracefully
accept any new enum value that arrives in future. The application can
thus also still log the shutdown reason string, even though it was not
a value explicitly known to the generated API.

> 
> 2. While in the Go codebase we can use the generated enum values, the
>    specification requires that, on the wire, the enumeration type's
>    value to be represented by its string name.
> 
>    For this reason, each Go type that represent's a QAPI enum will be
>    implementing the Marshaler[0] and Unmarshaler[1] interfaces to
>    seamless handle QMP's string to Go int32 value and vice-versa.
> 
> 3. Naming: CamelCase will be used in any identifier that we want to
>    export [2], which is everything in this patch.

With regards,
Daniel
Victor Toso May 10, 2022, 11:15 a.m. UTC | #2
Hi,

On Tue, May 10, 2022 at 11:06:16AM +0100, Daniel P. Berrangé wrote:
> On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote:
> > This patch handles QAPI enum types and generates its equivalent in Go.
> > 
> > The highlights of this implementation are:
> > 
> > 1. For each QAPI enum, we will define an int32 type in Go to be the
> >    assigned type of this specific enum
> 
> I think there's a potential case to be made for considering
> representation of enums as strings in Golang, as it more
> gracefully degrades.
> 
> Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field.
> 
> This implementation has
> 
> type ShutdownCause int32
> 
> const (
>         ShutdownCauseNone               ShutdownCause = iota
>         ShutdownCauseHostError                        // An error prevents further use of guest
>         ShutdownCauseHostQmpQuit                      // Reaction to the QMP command 'quit'
>         ShutdownCauseHostQmpSystemReset               // Reaction to the QMP command 'system_reset'
>         ShutdownCauseHostSignal                       // Reaction to a signal, such as SIGINT
>         ShutdownCauseHostUi                           // Reaction to a UI event, like window close
>         ShutdownCauseGuestShutdown                    // Guest shutdown/suspend request, via ACPI or other hardware-specific means
>         ShutdownCauseGuestReset                       // Guest reset request, and command line turns that into a shutdown
>         ShutdownCauseGuestPanic                       // Guest panicked, and command line turns that into a shutdown
>         ShutdownCauseSubsystemReset                   // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot
> )
> 
> and
> 
> type ShutdownEvent struct {
>         Guest  bool          `json:"guest"`  // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10)
>         Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0)
> }
> 
> Consider that the application is compiled against the QAPI
> generated API from QEMU 7.0. The application is believed to run
> happily against 7.1, because app doesn't need any of the
> functionality added in 7.1 QAPI.  But consider that QEMU 7.1
> had introduced a new shutdown reason value.
> 
> The application may want to know that the guest has shutdown,
> but does NOT care about the reason for the shutdown.
> 
> Since the ShutdownReason is implemented as an int though, the
> unmarshalling for the reason field is going to fail.

Marshalling does error if you try to convert an int that is not
in the range of the enum type.

Unmarshalling should not error in this case, but the field ends
up not being set which defaults to 0 (in this case,
ShutdownCauseNone). That could mislead the *actual* reason but
not without a warning which is expected in this case, imho.

(I know is not an actual warning, just a Println, but it'll be
fixed in the next iteration)

  | func (s *ShutdownCause) UnmarshalJSON(data []byte) error {
  |     var name string
  |
  |     if err := json.Unmarshal(data, &name); err != nil {
  |         return err
  |     }
  |
  |     switch name {
  |     case "none":
  |         (*s) = ShutdownCauseNone
  |     case "host-error":
  |         (*s) = ShutdownCauseHostError
  |     case "host-qmp-quit":
  |         (*s) = ShutdownCauseHostQmpQuit
  |     case "host-qmp-system-reset":
  |         (*s) = ShutdownCauseHostQmpSystemReset
  |     case "host-signal":
  |         (*s) = ShutdownCauseHostSignal
  |     case "host-ui":
  |         (*s) = ShutdownCauseHostUi
  |     case "guest-shutdown":
  |         (*s) = ShutdownCauseGuestShutdown
  |     case "guest-reset":
  |         (*s) = ShutdownCauseGuestReset
  |     case "guest-panic":
  |         (*s) = ShutdownCauseGuestPanic
  |     case "subsystem-reset":
  |         (*s) = ShutdownCauseSubsystemReset
  |     default:
  |         fmt.Println("Failed to decode ShutdownCause", *s)
  |     }
  |     return nil
  | }

> If the enums were just represented as strings, then we can
> gracefully accept any new enum value that arrives in future.
> The application can thus also still log the shutdown reason
> string, even though it was not a value explicitly known to the
> generated API.

As a string, the warning should still exist and default value of
"" or nil for ptr would apply. IMHO, between string + warning and
int + warning, I'd still go with int here.

That's a design decision to be made. All in all, I don't know
much about the changes in QEMU/QAPI per version to take this
decision, so I'll rely on you and the list for this, not just for
enums but for the other types too.

> > 2. While in the Go codebase we can use the generated enum values, the
> >    specification requires that, on the wire, the enumeration type's
> >    value to be represented by its string name.
> > 
> >    For this reason, each Go type that represent's a QAPI enum will be
> >    implementing the Marshaler[0] and Unmarshaler[1] interfaces to
> >    seamless handle QMP's string to Go int32 value and vice-versa.
> > 
> > 3. Naming: CamelCase will be used in any identifier that we want to
> >    export [2], which is everything in this patch.

Cheers,
Victor
Daniel P. Berrangé May 10, 2022, 11:19 a.m. UTC | #3
On Tue, May 10, 2022 at 01:15:32PM +0200, Victor Toso wrote:
> Hi,
> 
> On Tue, May 10, 2022 at 11:06:16AM +0100, Daniel P. Berrangé wrote:
> > On Sat, Apr 02, 2022 at 12:40:57AM +0200, Victor Toso wrote:
> > > This patch handles QAPI enum types and generates its equivalent in Go.
> > > 
> > > The highlights of this implementation are:
> > > 
> > > 1. For each QAPI enum, we will define an int32 type in Go to be the
> > >    assigned type of this specific enum
> > 
> > I think there's a potential case to be made for considering
> > representation of enums as strings in Golang, as it more
> > gracefully degrades.
> > 
> > Consider the 'SHUTDOWN' event in QEMU, which has a 'reason' field.
> > 
> > This implementation has
> > 
> > type ShutdownCause int32
> > 
> > const (
> >         ShutdownCauseNone               ShutdownCause = iota
> >         ShutdownCauseHostError                        // An error prevents further use of guest
> >         ShutdownCauseHostQmpQuit                      // Reaction to the QMP command 'quit'
> >         ShutdownCauseHostQmpSystemReset               // Reaction to the QMP command 'system_reset'
> >         ShutdownCauseHostSignal                       // Reaction to a signal, such as SIGINT
> >         ShutdownCauseHostUi                           // Reaction to a UI event, like window close
> >         ShutdownCauseGuestShutdown                    // Guest shutdown/suspend request, via ACPI or other hardware-specific means
> >         ShutdownCauseGuestReset                       // Guest reset request, and command line turns that into a shutdown
> >         ShutdownCauseGuestPanic                       // Guest panicked, and command line turns that into a shutdown
> >         ShutdownCauseSubsystemReset                   // Partial guest reset that does not trigger QMP events and ignores --no-reboot. This is useful for sanitizing hypercalls on s390 that are used during kexec/kdump/boot
> > )
> > 
> > and
> > 
> > type ShutdownEvent struct {
> >         Guest  bool          `json:"guest"`  // If true, the shutdown was triggered by a guest request (such as a guest-initiated ACPI shutdown request or other hardware-specific action) rather than a host request (such as sending qemu a SIGINT). (since 2.10)
> >         Reason ShutdownCause `json:"reason"` // The @ShutdownCause which resulted in the SHUTDOWN. (since 4.0)
> > }
> > 
> > Consider that the application is compiled against the QAPI
> > generated API from QEMU 7.0. The application is believed to run
> > happily against 7.1, because app doesn't need any of the
> > functionality added in 7.1 QAPI.  But consider that QEMU 7.1
> > had introduced a new shutdown reason value.
> > 
> > The application may want to know that the guest has shutdown,
> > but does NOT care about the reason for the shutdown.
> > 
> > Since the ShutdownReason is implemented as an int though, the
> > unmarshalling for the reason field is going to fail.
> 
> Marshalling does error if you try to convert an int that is not
> in the range of the enum type.
> 
> Unmarshalling should not error in this case, but the field ends
> up not being set which defaults to 0 (in this case,
> ShutdownCauseNone). That could mislead the *actual* reason but
> not without a warning which is expected in this case, imho.
> 
> (I know is not an actual warning, just a Println, but it'll be
> fixed in the next iteration)

I don't thinnk we should be emitting warnings/printlns at all
in this case though. The app should be able to consume the
enum without needing a warning.  If the app wants to validate
against a specific set of constants, it can decide to emit
a warning if there was a case it can't handle. We shouldn't
emit warnings/errors in the unmarshalling step though,as it
isn't the right place to decide on such policy.

> 
>   | func (s *ShutdownCause) UnmarshalJSON(data []byte) error {
>   |     var name string
>   |
>   |     if err := json.Unmarshal(data, &name); err != nil {
>   |         return err
>   |     }
>   |
>   |     switch name {
>   |     case "none":
>   |         (*s) = ShutdownCauseNone
>   |     case "host-error":
>   |         (*s) = ShutdownCauseHostError
>   |     case "host-qmp-quit":
>   |         (*s) = ShutdownCauseHostQmpQuit
>   |     case "host-qmp-system-reset":
>   |         (*s) = ShutdownCauseHostQmpSystemReset
>   |     case "host-signal":
>   |         (*s) = ShutdownCauseHostSignal
>   |     case "host-ui":
>   |         (*s) = ShutdownCauseHostUi
>   |     case "guest-shutdown":
>   |         (*s) = ShutdownCauseGuestShutdown
>   |     case "guest-reset":
>   |         (*s) = ShutdownCauseGuestReset
>   |     case "guest-panic":
>   |         (*s) = ShutdownCauseGuestPanic
>   |     case "subsystem-reset":
>   |         (*s) = ShutdownCauseSubsystemReset
>   |     default:
>   |         fmt.Println("Failed to decode ShutdownCause", *s)
>   |     }
>   |     return nil
>   | }
> 
> > If the enums were just represented as strings, then we can
> > gracefully accept any new enum value that arrives in future.
> > The application can thus also still log the shutdown reason
> > string, even though it was not a value explicitly known to the
> > generated API.
> 
> As a string, the warning should still exist and default value of
> "" or nil for ptr would apply. IMHO, between string + warning and
> int + warning, I'd still go with int here.
> 
> That's a design decision to be made. All in all, I don't know
> much about the changes in QEMU/QAPI per version to take this
> decision, so I'll rely on you and the list for this, not just for
> enums but for the other types too.

Essentially every release of QEMU will have QAPI changes. Most of
the time these are append-only changes. ie a new struct, new command,
new field, new enum value.  Sometimes there will be removals due
to deprecation, though note my other reply saying that we really
ought to stop doing removals from the schema, and instead just
annotate when a field stops being used.

With regards,
Daniel
Victor Toso May 10, 2022, 11:28 a.m. UTC | #4
Hi,

On Tue, May 10, 2022 at 12:19:57PM +0100, Daniel P. Berrangé wrote:
> > Marshalling does error if you try to convert an int that is not
> > in the range of the enum type.
> > 
> > Unmarshalling should not error in this case, but the field ends
> > up not being set which defaults to 0 (in this case,
> > ShutdownCauseNone). That could mislead the *actual* reason but
> > not without a warning which is expected in this case, imho.
> > 
> > (I know is not an actual warning, just a Println, but it'll be
> > fixed in the next iteration)
> 
> I don't thinnk we should be emitting warnings/printlns at all
> in this case though. The app should be able to consume the enum
> without needing a warning.  If the app wants to validate
> against a specific set of constants, it can decide to emit a
> warning if there was a case it can't handle. We shouldn't emit
> warnings/errors in the unmarshalling step though,as it isn't
> the right place to decide on such policy.

I think it is useful to know that, a binary compiled to qapi-go
7.0 but running with qemu 7.1 would have some mismatches. It
could help detect issues (e.g: Why my program doesn't know/show
the reason for shutdown?).

So, some sort of --verbose option for this level should exist.

> >   | func (s *ShutdownCause) UnmarshalJSON(data []byte) error {
> >   |     var name string
> >   |
> >   |     if err := json.Unmarshal(data, &name); err != nil {
> >   |         return err
> >   |     }
> >   |
> >   |     switch name {
> >   |     case "none":
> >   |         (*s) = ShutdownCauseNone
> >   |     case "host-error":
> >   |         (*s) = ShutdownCauseHostError
> >   |     case "host-qmp-quit":
> >   |         (*s) = ShutdownCauseHostQmpQuit
> >   |     case "host-qmp-system-reset":
> >   |         (*s) = ShutdownCauseHostQmpSystemReset
> >   |     case "host-signal":
> >   |         (*s) = ShutdownCauseHostSignal
> >   |     case "host-ui":
> >   |         (*s) = ShutdownCauseHostUi
> >   |     case "guest-shutdown":
> >   |         (*s) = ShutdownCauseGuestShutdown
> >   |     case "guest-reset":
> >   |         (*s) = ShutdownCauseGuestReset
> >   |     case "guest-panic":
> >   |         (*s) = ShutdownCauseGuestPanic
> >   |     case "subsystem-reset":
> >   |         (*s) = ShutdownCauseSubsystemReset
> >   |     default:
> >   |         fmt.Println("Failed to decode ShutdownCause", *s)
> >   |     }
> >   |     return nil
> >   | }
> > 
> > > If the enums were just represented as strings, then we can
> > > gracefully accept any new enum value that arrives in future.
> > > The application can thus also still log the shutdown reason
> > > string, even though it was not a value explicitly known to the
> > > generated API.
> > 
> > As a string, the warning should still exist and default value of
> > "" or nil for ptr would apply. IMHO, between string + warning and
> > int + warning, I'd still go with int here.
> > 
> > That's a design decision to be made. All in all, I don't know
> > much about the changes in QEMU/QAPI per version to take this
> > decision, so I'll rely on you and the list for this, not just for
> > enums but for the other types too.
> 
> Essentially every release of QEMU will have QAPI changes. Most of
> the time these are append-only changes. ie a new struct, new command,
> new field, new enum value.  Sometimes there will be removals due
> to deprecation, though note my other reply saying that we really
> ought to stop doing removals from the schema, and instead just
> annotate when a field stops being used.

Ok, thanks.

Cheers,
Victor
Daniel P. Berrangé May 10, 2022, 11:39 a.m. UTC | #5
On Tue, May 10, 2022 at 01:28:39PM +0200, Victor Toso wrote:
> Hi,
> 
> On Tue, May 10, 2022 at 12:19:57PM +0100, Daniel P. Berrangé wrote:
> > > Marshalling does error if you try to convert an int that is not
> > > in the range of the enum type.
> > > 
> > > Unmarshalling should not error in this case, but the field ends
> > > up not being set which defaults to 0 (in this case,
> > > ShutdownCauseNone). That could mislead the *actual* reason but
> > > not without a warning which is expected in this case, imho.
> > > 
> > > (I know is not an actual warning, just a Println, but it'll be
> > > fixed in the next iteration)
> > 
> > I don't thinnk we should be emitting warnings/printlns at all
> > in this case though. The app should be able to consume the enum
> > without needing a warning.  If the app wants to validate
> > against a specific set of constants, it can decide to emit a
> > warning if there was a case it can't handle. We shouldn't emit
> > warnings/errors in the unmarshalling step though,as it isn't
> > the right place to decide on such policy.
> 
> I think it is useful to know that, a binary compiled to qapi-go
> 7.0 but running with qemu 7.1 would have some mismatches. It
> could help detect issues (e.g: Why my program doesn't know/show
> the reason for shutdown?).

If apps are actually looking at the shutdown reasons, it is
trivial for the app themselves to log any unexpected reasons.

They should in fact do that regardless, because if someone
re-compiles the app against a new version of the QEMU Go API,
there may be new ShutdownCause's defined that their codes does
not handle. There's no compile time check from Go side that
their code has handled all ShutdownCause values, since there's
no struct enum type in Go.  This again pushes me to towards
the view that the enums should just remain as strings, the use
of integers gives no functional benefit AFAICT, given the lack
of a real enum type in Go.


With regards,
Daniel
diff mbox series

Patch

diff --git a/qapi/meson.build b/qapi/meson.build
index 656ef0e039..0951692332 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -90,6 +90,7 @@  qapi_nonmodule_outputs = [
   'qapi-init-commands.h', 'qapi-init-commands.c',
   'qapi-events.h', 'qapi-events.c',
   'qapi-emit-events.c', 'qapi-emit-events.h',
+  'qapibara.go',
 ]
 
 # First build all sources
diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
new file mode 100644
index 0000000000..070d4cbbae
--- /dev/null
+++ b/scripts/qapi/golang.py
@@ -0,0 +1,225 @@ 
+"""
+Golang QAPI generator
+"""
+# Copyright (c) 2021 Red Hat Inc.
+#
+# Authors:
+#  Victor Toso <victortoso@redhat.com>
+# 
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+# Just for type hint on self
+from __future__ import annotations
+
+import os
+from typing import Dict, List, Optional
+
+from .schema import (
+    QAPISchema,
+    QAPISchemaVisitor,
+    QAPISchemaEnumMember,
+    QAPISchemaFeature,
+    QAPISchemaIfCond,
+    QAPISchemaObjectType,
+    QAPISchemaObjectTypeMember,
+    QAPISchemaVariants,
+)
+from .source import QAPISourceInfo
+
+class QAPISchemaGenGolangVisitor(QAPISchemaVisitor):
+
+    def __init__(self, prefix: str):
+        super().__init__()
+        self.target = {name: "" for name in ["enum"]}
+        self.schema = None
+        self._docmap = {}
+        self.golang_package_name = "qapi"
+
+    def visit_begin(self, schema):
+        self.schema = schema
+
+        # Every Go file needs to reference its package name
+        for target in self.target:
+            self.target[target] = f"package {self.golang_package_name}\n"
+
+        # Iterate once in schema.docs to map doc objects to its name
+        for doc in schema.docs:
+            if doc.symbol is None:
+                continue
+            self._docmap[doc.symbol] = doc
+
+    def visit_end(self):
+        self.schema = None
+
+    def visit_object_type(self: QAPISchemaGenGolangVisitor,
+                          name: str,
+                          info: Optional[QAPISourceInfo],
+                          ifcond: QAPISchemaIfCond,
+                          features: List[QAPISchemaFeature],
+                          base: Optional[QAPISchemaObjectType],
+                          members: List[QAPISchemaObjectTypeMember],
+                          variants: Optional[QAPISchemaVariants]
+                          ) -> None:
+        pass
+
+    def visit_alternate_type(self: QAPISchemaGenGolangVisitor,
+                             name: str,
+                             info: Optional[QAPISourceInfo],
+                             ifcond: QAPISchemaIfCond,
+                             features: List[QAPISchemaFeature],
+                             variants: QAPISchemaVariants
+                             ) -> None:
+        pass
+
+    def visit_enum_type(self: QAPISchemaGenGolangVisitor,
+                        name: str,
+                        info: Optional[QAPISourceInfo],
+                        ifcond: QAPISchemaIfCond,
+                        features: List[QAPISchemaFeature],
+                        members: List[QAPISchemaEnumMember],
+                        prefix: Optional[str]
+                        ) -> None:
+        doc = self._docmap.get(name, None)
+        doc_struct, doc_fields = qapi_to_golang_struct_docs(doc)
+
+        value = qapi_to_field_name_enum(members[0].name)
+        fields = f"\t{name}{value} {name} = iota\n"
+        for member in members[1:]:
+            field_doc = " " + doc_fields.get(member.name, "") if doc_fields else ""
+            value = qapi_to_field_name_enum(member.name)
+            fields += f"\t{name}{value}{field_doc}\n"
+
+        self.target["enum"] += f'''
+{doc_struct}
+type {name} int32
+const (
+{fields[:-1]}
+)
+{generate_marshal_methods_enum(members)}
+'''
+
+    def visit_array_type(self, name, info, ifcond, element_type):
+        pass
+
+    def visit_command(self,
+                      name: str,
+                      info: Optional[QAPISourceInfo],
+                      ifcond: QAPISchemaIfCond,
+                      features: List[QAPISchemaFeature],
+                      arg_type: Optional[QAPISchemaObjectType],
+                      ret_type: Optional[QAPISchemaType],
+                      gen: bool,
+                      success_response: bool,
+                      boxed: bool,
+                      allow_oob: bool,
+                      allow_preconfig: bool,
+                      coroutine: bool) -> None:
+        pass
+
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
+        pass
+
+    def write(self, output_dir: str) -> None:
+        for module_name, content in self.target.items():
+            go_module = module_name + "s.go"
+            go_dir = "go"
+            pathname = os.path.join(output_dir, go_dir, go_module)
+            odir = os.path.dirname(pathname)
+            os.makedirs(odir, exist_ok=True)
+
+            with open(pathname, "w") as outfile:
+                outfile.write(content)
+
+
+def gen_golang(schema: QAPISchema,
+               output_dir: str,
+               prefix: str) -> None:
+    vis = QAPISchemaGenGolangVisitor(prefix)
+    schema.visit(vis)
+    vis.write(output_dir)
+
+def generate_marshal_methods_enum(members: List[QAPISchemaEnumMember]) -> str:
+    type = qapi_to_go_type_name(members[0].defined_in, "enum")
+
+    marshal_switch_cases = ""
+    unmarshal_switch_cases = ""
+    for i in range(len(members)):
+        go_type = type + qapi_to_field_name_enum(members[i].name)
+        name = members[i].name
+
+        marshal_switch_cases += f'''
+    case {go_type}:
+        return []byte(`"{name}"`), nil'''
+
+        unmarshal_switch_cases += f'''
+    case "{name}":
+        (*s) = {go_type}'''
+
+    return f'''
+func (s {type}) MarshalJSON() ([]byte, error) {{
+    switch s {{
+{marshal_switch_cases[1:]}
+    default:
+        fmt.Println("Failed to decode {type}", s)
+    }}
+    return nil, errors.New("Failed")
+}}
+
+func (s *{type}) UnmarshalJSON(data []byte) error {{
+    var name string
+
+    if err := json.Unmarshal(data, &name); err != nil {{
+        return err
+    }}
+
+    switch name {{
+{unmarshal_switch_cases[1:]}
+    default:
+        fmt.Println("Failed to decode {type}", *s)
+    }}
+    return nil
+}}
+'''
+
+# Takes the documentation object of a specific type and returns
+# that type's documentation followed by its member's docs.
+def qapi_to_golang_struct_docs(doc: QAPIDoc) -> (str, Dict[str, str]):
+    if doc is None:
+        return "// No documentation available", None
+
+    main = ""
+    if len(doc.body.text) > 0:
+        main = f"// {doc.body.text}".replace("\n", "\n// ")
+
+    for section in doc.sections:
+        # Skip sections that are not useful for Golang consumers
+        if section.name and "TODO" in section.name:
+            continue
+
+        # Small hack to only add // when doc.body.text was empty
+        prefix = "// " if len(main) == 0 else "\n\n"
+        main += f"{prefix}{section.name}: {section.text}".replace("\n", "\n// ")
+
+    fields = {}
+    for key, value in doc.args.items():
+        if len(value.text) > 0:
+            fields[key] = " // " + ' '.join(value.text.replace("\n", " ").split())
+
+    return main, fields
+
+def qapi_to_field_name_enum(name: str) -> str:
+    return name.title().replace("-", "")
+
+def qapi_to_go_type_name(name: str, meta: str) -> str:
+    if name.startswith("q_obj_"):
+        name = name[6:]
+
+    # We want to keep CamelCase for Golang types. We want to avoid removing
+    # already set CameCase names while fixing uppercase ones, eg:
+    # 1) q_obj_SocketAddress_base -> SocketAddressBase
+    # 2) q_obj_WATCHDOG-arg -> WatchdogArg
+    words = [word for word in name.replace("_", "-").split("-")]
+    name = words[0].title() if words[0].islower() or words[0].isupper() else words[0]
+    name += ''.join(word.title() for word in words[1:])
+    return name
diff --git a/scripts/qapi/main.py b/scripts/qapi/main.py
index fc216a53d3..661fb1e091 100644
--- a/scripts/qapi/main.py
+++ b/scripts/qapi/main.py
@@ -15,6 +15,7 @@ 
 from .common import must_match
 from .error import QAPIError
 from .events import gen_events
+from .golang import gen_golang
 from .introspect import gen_introspect
 from .schema import QAPISchema
 from .types import gen_types
@@ -54,6 +55,7 @@  def generate(schema_file: str,
     gen_events(schema, output_dir, prefix)
     gen_introspect(schema, output_dir, prefix, unmask)
 
+    gen_golang(schema, output_dir, prefix)
 
 def main() -> int:
     """