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
diff --git a/uuid.go b/uuid.go
index 524404c..daf3639 100644
--- a/uuid.go
+++ b/uuid.go
@@ -35,6 +35,12 @@
 
 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 @@
 		}
 		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 @@
 		}
 		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 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 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)
+		}
+	}
+}