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

add option to enforce nil container marshaling as empty containers #352

Merged
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
17 changes: 17 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,23 @@ func (m IndefLengthMode) valid() bool {
return m < maxIndefLengthMode
}

// NilContainersMode specifies how to encode []Type(nil) and map[Key]Type(nil).
type NilContainersMode int

const (
// NullForNil enforces null for []Type(nil)/map[Key]Type(nil).
NullForNil NilContainersMode = iota

// EmptyForNil enforces empty map/list for []Type(nil)/map[Key]Type(nil).
EmptyForNil

maxNilContainersMode
)

func (m NilContainersMode) valid() bool {
return m < maxNilContainersMode
}

// TagsMode specifies whether to allow CBOR tags.
type TagsMode int

Expand Down
14 changes: 11 additions & 3 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ type EncOptions struct {
// IndefLength specifies whether to allow indefinite length CBOR items.
IndefLength IndefLengthMode

// NilContainers specifies how to encode map[Key]Type(nil)/[]Type(nil)
NilContainers NilContainersMode

// TagsMd specifies whether to allow CBOR tags (major type 6).
TagsMd TagsMode
}
Expand Down Expand Up @@ -464,6 +467,9 @@ func (opts EncOptions) encMode() (*encMode, error) {
if !opts.IndefLength.valid() {
return nil, errors.New("cbor: invalid IndefLength " + strconv.Itoa(int(opts.IndefLength)))
}
if !opts.NilContainers.valid() {
return nil, errors.New("cbor: invalid NilContainers " + strconv.Itoa(int(opts.NilContainers)))
}
if !opts.TagsMd.valid() {
return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd)))
}
Expand All @@ -479,6 +485,7 @@ func (opts EncOptions) encMode() (*encMode, error) {
time: opts.Time,
timeTag: opts.TimeTag,
indefLength: opts.IndefLength,
nilContainers: opts.NilContainers,
tagsMd: opts.TagsMd,
}
return &em, nil
Expand All @@ -501,6 +508,7 @@ type encMode struct {
time TimeMode
timeTag EncTagMode
indefLength IndefLengthMode
nilContainers NilContainersMode
tagsMd TagsMode
}

Expand Down Expand Up @@ -787,7 +795,7 @@ func encodeFloat64(e *encoderBuffer, f64 float64) error {

func encodeByteString(e *encoderBuffer, em *encMode, v reflect.Value) error {
vk := v.Kind()
if vk == reflect.Slice && v.IsNil() {
if vk == reflect.Slice && v.IsNil() && em.nilContainers == NullForNil {
e.Write(cborNil)
return nil
}
Expand Down Expand Up @@ -824,7 +832,7 @@ type arrayEncodeFunc struct {
}

func (ae arrayEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
if v.Kind() == reflect.Slice && v.IsNil() {
if v.Kind() == reflect.Slice && v.IsNil() && em.nilContainers == NullForNil {
e.Write(cborNil)
return nil
}
Expand All @@ -849,7 +857,7 @@ type mapEncodeFunc struct {
}

func (me mapEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
if v.IsNil() {
if v.IsNil() && em.nilContainers == NullForNil {
e.Write(cborNil)
return nil
}
Expand Down
45 changes: 45 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2852,6 +2852,50 @@ func TestInvalidInfConvert(t *testing.T) {
}
}

func TestNilContainers(t *testing.T) {
nilContainersNull := EncOptions{NilContainers: NullForNil}
nilContainersEmpty := EncOptions{NilContainers: EmptyForNil}
testCases := []struct {
name string
v interface{}
opts EncOptions
wantCborData []byte
}{
{"map(nil) as null", map[string]string(nil), nilContainersNull, hexDecode("f6")},
{"map(nil) as empty map", map[string]string(nil), nilContainersEmpty, hexDecode("a0")},

{"slice(nil) as null", []int(nil), nilContainersNull, hexDecode("f6")},
{"slice(nil) as empty list", []int(nil), nilContainersEmpty, hexDecode("80")},

{"[]byte(nil) as null", []byte(nil), nilContainersNull, hexDecode("f6")},
{"[]byte(nil) as empty bytestring", []byte(nil), nilContainersEmpty, hexDecode("40")},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
em, err := tc.opts.EncMode()
if err != nil {
t.Errorf("EncMode() returned an error %v", err)
}
b, err := em.Marshal(tc.v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", tc.v, err)
} else if !bytes.Equal(b, tc.wantCborData) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", tc.v, b, tc.wantCborData)
}
})
}
}

func TestInvalidNilContainers(t *testing.T) {
wantErrorMsg := "cbor: invalid NilContainers 100"
_, err := EncOptions{NilContainers: NilContainersMode(100)}.EncMode()
if err == nil {
t.Errorf("EncMode() didn't return an error")
} else if err.Error() != wantErrorMsg {
t.Errorf("EncMode() returned error %q, want %q", err.Error(), wantErrorMsg)
}
}

// Keith Randall's workaround for constant propagation issue https://github.com/golang/go/issues/36400
const (
// qnan 32 bits constants
Expand Down Expand Up @@ -3306,6 +3350,7 @@ func TestEncOptions(t *testing.T) {
Time: TimeRFC3339Nano,
TimeTag: EncTagRequired,
IndefLength: IndefLengthForbidden,
NilContainers: NullForNil,
TagsMd: TagsAllowed,
}
em, err := opts1.EncMode()
Expand Down