Skip to content

Commit edef28d

Browse files
authored
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
1 parent 0e4e311 commit edef28d

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

uuid.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ const (
3535

3636
var rander = rand.Reader // random function
3737

38+
type invalidLengthError struct{ len int }
39+
40+
func (err *invalidLengthError) Error() string {
41+
return fmt.Sprintf("invalid UUID length: %d", err.len)
42+
}
43+
3844
// Parse decodes s into a UUID or returns an error. Both the standard UUID
3945
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
4046
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
@@ -68,7 +74,7 @@ func Parse(s string) (UUID, error) {
6874
}
6975
return uuid, nil
7076
default:
71-
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
77+
return uuid, &invalidLengthError{len(s)}
7278
}
7379
// s is now at least 36 bytes long
7480
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
@@ -112,7 +118,7 @@ func ParseBytes(b []byte) (UUID, error) {
112118
}
113119
return uuid, nil
114120
default:
115-
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
121+
return uuid, &invalidLengthError{len(b)}
116122
}
117123
// s is now at least 36 bytes long
118124
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

uuid_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,15 @@ func TestRandomFromReader(t *testing.T) {
517517
}
518518
}
519519

520+
func TestWrongLength(t *testing.T) {
521+
_, err := Parse("12345")
522+
if err == nil {
523+
t.Errorf("expected ‘12345’ was invalid")
524+
} else if err.Error() != "invalid UUID length: 5" {
525+
t.Errorf("expected a different error message for an invalid length")
526+
}
527+
}
528+
520529
var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
521530
var asBytes = []byte(asString)
522531

@@ -595,3 +604,33 @@ func BenchmarkUUID_URN(b *testing.B) {
595604
}
596605
}
597606
}
607+
608+
func BenchmarkParseBadLength(b *testing.B) {
609+
short := asString[:10]
610+
for i := 0; i < b.N; i++ {
611+
_, err := Parse(short)
612+
if err == nil {
613+
b.Fatalf("expected ‘%s’ was invalid", short)
614+
}
615+
}
616+
}
617+
618+
func BenchmarkParseLen32Truncated(b *testing.B) {
619+
partial := asString[:len(asString)-4]
620+
for i := 0; i < b.N; i++ {
621+
_, err := Parse(partial)
622+
if err == nil {
623+
b.Fatalf("expected ‘%s’ was invalid", partial)
624+
}
625+
}
626+
}
627+
628+
func BenchmarkParseLen36Corrupted(b *testing.B) {
629+
wrong := asString[:len(asString)-1] + "x"
630+
for i := 0; i < b.N; i++ {
631+
_, err := Parse(wrong)
632+
if err == nil {
633+
b.Fatalf("expected ‘%s’ was invalid", wrong)
634+
}
635+
}
636+
}

0 commit comments

Comments
 (0)