feat: add error types for better validation (#166)

* feat: add error types

* fix: broken backward compatibility
diff --git a/uuid.go b/uuid.go
index b745bac..dc75cee 100644
--- a/uuid.go
+++ b/uuid.go
@@ -47,18 +47,40 @@
 	poolMu      sync.Mutex
 	poolPos     = randPoolSize     // protected with poolMu
 	pool        [randPoolSize]byte // protected with poolMu
+
+	ErrInvalidUUIDFormat      = errors.New("invalid UUID format")
+	ErrInvalidBracketedFormat = errors.New("invalid bracketed UUID format")
 )
 
+type URNPrefixError struct { prefix string }
+
+func (e URNPrefixError) Error() string {
+	return fmt.Sprintf("invalid urn prefix: %q", e.prefix)
+}
+
+func (e URNPrefixError) Is(target error) bool {
+	_, ok := target.(URNPrefixError)
+	return ok
+}
+
+var ErrInvalidURNPrefix = URNPrefixError{}
+
 type invalidLengthError struct{ len int }
 
 func (err invalidLengthError) Error() string {
 	return fmt.Sprintf("invalid UUID length: %d", err.len)
 }
 
+func (e invalidLengthError) Is(target error) bool {
+	_, ok := target.(invalidLengthError)
+	return ok
+}
+
+var ErrInvalidLength = invalidLengthError{}
+
 // IsInvalidLengthError is matcher function for custom error invalidLengthError
 func IsInvalidLengthError(err error) bool {
-	_, ok := err.(invalidLengthError)
-	return ok
+	return errors.Is(err, ErrInvalidLength)
 }
 
 // Parse decodes s into a UUID or returns an error if it cannot be parsed.  Both
@@ -79,7 +101,7 @@
 	// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 	case 36 + 9:
 		if !strings.EqualFold(s[:9], "urn:uuid:") {
-			return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
+			return uuid, URNPrefixError{s[:9]}
 		}
 		s = s[9:]
 
@@ -93,7 +115,7 @@
 		for i := range uuid {
 			uuid[i], ok = xtob(s[i*2], s[i*2+1])
 			if !ok {
-				return uuid, errors.New("invalid UUID format")
+				return uuid, ErrInvalidUUIDFormat
 			}
 		}
 		return uuid, nil
@@ -103,7 +125,8 @@
 	// s is now at least 36 bytes long
 	// it must be of the form  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 	if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
-		return uuid, errors.New("invalid UUID format")
+		return uuid, ErrInvalidUUIDFormat
+
 	}
 	for i, x := range [16]int{
 		0, 2, 4, 6,
@@ -114,7 +137,7 @@
 	} {
 		v, ok := xtob(s[x], s[x+1])
 		if !ok {
-			return uuid, errors.New("invalid UUID format")
+			return uuid, ErrInvalidUUIDFormat
 		}
 		uuid[i] = v
 	}
@@ -128,7 +151,7 @@
 	case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 	case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 		if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
-			return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
+			return uuid, URNPrefixError{string(b[:9])}
 		}
 		b = b[9:]
 	case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
@@ -138,7 +161,7 @@
 		for i := 0; i < 32; i += 2 {
 			uuid[i/2], ok = xtob(b[i], b[i+1])
 			if !ok {
-				return uuid, errors.New("invalid UUID format")
+				return uuid, ErrInvalidUUIDFormat
 			}
 		}
 		return uuid, nil
@@ -148,7 +171,7 @@
 	// s is now at least 36 bytes long
 	// it must be of the form  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
 	if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
-		return uuid, errors.New("invalid UUID format")
+		return uuid, ErrInvalidUUIDFormat
 	}
 	for i, x := range [16]int{
 		0, 2, 4, 6,
@@ -159,7 +182,7 @@
 	} {
 		v, ok := xtob(b[x], b[x+1])
 		if !ok {
-			return uuid, errors.New("invalid UUID format")
+			return uuid, ErrInvalidUUIDFormat
 		}
 		uuid[i] = v
 	}
@@ -205,14 +228,14 @@
 	// UUID with "urn:uuid:" prefix
 	case 36 + 9:
 		if !strings.EqualFold(s[:9], "urn:uuid:") {
-			return fmt.Errorf("invalid urn prefix: %q", s[:9])
+			return URNPrefixError{s[:9]}
 		}
 		s = s[9:]
 
 	// UUID enclosed in braces
 	case 36 + 2:
 		if s[0] != '{' || s[len(s)-1] != '}' {
-			return fmt.Errorf("invalid bracketed UUID format")
+			return ErrInvalidBracketedFormat
 		}
 		s = s[1 : len(s)-1]
 
@@ -221,7 +244,7 @@
 		for i := 0; i < len(s); i += 2 {
 			_, ok := xtob(s[i], s[i+1])
 			if !ok {
-				return errors.New("invalid UUID format")
+				return ErrInvalidUUIDFormat
 			}
 		}
 
@@ -232,11 +255,11 @@
 	// Check for standard UUID format
 	if len(s) == 36 {
 		if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
-			return errors.New("invalid UUID format")
+			return ErrInvalidUUIDFormat
 		}
 		for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
 			if _, ok := xtob(s[x], s[x+1]); !ok {
-				return errors.New("invalid UUID format")
+				return ErrInvalidUUIDFormat
 			}
 		}
 	}
diff --git a/uuid_test.go b/uuid_test.go
index d848382..906ecbe 100644
--- a/uuid_test.go
+++ b/uuid_test.go
@@ -569,7 +569,7 @@
 func TestIsWrongLength(t *testing.T) {
 	_, err := Parse("12345")
 	if !IsInvalidLengthError(err) {
-		t.Errorf("expected error type is invalidLengthError")
+		t.Errorf("IsInvalidLength returned incorrect type %T", err)
 	}
 }