forked from TrueCloudLab/restic
restic: Make JSON unmarshal for ID more efficient
This commit reduces several allocations in UnmarshalJSON() by decoding the hex string directly in a single step.
This commit is contained in:
parent
2022355800
commit
203d775190
2 changed files with 64 additions and 6 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
@ -101,13 +102,33 @@ func (id ID) MarshalJSON() ([]byte, error) {
|
|||
|
||||
// UnmarshalJSON parses the JSON-encoded data and stores the result in id.
|
||||
func (id *ID) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(b, &s)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unmarshal")
|
||||
// check string length
|
||||
if len(b) < 2 {
|
||||
return fmt.Errorf("invalid ID: %q", b)
|
||||
}
|
||||
|
||||
_, err = hex.Decode(id[:], []byte(s))
|
||||
if len(b)%2 != 0 {
|
||||
return fmt.Errorf("invalid ID length: %q", b)
|
||||
}
|
||||
|
||||
// check string delimiters
|
||||
if b[0] != '"' && b[0] != '\'' {
|
||||
return fmt.Errorf("invalid start of string: %q", b[0])
|
||||
}
|
||||
|
||||
last := len(b) - 1
|
||||
if b[0] != b[last] {
|
||||
return fmt.Errorf("starting string delimiter (%q) does not match end (%q)", b[0], b[last])
|
||||
}
|
||||
|
||||
// strip JSON string delimiters
|
||||
b = b[1:last]
|
||||
|
||||
if len(b) != 2*len(id) {
|
||||
return fmt.Errorf("invalid length for ID")
|
||||
}
|
||||
|
||||
_, err := hex.Decode(id[:], b)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "hex.Decode")
|
||||
}
|
||||
|
|
|
@ -51,10 +51,47 @@ func TestID(t *testing.T) {
|
|||
var id3 ID
|
||||
err = id3.UnmarshalJSON(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("error for %q: %v", buf, err)
|
||||
}
|
||||
if !reflect.DeepEqual(id, id3) {
|
||||
t.Error("ids are not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDUnmarshal(t *testing.T) {
|
||||
var tests = []struct {
|
||||
s string
|
||||
valid bool
|
||||
}{
|
||||
{`"`, false},
|
||||
{`""`, false},
|
||||
{`'`, false},
|
||||
{`"`, false},
|
||||
{`"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4"`, false},
|
||||
{`"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f"`, false},
|
||||
{`"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"`, true},
|
||||
}
|
||||
|
||||
wantID, err := ParseID("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
id := &ID{}
|
||||
err := id.UnmarshalJSON([]byte(test.s))
|
||||
if test.valid && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !test.valid && err == nil {
|
||||
t.Fatalf("want error for invalid value, got nil")
|
||||
}
|
||||
|
||||
if test.valid && !id.Equal(wantID) {
|
||||
t.Fatalf("wrong ID returned, want %s, got %s", wantID, id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue