From: Victor Toso <victortoso@redhat.com>
To: Andrea Bolognani <abologna@redhat.com>
Cc: "Daniel P. Berrangé" <berrange@redhat.com>,
qemu-devel@nongnu.org, "Eric Blake" <eblake@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"John Snow" <jsnow@redhat.com>
Subject: Re: [RFC PATCH v2 4/8] qapi: golang: Generate qapi's union types in Go
Date: Mon, 22 Aug 2022 08:33:21 +0200 [thread overview]
Message-ID: <20220822063321.msxpxbvq5o7l4hat@tapioca> (raw)
In-Reply-To: <CABJz62Msu=vCqWPF6mtREQph8F_aQA-EmC8bm8ez8-AeEiv+OA@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 5350 bytes --]
Hi,
On Fri, Aug 19, 2022 at 10:25:26AM -0500, Andrea Bolognani wrote:
> > func (s QCryptoBlockOpenOptions) MarshalJSON() ([]byte, error) {
> > var bytes []byte
> > var err error
> > if s.Qcow != nil {
> > tmp := struct {
> > QCryptoBlockOptionsQCow
> > Discriminator string `json:"format"`
> > }{
> > QCryptoBlockOptionsQCow: *s.Qcow,
> > Discriminator: "qcow",
> > }
> > if bytes, err = json.Marshal(tmp); err != nil {
> > return nil, err
> > }
> > }
> > if s.Luks != nil {
> > if len(bytes) != 0 {
> > return nil, errors.New(`multiple fields set for QCryptoBlockOpenOptions`)
> > }
>
> Why are you checking this here? I would just have a check like
>
> if s.Qcow != nil && s.Luks != nil {
> return nil, errors.New(`multiple fields set for QCryptoBlockOpenOptions`)
> }
>
> at the top of the function, so that you can abort early and
> cheaply if the user has provided invalid input instead of
> having to wait after one of the fields has already been
> serialized.
In general, I too prefer to return early! So I agree with you
that it is nicer. At the same time, I was thinking a bit from the
code generator point of view and the overall expected diffs when
generating new code. More below.
> > tmp := struct {
> > QCryptoBlockOptionsLUKS
> > Discriminator string `json:"format"`
> > }{
> > QCryptoBlockOptionsLUKS: *s.Luks,
> > Discriminator: "luks",
> > }
> > if bytes, err = json.Marshal(tmp); err != nil {
> > return nil, err
> > }
> > }
> > if len(bytes) == 0 {
> > return nil, errors.New(`null not supported for QCryptoBlockOpenOptions`)
> > }
>
> Similarly, this should be checked early. So the complete check
> could be turned into
>
> if (s.Qcow != nil && s.Luks != nil) || (s.Qcow == nil && s.Luks == nil) {
> return nil, errors.New("must set exactly one field")
> }
Main problem with this approach is that there is some big unions
like BlockdevOptions, this means that we would have to repeat all
fields by the number fields times (40+ in BlockdevOptions' case).
> You should have enough information to produce such a check when
> generating the function, right?
We do!
> If making sure all possible combinations are covered up ends up
> being too complicated, something
> like
>
> var setFields int
> if s.Field1 != nil {
> setFields += 1
> }
> if s.Field2 != nil {
> setFields += 1
> }
> // ...
> if setFields != 1 {
> return nil, errors.New("must set exactly one field")
> }
>
> would also work.
I started with this approach actually but then I realized that we
can just keep the if checks and instead of counter, do check the
variable bytes []byte. So, do you think that the counter is
actually preferred instead of inner check? I don't mind changing
it.
> > func (s *QCryptoBlockOpenOptions) UnmarshalJSON(data []byte) error {
> > tmp := struct {
> > Discriminator string `json:"format"`
> > }{}
> > if err := json.Unmarshal(data, &tmp); err != nil {
> > return err
> > }
> > switch tmp.Discriminator {
> > case "qcow":
> > s.Qcow = &QCryptoBlockOptionsQCow{}
> > if err := json.Unmarshal(data, s.Qcow); err != nil {
> > s.Qcow = nil
> > return err
> > }
> > case "luks":
> > s.Luks = &QCryptoBlockOptionsLUKS{}
> > if err := json.Unmarshal(data, s.Luks); err != nil {
> > s.Luks = nil
> > return err
> > }
> > }
> > return nil
> > }
>
> Alternatively, you could define a struct that covers all
> possible fields and deserialize everything in one go, then copy
> the various parts over to the appropriate struct:
>
> func (s *QCryptoBlockOpenOptions) UnmarshalJSON(data []byte) error {
> tmp := struct {
> Discriminator string `json:"format"`
> KeySecret *string `json:"key-secret,omitempty"`
> }{}
> if err := json.Unmarshal(data, &tmp); err != nil {
> return err
> }
> switch tmp.Discriminator {
> case "qcow":
> s.Qcow = &QCryptoBlockOptionsQCow{}
> s.Qcow.KeySecret = tmp.KeySecret
> case "luks":
> s.Luks = &QCryptoBlockOptionsLUKS{}
> s.Luks.KeySecret = tmp.KeySecret
I think this one adds a bit more complexity and I'm not convinced
that the gains are worth.
For example, with types that differ over fields with the same
name? We would need to move that inside the anonymous struct
somehow;
For example,if Luks's KeySecret was KeyScretType we would have to
use KeySecretLuks *KeySecretType. Still, both of them would
likely be inside of "key-secret" json object (so, same key for
two different fields, not really sure how that would work out)
> }
> return nil
> }
>
> Honestly I'm unclear on which option is better. Parsing the
> JSON twice, as you're doing in your version, could be
> problematic when the document is large; on the other hand, my
> approach will probably result in more copies of the same data
> being created.
It does sound nice to parse it once but if we really want to do
that, we would need to work with Golang's JSON decoder [0]. IMHO,
parsing twice with in-memory data might be okay for the moment
while we are trying to keep things simple and later we could
benchmark against a custom decoder [0] in the future.
[0] https://pkg.go.dev/encoding/json#Decoder
Cheers,
Victor
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2022-08-22 6:34 UTC|newest]
Thread overview: 53+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-06-17 12:19 [RFC PATCH v2 0/8] qapi: add generator for Golang interface Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 1/8] qapi: golang: Generate qapi's enum types in Go Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 2/8] qapi: golang: Generate qapi's alternate " Victor Toso
2022-07-05 15:45 ` Andrea Bolognani
2022-08-17 14:04 ` Victor Toso
2022-08-19 16:27 ` Andrea Bolognani
2022-08-22 6:59 ` Victor Toso
2022-08-29 11:27 ` Markus Armbruster
2022-08-29 13:31 ` Victor Toso
2022-09-02 14:49 ` Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 3/8] qapi: golang: Generate qapi's struct " Victor Toso
2022-06-17 14:41 ` Daniel P. Berrangé
2022-06-17 15:23 ` Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 4/8] qapi: golang: Generate qapi's union " Victor Toso
2022-07-05 15:45 ` Andrea Bolognani
2022-07-05 16:35 ` Daniel P. Berrangé
2022-07-06 9:28 ` Andrea Bolognani
2022-07-06 9:37 ` Daniel P. Berrangé
2022-07-06 9:48 ` Daniel P. Berrangé
2022-07-06 12:20 ` Andrea Bolognani
2022-08-17 16:25 ` Victor Toso
2022-08-19 7:20 ` Victor Toso
2022-08-19 15:25 ` Andrea Bolognani
2022-08-22 6:33 ` Victor Toso [this message]
2022-08-17 16:06 ` Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 5/8] qapi: golang: Generate qapi's event " Victor Toso
2022-07-05 15:45 ` Andrea Bolognani
2022-07-05 16:47 ` Daniel P. Berrangé
2022-07-06 14:53 ` Andrea Bolognani
2022-07-06 15:07 ` Daniel P. Berrangé
2022-07-06 16:22 ` Andrea Bolognani
2022-08-18 7:55 ` Victor Toso
2022-08-18 7:47 ` Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 6/8] qapi: golang: Generate qapi's command " Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 7/8] qapi: golang: Add CommandResult type to Go Victor Toso
2022-07-05 15:46 ` Andrea Bolognani
2022-07-05 16:49 ` Daniel P. Berrangé
2022-08-17 15:00 ` Victor Toso
2022-06-17 12:19 ` [RFC PATCH v2 8/8] qapi: golang: document skip function visit_array_types Victor Toso
2022-06-27 7:15 ` [RFC PATCH v2 0/8] qapi: add generator for Golang interface Markus Armbruster
2022-06-27 12:48 ` Victor Toso
2022-06-27 15:29 ` Markus Armbruster
2022-08-18 8:04 ` Victor Toso
2022-07-05 15:46 ` Andrea Bolognani
2022-08-17 14:24 ` Victor Toso
2022-08-29 11:53 ` Markus Armbruster
2022-08-29 14:05 ` Victor Toso
2024-11-07 10:43 ` Daniel P. Berrangé
2024-11-07 12:36 ` Markus Armbruster
2024-11-07 13:06 ` Daniel P. Berrangé
2024-11-07 13:35 ` Daniel P. Berrangé
2024-11-07 14:18 ` Markus Armbruster
2024-11-08 9:43 ` Victor Toso
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220822063321.msxpxbvq5o7l4hat@tapioca \
--to=victortoso@redhat.com \
--cc=abologna@redhat.com \
--cc=armbru@redhat.com \
--cc=berrange@redhat.com \
--cc=eblake@redhat.com \
--cc=jsnow@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).