Skip to content

Commit 39453ac

Browse files
Merge pull request digitalocean#152 from digitalocean/handle_json_null
handle json null for QMP API generation
2 parents aef5f55 + 6a898e3 commit 39453ac

File tree

7 files changed

+19275
-9769
lines changed

7 files changed

+19275
-9769
lines changed

internal/qmp-gen/main.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ func Generate() error {
4242

4343
// Write out the concatenated spec.
4444
var spec bytes.Buffer
45+
46+
// First add a comment with the best guess of version
47+
spec.WriteString("\n##QEMU SPECIFICATION VERSION: ")
48+
spec.WriteString(tryGetVersionFromSpecPath(*inputSpec))
49+
spec.WriteString("\n\n")
50+
4551
for _, def := range defs {
4652
spec.WriteString(def.Docstring)
4753
spec.WriteByte('\n')
@@ -68,9 +74,6 @@ func Generate() error {
6874
ioutil.WriteFile(*outputGo, bs, 0640)
6975
return err
7076
}
71-
if err = ioutil.WriteFile(*outputGo, formatted, 0640); err != nil {
72-
return err
73-
}
7477

75-
return nil
78+
return ioutil.WriteFile(*outputGo, formatted, 0640)
7679
}

internal/qmp-gen/templates/alternate

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ type {{ $basename }} interface {
2424
{{- range $suffix, $type := .Options }}
2525
{{ $subname := printf "%s%s" $basename $suffix.Go }}
2626

27-
// {{ $subname }} is an implementation of {{ $basename }}
28-
type {{ $subname }} {{ $type.Go }}
27+
{{- if $type.NullType }}
28+
// {{ $subname }} is a JSON null type, so it must
29+
// also implement the isNullable interface.
30+
type {{ $subname }} struct {}
31+
func ({{ $subname }}) isNull() bool { return true }
32+
{{- else }}
33+
// {{ $subname }} is an implementation of {{ $basename }}
34+
type {{ $subname }} {{ $type.Go }}
35+
{{- end }}
2936

3037
{{- if eq (typeOf (index API $type)) "flatunion" }}
3138
{{ $u := index API $type }}
@@ -38,6 +45,23 @@ type {{ $basename }} interface {
3845
{{- end }}
3946

4047
func decode{{ $basename }}(bs json.RawMessage) ({{ $basename }}, error) {
48+
{{/*
49+
Unfortunately, we have to range through the types three times in order
50+
for us to do the type discovery via unmarshalling in the correct order
51+
*/}}
52+
{{- range $suffix, $type := .Options }}
53+
{{- if $type.NullType }}
54+
// Always try unmarshalling for nil first if it's an option
55+
// because other types could unmarshal successfully in the case
56+
// where a Null json type was provided.
57+
var {{ $suffix }} *int
58+
if err := json.Unmarshal([]byte(bs), &{{ $suffix }}); err == nil {
59+
if {{ $suffix }} == nil {
60+
return {{ printf "%s%s" $basename $suffix.Go }}{}, nil
61+
}
62+
}
63+
{{- end }}
64+
{{- end }}
4165
{{- range $suffix, $type := .Options }}
4266
{{- if $type.SimpleType }}
4367
var {{ $suffix }} {{ printf "%s%s" $basename $suffix.Go }}
@@ -47,22 +71,24 @@ func decode{{ $basename }}(bs json.RawMessage) ({{ $basename }}, error) {
4771
{{- end }}
4872
{{- end }}
4973
{{- range $suffix, $type := .Options }}
50-
{{- if not $type.SimpleType }}
51-
{{ $subtype := index API $type }}
52-
{{- if eq (typeOf $subtype) "flatunion" }}
53-
if {{ $suffix }}, err := decode{{ $type.Go }}([]byte(bs)); err == nil {
54-
switch impl := {{ $suffix }}.(type) {
55-
{{- range $suffix, $type := $subtype.Options }}
56-
case {{ $subtype.Name.Go }}{{ $suffix.Go }}:
57-
return impl, nil
58-
{{- end }}
74+
{{- if not $type.NullType }}
75+
{{- if not $type.SimpleType }}
76+
{{ $subtype := index API $type }}
77+
{{- if eq (typeOf $subtype) "flatunion" }}
78+
if {{ $suffix }}, err := decode{{ $type.Go }}([]byte(bs)); err == nil {
79+
switch impl := {{ $suffix }}.(type) {
80+
{{- range $suffix, $type := $subtype.Options }}
81+
case {{ $subtype.Name.Go }}{{ $suffix.Go }}:
82+
return impl, nil
83+
{{- end }}
84+
}
5985
}
60-
}
61-
{{- else }}
62-
var {{ $suffix }} {{ printf "%s%s" $basename $suffix.Go }}
63-
if err := json.Unmarshal([]byte(bs), &{{ $suffix }}); err == nil {
64-
return {{ $suffix }}, nil
65-
}
86+
{{- else }}
87+
var {{ $suffix }} {{ printf "%s%s" $basename $suffix.Go }}
88+
if err := json.Unmarshal([]byte(bs), &{{ $suffix }}); err == nil {
89+
return {{ $suffix }}, nil
90+
}
91+
{{- end }}
6692
{{- end }}
6793
{{- end }}
6894
{{- end }}

internal/qmp-gen/templates/main

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import (
2121
"fmt"
2222
)
2323

24+
// IsNullable is implemented by any
25+
// JSON null type
26+
type IsNullable interface {
27+
isNull() bool
28+
}
29+
2430
{{ range . }}
2531
{{ render . }}
2632
{{ end }}

internal/qmp-gen/types.go

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,40 @@ type definition struct {
3333
JSON []byte
3434
}
3535

36+
// definitions that are safe to ignore when a docstring is missing.
37+
func ignoreWithoutDocstring(buf []byte) bool {
38+
switch {
39+
case bytes.HasPrefix(buf, []byte("{ 'pragma'")):
40+
return true
41+
case bytes.HasPrefix(buf, []byte("{ 'include'")):
42+
return true
43+
}
44+
45+
return false
46+
}
47+
48+
func importDefinitions(path string, jsonBuf []byte) ([]definition, error) {
49+
v := struct {
50+
I string `json:"include"`
51+
}{}
52+
53+
if err := json.Unmarshal(jsonBuf, &v); err != nil {
54+
return nil, fmt.Errorf("failed to unmarshal include %q json: %s", path, err)
55+
}
56+
57+
incPath, err := resolvePath(path, v.I)
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to resolve include %q relative to %q: %s", v.I, path, err)
60+
}
61+
62+
subdefs, err := readDefinitions(incPath)
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to parse included file %q: %s", incPath, err)
65+
}
66+
67+
return subdefs, nil
68+
}
69+
3670
// readDefinitions reads the definitions from a QAPI spec file.
3771
//
3872
// Includes are processed, so the returned definitions is the full API
@@ -51,32 +85,49 @@ func readDefinitions(path string) ([]definition, error) {
5185
// input so that we can process it with less gymnastics.
5286
bs = bytes.Replace(bs, []byte("}\n##"), []byte("}\n\n##"), -1)
5387

88+
// As mentioned directly above, most of the spec includes two newlines
89+
// between definitions. This normalizes stacked include statements.
90+
bs = bytes.Replace(bs, []byte("}\n{ 'include'"), []byte("}\n\n{ 'include'"), -1)
91+
5492
for _, part := range bytes.Split(bs, []byte("\n\n")) {
93+
if len(part) < 1 {
94+
continue
95+
}
96+
5597
fs := bytes.SplitN(part, []byte("\n{"), 2)
5698
switch len(fs) {
5799
default:
58100
return nil, fmt.Errorf("unexpected part of spec file %q: %s", path, string(part))
59101
case 1:
60-
if len(fs) == 1 && part[0] == '{' && !bytes.HasPrefix(part, []byte("{ 'pragma'")) {
102+
if len(fs) == 1 && part[0] == '{' && !ignoreWithoutDocstring(part) {
61103
return nil, fmt.Errorf("found type definition without a docstring in %q: %s", path, string(part))
62104
}
105+
106+
// handle 'include' when no docstring is present
107+
if bytes.HasPrefix(fs[0], []byte("{ 'include'")) {
108+
js := pyToJSON(fs[0])
109+
110+
subdefs, err := importDefinitions(path, js)
111+
if err != nil {
112+
return nil, err
113+
114+
}
115+
116+
ret = append(ret, subdefs...)
117+
}
118+
63119
// This part looks like a non-docstring comment, just skip it.
64120
case 2:
65121
docstring := string(fs[0])
66122
js := pyToJSON(append([]byte{'{'}, fs[1]...))
67123

68-
v := struct {
69-
I string `json:"include"`
70-
}{}
71-
if err = json.Unmarshal(js, &v); err == nil && v.I != "" {
72-
incPath, err := resolvePath(path, v.I)
73-
if err != nil {
74-
return nil, fmt.Errorf("failed to resolve include %q relative to %q: %s", v.I, path, err)
75-
}
76-
subdefs, err := readDefinitions(incPath)
124+
if bytes.HasPrefix(fs[0], []byte("{ 'include'")) {
125+
subdefs, err := importDefinitions(path, js)
77126
if err != nil {
78-
return nil, fmt.Errorf("failed to parse included file %q: %s", incPath, err)
127+
return nil, err
128+
79129
}
130+
80131
ret = append(ret, subdefs...)
81132
} else {
82133
ret = append(ret, definition{docstring, js})
@@ -424,6 +475,13 @@ func (n name) SimpleType() bool {
424475
return ok
425476
}
426477

478+
func (n name) NullType() bool {
479+
if n.SimpleType() {
480+
return false
481+
}
482+
return strings.EqualFold(string(n), "Null")
483+
}
484+
427485
func (n name) InterfaceType(api map[name]interface{}) bool {
428486
if n.SimpleType() {
429487
return false
@@ -460,6 +518,7 @@ var upperWords = map[string]bool{
460518
"fd": true,
461519
"ftp": true,
462520
"ftps": true,
521+
"guid": true,
463522
"http": true,
464523
"https": true,
465524
"id": true,
@@ -476,11 +535,13 @@ var upperWords = map[string]bool{
476535
"qmp": true,
477536
"ram": true,
478537
"sparc": true,
538+
"ssh": true,
479539
"tcp": true,
480540
"tls": true,
481541
"tpm": true,
482542
"ttl": true,
483543
"udp": true,
544+
"uri": true,
484545
"uuid": true,
485546
"vm": true,
486547
"vmdk": true,
@@ -527,6 +588,18 @@ func pyToJSON(py []byte) []byte {
527588
return ret
528589
}
529590

591+
func tryGetVersionFromSpecPath(specPath string) string {
592+
retVersion := "UNKNOWN"
593+
verPath, err := resolvePath(specPath, "VERSION")
594+
if err == nil {
595+
verBuf, err := getQAPI(verPath)
596+
if err == nil {
597+
return string(verBuf)
598+
}
599+
}
600+
return retVersion
601+
}
602+
530603
func resolvePath(orig, new string) (string, error) {
531604
u, err := url.Parse(orig)
532605
if err != nil {

qemu/domain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (d *Domain) Commands() ([]string, error) {
6666
if err != nil {
6767
return nil, err
6868
}
69-
69+
7070
// flatten response
7171
cmds := make([]string, 0, len(commands))
7272
for _, c := range commands {

0 commit comments

Comments
 (0)