Hi, On Mon, Nov 06, 2023 at 07:28:04AM -0800, Andrea Bolognani wrote: > On Mon, Oct 16, 2023 at 05:26:57PM +0200, Victor Toso wrote: > > This patch handles QAPI alternate types and generates data structures > > in Go that handles it. > > > > Alternate types are similar to Union but without a discriminator that > > can be used to identify the underlying value on the wire. It is needed > > to infer it. In Go, most of the types [*] are mapped as optional > > fields and Marshal and Unmarshal methods will be handling the data > > checks. > > > > Example: > > > > qapi: > > | { 'alternate': 'BlockdevRef', > > | 'data': { 'definition': 'BlockdevOptions', > > | 'reference': 'str' } } > > > > go: > > | type BlockdevRef struct { > > | Definition *BlockdevOptions > > | Reference *string > > | } > > > > usage: > > | input := `{"driver":"qcow2","data-file":"/some/place/my-image"}` > > | k := BlockdevRef{} > > | err := json.Unmarshal([]byte(input), &k) > > | if err != nil { > > | panic(err) > > | } > > | // *k.Definition.Qcow2.DataFile.Reference == "/some/place/my-image" > > > > [*] The exception for optional fields as default is to Types that can > > accept JSON Null as a value. For this case, we translate NULL to a > > member type called IsNull, which is boolean in Go. This will be > > explained better in the documentation patch of this series but the > > main rationale is around Marshaling to and from JSON and Go data > > structures. > > These usage examples are great; in fact, I think they're too good to > be relegated to the commit messages. I would like to see them in the > actual documentation. > > At the same time, the current documentation seems to focus a lot on > internals rather than usage. I think we really need two documents > here: > > * one for users of the library, with lots of usage examples > (ideally at least one for JSON->Go and one for Go->JSON for each > class of QAPI object) and little-to-no peeking behind the > curtains; > > * one for QEMU developers / QAPI maintainers, which goes into > detail regarding the internals and explains the various design > choices and trade-offs. > > Some parts of the latter should probably be code comments instead. A > reasonable balance will have to be found. > > > diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py > > +TEMPLATE_HELPER = """ > > +// 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 > > +func StrictDecode(into interface{}, from []byte) error { > > +\tdec := json.NewDecoder(strings.NewReader(string(from))) > > +\tdec.DisallowUnknownFields() > > + > > +\tif err := dec.Decode(into); err != nil { > > +\t\treturn err > > +\t} > > +\treturn nil > > +} > > +""" > > I think the use of \t here makes things a lot less readable. Can't > you just do > > TEMPLATE_HELPER = """ > func StrictDecode() { > dec := ... > if err := ... { > return err > } > return nil > } > """ > > I would actually recommend the use of textwrap.dedent() to make > things less awkward: > > TEMPLATE_HELPER = textwrap.dedent(""" > func StrictDecode() { > dec := ... > if err := ... { > return err > } > return nil > } > """ > > This is particularly useful for blocks of Go code that are not > declared at the top level... > > > + unmarshal_check_fields = f""" > > +\t// Check for json-null first > > +\tif string(data) == "null" {{ > > +\t\treturn errors.New(`null not supported for {name}`) > > +\t}}""" > > ... such as this one, which could be turned into: > > unmarshal_check_fields = textwrap.dedent(f""" > // Check for json-null first > if string(data) == "null" {{ > return errors.New(`null not supported for {name}`) > }} > """) > > Much more manageable, don't you think? :)
Didn't know this one, I agree 100% that is nicer. I'll take a look for the next iteration if the output would still be similar as I want to gofmt change be zero (see bellow). > > On a partially related note: while I haven't yet looked closely at > how much effort you've dedicated to producing pretty output, from a > quick look at generate_struct_type() it seems that the answer is "not > zero". I think it would be fine to simplify things there and produce > ugly output, under the assumption that gofmt will be called on the > generated code immediately afterwards. The C generator doesn't have > this luxury, but we should take advantage of it. Yes, I wholeheartedly agree. The idea of the generator producing a well formatted Go code came from previous review. I didn't want to introduce gofmt and friends to QEMU build system, perhaps it wasn't a big deal but I find it foreign to QEMU for a generated code that QEMU itself would not use. See: https://lists.gnu.org/archive/html/qemu-devel/2023-10/msg01188.html Cheers, Victor
signature.asc
Description: PGP signature