1
0
mirror of https://github.com/google/uuid.git synced 2025-11-06 08:59:16 +02:00

Use a custom error type for invalid lengths, replacing fmt.Errorf (#69)

* Add benchmarks for different kinds of invalid UUIDs

Also add a test case for too-short UUIDs to ensure behavior doesn’t
change.

* Use a custom error type for invalid lengths, replacing `fmt.Errorf`

This significantly improves the speed of failed parses due to wrong
lengths. Previously the `fmt.Errorf` call dominated, making this the
most expensive error and more expensive than successfully parsing:

    BenchmarkParse-4                 29226529        36.1 ns/op
    BenchmarkParseBadLength-4         6923106       174 ns/op
    BenchmarkParseLen32Truncated-4   26641954        38.1 ns/op
    BenchmarkParseLen36Corrupted-4   19405598        59.5 ns/op

When the formatting is not required and done on-demand, the failure per
se is much faster:

    BenchmarkParse-4                 29641700        36.3 ns/op
    BenchmarkParseBadLength-4        58602537        20.0 ns/op
    BenchmarkParseLen32Truncated-4   30664791        43.6 ns/op
    BenchmarkParseLen36Corrupted-4   18882410        61.9 ns/op
This commit is contained in:
Joe Wreschnig
2020-12-30 20:35:21 +01:00
committed by GitHub
parent 0e4e311974
commit edef28d0c8
2 changed files with 47 additions and 2 deletions

10
uuid.go
View File

@@ -35,6 +35,12 @@ const (
var rander = rand.Reader // random function
type invalidLengthError struct{ len int }
func (err *invalidLengthError) Error() string {
return fmt.Sprintf("invalid UUID length: %d", err.len)
}
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
@@ -68,7 +74,7 @@ func Parse(s string) (UUID, error) {
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
return uuid, &invalidLengthError{len(s)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
@@ -112,7 +118,7 @@ func ParseBytes(b []byte) (UUID, error) {
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
return uuid, &invalidLengthError{len(b)}
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

View File

@@ -517,6 +517,15 @@ func TestRandomFromReader(t *testing.T) {
}
}
func TestWrongLength(t *testing.T) {
_, err := Parse("12345")
if err == nil {
t.Errorf("expected ‘12345’ was invalid")
} else if err.Error() != "invalid UUID length: 5" {
t.Errorf("expected a different error message for an invalid length")
}
}
var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
var asBytes = []byte(asString)
@@ -595,3 +604,33 @@ func BenchmarkUUID_URN(b *testing.B) {
}
}
}
func BenchmarkParseBadLength(b *testing.B) {
short := asString[:10]
for i := 0; i < b.N; i++ {
_, err := Parse(short)
if err == nil {
b.Fatalf("expected ‘%s’ was invalid", short)
}
}
}
func BenchmarkParseLen32Truncated(b *testing.B) {
partial := asString[:len(asString)-4]
for i := 0; i < b.N; i++ {
_, err := Parse(partial)
if err == nil {
b.Fatalf("expected ‘%s’ was invalid", partial)
}
}
}
func BenchmarkParseLen36Corrupted(b *testing.B) {
wrong := asString[:len(asString)-1] + "x"
for i := 0; i < b.N; i++ {
_, err := Parse(wrong)
if err == nil {
b.Fatalf("expected ‘%s’ was invalid", wrong)
}
}
}