lxdrgen-go: improve usability
Alpine 3.20 Success Details

Turning union tags into read-only methods of actual types:
 - eliminates duplicated JSON unmarshalling of tags,
 - makes AppendTo/ConsumeFrom symmetrical in nature,
 - eliminates duplicated AppendTo code,
 - eliminates trivial AppendTo methods for subtypes without fields,
 - gives us an opportunity to use a more specific interface than "any"
   (the type being anonymous is an acknowledged inconvenience).

Implementing our own json.Marshalers some time ago
(for performance reasons) has made this easier to implement.

Also rename "Interface" fields to more suitable "Variant".
This commit is contained in:
Přemysl Eric Janouch 2024-11-07 08:57:28 +01:00
parent 49d7cb12bb
commit aacf1b1d47
Signed by: p
GPG Key ID: A0420B94F92B9493
1 changed files with 29 additions and 21 deletions

View File

@ -1,6 +1,6 @@
# lxdrgen-go.awk: Go backend for lxdrgen.awk. # lxdrgen-go.awk: Go backend for lxdrgen.awk.
# #
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name> # Copyright (c) 2022 - 2024, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD # SPDX-License-Identifier: 0BSD
# #
# This backend also enables proxying to other endpoints using JSON. # This backend also enables proxying to other endpoints using JSON.
@ -301,9 +301,11 @@ function codegen_marshal(type, f, marshal) {
"\t}\n" "\t}\n"
} }
function codegen_struct_field_marshal(d, cg, camel, f, marshal) { function codegen_struct_field_marshal(d, cg, isaccessor, camel, f, marshal) {
camel = snaketocamel(d["name"]) camel = snaketocamel(d["name"])
f = "s." camel f = "s." camel
if (isaccessor)
f = f "()"
if (!d["isarray"]) { if (!d["isarray"]) {
append(cg, "marshal", append(cg, "marshal",
"\tb = append(b, `,\"" decapitalize(camel) "\":`...)\n" \ "\tb = append(b, `,\"" decapitalize(camel) "\":`...)\n" \
@ -333,7 +335,7 @@ function codegen_struct_field_marshal(d, cg, camel, f, marshal) {
} }
function codegen_struct_field(d, cg, camel, f, serialize, deserialize) { function codegen_struct_field(d, cg, camel, f, serialize, deserialize) {
codegen_struct_field_marshal(d, cg) codegen_struct_field_marshal(d, cg, 0)
camel = snaketocamel(d["name"]) camel = snaketocamel(d["name"])
f = "s." camel f = "s." camel
@ -384,15 +386,11 @@ function codegen_struct_field(d, cg, camel, f, serialize, deserialize) {
} }
} }
function codegen_struct_tag(d, cg, camel, f) { function codegen_struct_tag(d, cg) {
codegen_struct_field_marshal(d, cg) codegen_struct_field_marshal(d, cg, 1)
camel = snaketocamel(d["name"]) # Do not serialize or deserialize here,
f = "s." camel # that is already done by the containing union.
append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \
" `json:\"" decapitalize(camel) "\"`\n")
append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
# Do not deserialize here, that is already done by the containing union.
} }
function codegen_struct(name, cg, gotype) { function codegen_struct(name, cg, gotype) {
@ -450,13 +448,18 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
structname = name snaketocamel(casename) structname = name snaketocamel(casename)
codegen_struct(structname, scg) codegen_struct(structname, scg)
init = CodegenGoType[structname] "{" cg["tagname"] \ print "func (u *" CodegenGoType[structname] ") " cg["tagname"] "() " \
": " decapitalize(cg["tagname"]) "}" CodegenGoType[cg["tagtype"]] " {"
print "\treturn " CodegenGoType[cg["tagtype"]] snaketocamel(casename)
print "}"
print ""
init = CodegenGoType[structname] "{}"
append(cg, "unmarshal", append(cg, "unmarshal",
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ "\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
"\t\ts := " init "\n" \ "\t\ts := " init "\n" \
"\t\terr = json.Unmarshal(data, &s)\n" \ "\t\terr = json.Unmarshal(data, &s)\n" \
"\t\tu.Interface = &s\n") "\t\tu.Variant = &s\n")
append(cg, "serialize", append(cg, "serialize",
"\tcase *" CodegenGoType[structname] ":\n" \ "\tcase *" CodegenGoType[structname] ":\n" \
indent(sprintf(CodegenSerialize[structname], "union"))) indent(sprintf(CodegenSerialize[structname], "union")))
@ -464,20 +467,23 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ "\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
"\t\ts := " init "\n" \ "\t\ts := " init "\n" \
indent(sprintf(CodegenDeserialize[structname], "s")) \ indent(sprintf(CodegenDeserialize[structname], "s")) \
"\t\tu.Interface = &s\n") "\t\tu.Variant = &s\n")
} }
function codegen_union(name, cg, exhaustive, gotype, tagvar) { function codegen_union(name, cg, exhaustive, gotype, tagvar) {
gotype = PrefixCamel name gotype = PrefixCamel name
# This must be a struct, so that UnmarshalJSON can create concrete types.
print "type " gotype " struct {" print "type " gotype " struct {"
print "\tInterface any" print "\tVariant interface {"
print "\t\t" cg["tagname"] "() " CodegenGoType[cg["tagtype"]]
print "\t}"
print "}" print "}"
print "" print ""
# This cannot be a pointer method, it wouldn't work recursively. # This cannot be a pointer method, it wouldn't work recursively.
CodegenIsMarshaler[name] = 1 CodegenIsMarshaler[name] = 1
print "func (u " gotype ") MarshalJSON() ([]byte, error) {" print "func (u " gotype ") MarshalJSON() ([]byte, error) {"
print "\treturn u.Interface.(json.Marshaler).MarshalJSON()" print "\treturn u.Variant.(json.Marshaler).MarshalJSON()"
print "}" print "}"
print "" print ""
@ -499,13 +505,15 @@ function codegen_union(name, cg, exhaustive, gotype, tagvar) {
print "}" print "}"
print "" print ""
# XXX: Consider changing the interface into an AppendTo/ConsumeFrom one, # XXX: Consider rather testing the type for having an AppendTo method,
# that would eliminate these type case switches entirely. # which would eliminate this type case switch entirely.
# On the other hand, it would make it possible to send unsuitable structs.
print "func (u *" gotype ") AppendTo(data []byte) ([]byte, bool) {" print "func (u *" gotype ") AppendTo(data []byte) ([]byte, bool) {"
print "\tok := true" print "\tok := true"
print "\tswitch union := u.Interface.(type) {" print sprintf(CodegenSerialize[cg["tagtype"]],
"u.Variant." cg["tagname"] "()") \
"\tswitch union := u.Variant.(type) {"
print cg["serialize"] "\tdefault:" print cg["serialize"] "\tdefault:"
print "\t\t_ = union"
print "\t\treturn nil, false" print "\t\treturn nil, false"
print "\t}" print "\t}"
print "\treturn data, ok" print "\treturn data, ok"