Skip to content

Commit

Permalink
omitzero implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
bradleypeabody committed Oct 26, 2023
1 parent 4c45222 commit 0ce1369
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 13 deletions.
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

0 comments on commit 0ce1369

Please sign in to comment.