Skip to content

Commit

Permalink
Merge pull request #6 from bugst/binary_encoding
Browse files Browse the repository at this point in the history
Add binary encoding support
  • Loading branch information
cmaglie authored May 28, 2022
2 parents 2f82020 + 3d72585 commit ea6e052
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
118 changes: 118 additions & 0 deletions binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2018-2022 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package semver

import (
"bytes"
"encoding/binary"
)

func marshalByteArray(b []byte) []byte {
l := len(b)
res := make([]byte, l+4)
binary.BigEndian.PutUint32(res, uint32(l))
copy(res[4:], b)
return res
}

func marshalInt(i int) []byte {
res := make([]byte, 4)
binary.BigEndian.PutUint32(res, uint32(i))
return res
}

// MarshalBinary implements binary custom encoding
func (v *Version) MarshalBinary() ([]byte, error) {
res := new(bytes.Buffer)
_, _ = res.Write(marshalByteArray(v.major))
_, _ = res.Write(marshalByteArray(v.minor))
_, _ = res.Write(marshalByteArray(v.patch))
_, _ = res.Write(marshalInt(len(v.prerelases)))
for _, pre := range v.prerelases {
_, _ = res.Write(marshalByteArray(pre))
}
_, _ = res.Write(marshalInt(len(v.numericPrereleases)))
for _, npre := range v.numericPrereleases {
v := []byte{0}
if npre {
v[0] = 1
}
_, _ = res.Write(v)
}
_, _ = res.Write(marshalInt(len(v.builds)))
for _, build := range v.builds {
_, _ = res.Write(marshalByteArray(build))
}
return res.Bytes(), nil
}

func decodeArray(data []byte) ([]byte, []byte) {
l, data := int(binary.BigEndian.Uint32(data)), data[4:]
return data[:l], data[l:]
}

func decodeInt(data []byte) (int, []byte) {
return int(binary.BigEndian.Uint32(data)), data[4:]
}

// UnmarshalJSON implements binary custom decoding
func (v *Version) UnmarshalBinary(data []byte) error {
var buff []byte

v.major, data = decodeArray(data)
v.minor, data = decodeArray(data)
v.patch, data = decodeArray(data)
n, data := decodeInt(data)
v.prerelases = nil
for i := 0; i < n; i++ {
buff, data = decodeArray(data)
v.prerelases = append(v.prerelases, buff)
}
v.numericPrereleases = nil
n, data = decodeInt(data)
for i := 0; i < n; i++ {
num := false
if data[0] == 1 {
num = true
}
v.numericPrereleases = append(v.numericPrereleases, num)
data = data[1:]
}
v.builds = nil
n, data = decodeInt(data)
for i := 0; i < n; i++ {
buff, data = decodeArray(data)
v.builds = append(v.builds, buff)
}
return nil
}

// MarshalBinary implements json.Marshaler
func (v *RelaxedVersion) MarshalBinary() ([]byte, error) {
res := new(bytes.Buffer)
if len(v.customversion) > 0 {
_, _ = res.Write([]byte{0})
_, _ = res.Write(marshalByteArray(v.customversion))
return res.Bytes(), nil
}
res.Write([]byte{1})
d, _ := v.version.MarshalBinary() // can't fail
_, _ = res.Write(d)
return res.Bytes(), nil
}

// UnmarshalBinary implements json.Unmarshaler
func (v *RelaxedVersion) UnmarshalBinary(data []byte) error {
if data[0] == 0 {
v.customversion, _ = decodeArray(data[1:])
v.version = nil
return nil
}

v.customversion = nil
v.version = &Version{}
return v.version.UnmarshalBinary(data[1:])
}
77 changes: 77 additions & 0 deletions binary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2018-2022 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package semver

import (
"bytes"
"encoding/gob"
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

func TestGOBEncoderVersion(t *testing.T) {
testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9"

v, err := Parse(testVersion)
require.NoError(t, err)
dumpV := fmt.Sprintf("%s,%s,%s,%s,%v,%s", v.major, v.minor, v.patch, v.prerelases, v.numericPrereleases, v.builds)
require.Equal(t, "1,2,3,[aaa 4 5 6],[false true true true],[bbb 7 8 9]", dumpV)
require.Equal(t, testVersion, v.String())

dataV := new(bytes.Buffer)
err = gob.NewEncoder(dataV).Encode(v)
require.NoError(t, err)

var u Version
err = gob.NewDecoder(dataV).Decode(&u)
require.NoError(t, err)
dumpU := fmt.Sprintf("%s,%s,%s,%s,%v,%s", u.major, u.minor, u.patch, u.prerelases, u.numericPrereleases, u.builds)

require.Equal(t, dumpV, dumpU)
require.Equal(t, testVersion, u.String())
}

func TestGOBEncoderRelaxedVersion(t *testing.T) {
check := func(testVersion string) {
v := ParseRelaxed(testVersion)

dataV := new(bytes.Buffer)
err := gob.NewEncoder(dataV).Encode(v)
require.NoError(t, err)

var u RelaxedVersion
err = gob.NewDecoder(dataV).Decode(&u)
require.NoError(t, err)

require.Equal(t, testVersion, u.String())
}
check("1.2.3-aaa.4.5.6+bbb.7.8.9")
check("asdasdasd-1.2.3-aaa.4.5.6+bbb.7.8.9")
}

func BenchmarkBinaryDecoding(b *testing.B) {
testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9"
v := MustParse(testVersion)

data, _ := v.MarshalBinary()
var u Version
for i := 0; i < b.N; i++ {
u.UnmarshalBinary(data)
}
}

func BenchmarkBinaryDecodingRelaxed(b *testing.B) {
testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9"
v := ParseRelaxed(testVersion)

data, _ := v.MarshalBinary()
var u RelaxedVersion
for i := 0; i < b.N; i++ {
u.UnmarshalBinary(data)
}
}
20 changes: 20 additions & 0 deletions json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,23 @@ func TestJSONParseRelaxedVersion(t *testing.T) {
err = json.Unmarshal([]byte(`123`), &u)
require.Error(t, err)
}

func BenchmarkJSONDecoding(b *testing.B) {
testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9"
v, _ := Parse(testVersion)
data, _ := json.Marshal(v)
var u Version
for i := 0; i < b.N; i++ {
json.Unmarshal(data, &u)
}
}

func BenchmarkJSONDecodingRelaxed(b *testing.B) {
testVersion := "1.2.3-aaa.4.5.6+bbb.7.8.9"
v := ParseRelaxed(testVersion)
data, _ := json.Marshal(v)
var u RelaxedVersion
for i := 0; i < b.N; i++ {
json.Unmarshal(data, &u)
}
}

0 comments on commit ea6e052

Please sign in to comment.