aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Wreschnig <joe.wreschnig@gmail.com>2020-12-30 20:35:21 +0100
committerGitHub <noreply@github.com>2020-12-30 13:35:21 -0600
commitedef28d0c8acc7d3bdfaddde8ab27de602cc131b (patch)
tree31b3c19e592b1f2a127d955cdbb09c5073e4eaa7
parent0e4e31197428a347842d152773b4cace4645ca25 (diff)
downloadgoogle-uuid-edef28d0c8acc7d3bdfaddde8ab27de602cc131b.tar.gz
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
-rw-r--r--uuid.go10
-rw-r--r--uuid_test.go39
2 files changed, 47 insertions, 2 deletions
diff --git a/uuid.go b/uuid.go
index 524404c..daf3639 100644
--- a/uuid.go
+++ b/uuid.go
@@ -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
diff --git a/uuid_test.go b/uuid_test.go
index 4e04e7c..709e34c 100644
--- a/uuid_test.go
+++ b/uuid_test.go
@@ -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)
+ }
+ }
+}