Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

omitzero implementation #334

Merged
merged 1 commit into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions _generated/omitzero.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package _generated

import "time"

//go:generate msgp

// check some specific cases for omitzero

type OmitZero0 struct {
AStruct OmitZeroA `msg:"astruct,omitempty"` // leave this one omitempty
BStruct OmitZeroA `msg:"bstruct,omitzero"` // and compare to this
AStructPtr *OmitZeroA `msg:"astructptr,omitempty"` // a pointer case omitempty
BStructPtr *OmitZeroA `msg:"bstructptr,omitzero"` // a pointer case omitzero
AExt OmitZeroExt `msg:"aext,omitzero"` // external type case
AExtPtr *OmitZeroExtPtr `msg:"aextptr,omitzero"` // external type pointer case

// more
APtrNamedStr *NamedStringOZ `msg:"aptrnamedstr,omitzero"`
ANamedStruct NamedStructOZ `msg:"anamedstruct,omitzero"`
APtrNamedStruct *NamedStructOZ `msg:"aptrnamedstruct,omitzero"`
EmbeddableStruct `msg:",flatten,omitzero"` // embed flat
EmbeddableStructOZ `msg:"embeddablestruct2,omitzero"` // embed non-flat
ATime time.Time `msg:"atime,omitzero"`

OmitZeroTuple OmitZeroTuple `msg:"ozt"` // the inside of a tuple should ignore both omitempty and omitzero
}

type OmitZeroA struct {
A string `msg:"a,omitempty"`
B NamedStringOZ `msg:"b,omitzero"`
C NamedStringOZ `msg:"c,omitzero"`
}

func (o *OmitZeroA) IsZero() bool {
if o == nil {
return true
}
return *o == (OmitZeroA{})
}

type NamedStructOZ struct {
A string `msg:"a,omitempty"`
B string `msg:"b,omitempty"`
}

func (ns *NamedStructOZ) IsZero() bool {
if ns == nil {
return true
}
return *ns == (NamedStructOZ{})
}

type NamedStringOZ string

func (ns *NamedStringOZ) IsZero() bool {
if ns == nil {
return true
}
return *ns == ""
}

type EmbeddableStructOZ struct {
SomeEmbed string `msg:"someembed2,omitempty"`
}

func (es EmbeddableStructOZ) IsZero() bool { return es == (EmbeddableStructOZ{}) }

type EmbeddableStructOZ2 struct {
SomeEmbed2 string `msg:"someembed2,omitempty"`
}

func (es EmbeddableStructOZ2) IsZero() bool { return es == (EmbeddableStructOZ2{}) }

//msgp:tuple OmitZeroTuple

// OmitZeroTuple is flagged for tuple output, it should ignore all omitempty and omitzero functionality
// since it's fundamentally incompatible.
type OmitZeroTuple struct {
FieldA string `msg:"fielda,omitempty"`
FieldB NamedStringOZ `msg:"fieldb,omitzero"`
FieldC NamedStringOZ `msg:"fieldc,omitzero"`
}

type OmitZero1 struct {
T1 OmitZeroTuple `msg:"t1"`
}
114 changes: 114 additions & 0 deletions _generated/omitzero_ext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package _generated

import (
"github.com/tinylib/msgp/msgp"
)

// this has "external" types that will show up
// as generic IDENT during code generation

type OmitZeroExt struct {
a int // custom type
}

// IsZero will return true if a is not positive
func (o OmitZeroExt) IsZero() bool { return o.a <= 0 }

// EncodeMsg implements msgp.Encodable
func (o OmitZeroExt) EncodeMsg(en *msgp.Writer) (err error) {
if o.a > 0 {
return en.WriteInt(o.a)
}
return en.WriteNil()
}

// DecodeMsg implements msgp.Decodable
func (o *OmitZeroExt) DecodeMsg(dc *msgp.Reader) (err error) {
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
return
}
o.a = 0
return
}
o.a, err = dc.ReadInt()
return err
}

// MarshalMsg implements msgp.Marshaler
func (o OmitZeroExt) MarshalMsg(b []byte) (ret []byte, err error) {
ret = msgp.Require(b, o.Msgsize())
if o.a > 0 {
return msgp.AppendInt(ret, o.a), nil
}
return msgp.AppendNil(ret), nil
}

// UnmarshalMsg implements msgp.Unmarshaler
func (o *OmitZeroExt) UnmarshalMsg(bts []byte) (ret []byte, err error) {
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
return bts, err
}
o.a, bts, err = msgp.ReadIntBytes(bts)
return bts, err
}

// Msgsize implements msgp.Msgsizer
func (o OmitZeroExt) Msgsize() (s int) {
return msgp.IntSize
}

type OmitZeroExtPtr struct {
a int // custom type
}

// IsZero will return true if a is nil or not positive
func (o *OmitZeroExtPtr) IsZero() bool { return o == nil || o.a <= 0 }

// EncodeMsg implements msgp.Encodable
func (o *OmitZeroExtPtr) EncodeMsg(en *msgp.Writer) (err error) {
if o.a > 0 {
return en.WriteInt(o.a)
}
return en.WriteNil()
}

// DecodeMsg implements msgp.Decodable
func (o *OmitZeroExtPtr) DecodeMsg(dc *msgp.Reader) (err error) {
if dc.IsNil() {
err = dc.ReadNil()
if err != nil {
return
}
o.a = 0
return
}
o.a, err = dc.ReadInt()
return err
}

// MarshalMsg implements msgp.Marshaler
func (o *OmitZeroExtPtr) MarshalMsg(b []byte) (ret []byte, err error) {
ret = msgp.Require(b, o.Msgsize())
if o.a > 0 {
return msgp.AppendInt(ret, o.a), nil
}
return msgp.AppendNil(ret), nil
}

// UnmarshalMsg implements msgp.Unmarshaler
func (o *OmitZeroExtPtr) UnmarshalMsg(bts []byte) (ret []byte, err error) {
if msgp.IsNil(bts) {
bts, err = msgp.ReadNilBytes(bts)
return bts, err
}
o.a, bts, err = msgp.ReadIntBytes(bts)
return bts, err
}

// Msgsize implements msgp.Msgsizer
func (o *OmitZeroExtPtr) Msgsize() (s int) {
return msgp.IntSize
}
77 changes: 77 additions & 0 deletions _generated/omitzero_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package _generated

import (
"bytes"
"testing"
)

func TestOmitZero(t *testing.T) {

t.Run("OmitZeroExt_not_empty", func(t *testing.T) {

z := OmitZero0{AExt: OmitZeroExt{a: 1}}
b, err := z.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(b, []byte("aext")) {
t.Errorf("expected to find aext in bytes %X", b)
}
z = OmitZero0{}
_, err = z.UnmarshalMsg(b)
if err != nil {
t.Fatal(err)
}
if z.AExt.a != 1 {
t.Errorf("z.AExt.a expected 1 but got %d", z.AExt.a)
}

})

t.Run("OmitZeroExt_negative", func(t *testing.T) {

z := OmitZero0{AExt: OmitZeroExt{a: -1}} // negative value should act as empty, via IsEmpty() call
b, err := z.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(b, []byte("aext")) {
t.Errorf("expected to not find aext in bytes %X", b)
}
z = OmitZero0{}
_, err = z.UnmarshalMsg(b)
if err != nil {
t.Fatal(err)
}
if z.AExt.a != 0 {
t.Errorf("z.AExt.a expected 0 but got %d", z.AExt.a)
}

})

t.Run("OmitZeroTuple", func(t *testing.T) {

// make sure tuple encoding isn't affected by omitempty or omitzero

z := OmitZero0{OmitZeroTuple: OmitZeroTuple{FieldA: "", FieldB: "", FieldC: "fcval"}}
b, err := z.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
// verify the exact binary encoding, that the values follow each other without field names
if !bytes.Contains(b, []byte{0xA0, 0xA0, 0xA5, 'f', 'c', 'v', 'a', 'l'}) {
t.Errorf("failed to find expected bytes in %X", b)
}
z = OmitZero0{}
_, err = z.UnmarshalMsg(b)
if err != nil {
t.Fatal(err)
}
if z.OmitZeroTuple.FieldA != "" ||
z.OmitZeroTuple.FieldB != "" ||
z.OmitZeroTuple.FieldC != "fcval" {
t.Errorf("z.OmitZeroTuple unexpected value: %#v", z.OmitZeroTuple)
}

})
}
6 changes: 4 additions & 2 deletions gen/elem.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,13 @@ type Elem interface {
// This is true for slices and maps.
AllowNil() bool

// IfZeroExpr returns the expression to compare to zero/empty
// for this type. It is meant to be used in an if statement
// IfZeroExpr returns the expression to compare to an empty value
// for this type, per the rules of the `omitempty` feature.
// It is meant to be used in an if statement
// and may include the simple statement form followed by
// semicolon and then the expression.
// Returns "" if zero/empty not supported for this Elem.
// Note that this is NOT used by the `omitzero` feature.
IfZeroExpr() string

hidden()
Expand Down
20 changes: 14 additions & 6 deletions gen/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,13 @@ func (e *encodeGen) structmap(s *Struct) {
}

omitempty := s.AnyHasTagPart("omitempty")
omitzero := s.AnyHasTagPart("omitzero")
var fieldNVar string
if omitempty {
if omitempty || omitzero {

fieldNVar = oeIdentPrefix + "Len"

e.p.printf("\n// omitempty: check for empty values")
e.p.printf("\n// check for omitted fields")
e.p.printf("\n%s := uint32(%d)", fieldNVar, nfields)
e.p.printf("\n%s", bm.typeDecl())
e.p.printf("\n_ = %s", bm.varname)
Expand All @@ -147,6 +148,11 @@ func (e *encodeGen) structmap(s *Struct) {
e.p.printf("\n%s--", fieldNVar)
e.p.printf("\n%s", bm.setStmt(i))
e.p.printf("\n}")
} else if sf.HasTagPart("omitzero") {
e.p.printf("\nif %s.IsZero() {", sf.FieldElem.Varname())
e.p.printf("\n%s--", fieldNVar)
e.p.printf("\n%s", bm.setStmt(i))
e.p.printf("\n}")
}
}

Expand All @@ -164,7 +170,7 @@ func (e *encodeGen) structmap(s *Struct) {

} else {

// non-omitempty version
// non-omit version
data = msgp.AppendMapHeader(nil, uint32(nfields))
e.p.printf("\n// map header, size %d", nfields)
e.Fuse(data)
Expand All @@ -179,10 +185,12 @@ func (e *encodeGen) structmap(s *Struct) {
return
}

// if field is omitempty, wrap with if statement based on the emptymask
oeField := omitempty && s.Fields[i].HasTagPart("omitempty") && s.Fields[i].FieldElem.IfZeroExpr() != ""
// if field is omitempty or omitzero, wrap with if statement based on the emptymask
oeField := (omitempty || omitzero) &&
((s.Fields[i].HasTagPart("omitempty") && s.Fields[i].FieldElem.IfZeroExpr() != "") ||
s.Fields[i].HasTagPart("omitzero"))
if oeField {
e.p.printf("\nif %s == 0 { // if not empty", bm.readExpr(i))
e.p.printf("\nif %s == 0 { // if not omitted", bm.readExpr(i))
}

data = msgp.AppendString(nil, s.Fields[i].FieldTag)
Expand Down
18 changes: 13 additions & 5 deletions gen/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,13 @@ func (m *marshalGen) mapstruct(s *Struct) {
}

omitempty := s.AnyHasTagPart("omitempty")
omitzero := s.AnyHasTagPart("omitzero")
var fieldNVar string
if omitempty {
if omitempty || omitzero {

fieldNVar = oeIdentPrefix + "Len"

m.p.printf("\n// omitempty: check for empty values")
m.p.printf("\n// check for omitted fields")
m.p.printf("\n%s := uint32(%d)", fieldNVar, nfields)
m.p.printf("\n%s", bm.typeDecl())
m.p.printf("\n_ = %s", bm.varname)
Expand All @@ -142,6 +143,11 @@ func (m *marshalGen) mapstruct(s *Struct) {
m.p.printf("\n%s--", fieldNVar)
m.p.printf("\n%s", bm.setStmt(i))
m.p.printf("\n}")
} else if sf.HasTagPart("omitzero") {
m.p.printf("\nif %s.IsZero() {", sf.FieldElem.Varname())
m.p.printf("\n%s--", fieldNVar)
m.p.printf("\n%s", bm.setStmt(i))
m.p.printf("\n}")
}
}

Expand Down Expand Up @@ -174,10 +180,12 @@ func (m *marshalGen) mapstruct(s *Struct) {
return
}

// if field is omitempty, wrap with if statement based on the emptymask
oeField := s.Fields[i].HasTagPart("omitempty") && s.Fields[i].FieldElem.IfZeroExpr() != ""
// if field is omitempty or omitzero, wrap with if statement based on the emptymask
oeField := (omitempty || omitzero) &&
((s.Fields[i].HasTagPart("omitempty") && s.Fields[i].FieldElem.IfZeroExpr() != "") ||
s.Fields[i].HasTagPart("omitzero"))
if oeField {
m.p.printf("\nif %s == 0 { // if not empty", bm.readExpr(i))
m.p.printf("\nif %s == 0 { // if not omitted", bm.readExpr(i))
}

data = msgp.AppendString(nil, s.Fields[i].FieldTag)
Expand Down
Loading