Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented support for checkable errors #131

Merged
merged 9 commits into from May 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 14 additions & 19 deletions codec.go
Expand Up @@ -21,10 +21,7 @@

package uuid

import (
"errors"
"fmt"
)
import "fmt"

// FromBytes returns a UUID generated from the raw byte slice input.
// It will return an error if the slice isn't 16 bytes long.
Expand All @@ -44,8 +41,6 @@ func FromBytesOrNil(input []byte) UUID {
return uuid
}

var errInvalidFormat = errors.New("uuid: invalid UUID format")

func fromHexChar(c byte) byte {
switch {
case '0' <= c && c <= '9':
Expand All @@ -66,21 +61,21 @@ func (u *UUID) Parse(s string) error {
case 36: // canonical
case 34, 38:
if s[0] != '{' || s[len(s)-1] != '}' {
return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s)
}
s = s[1 : len(s)-1]
case 41, 45:
if s[:9] != "urn:uuid:" {
return fmt.Errorf("uuid: incorrect UUID format in string %q", s[:9])
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s[:9])
}
s = s[9:]
default:
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(s), s)
return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(s), s)
}
// canonical
if len(s) == 36 {
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return fmt.Errorf("uuid: incorrect UUID format in string %q", s)
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s)
}
for i, x := range [16]byte{
0, 2, 4, 6,
Expand All @@ -92,7 +87,7 @@ func (u *UUID) Parse(s string) error {
v1 := fromHexChar(s[x])
v2 := fromHexChar(s[x+1])
if v1|v2 == 255 {
return errInvalidFormat
return ErrInvalidFormat
}
u[i] = (v1 << 4) | v2
}
Expand All @@ -103,7 +98,7 @@ func (u *UUID) Parse(s string) error {
v1 := fromHexChar(s[i])
v2 := fromHexChar(s[i+1])
if v1|v2 == 255 {
return errInvalidFormat
return ErrInvalidFormat
}
u[i/2] = (v1 << 4) | v2
}
Expand Down Expand Up @@ -175,20 +170,20 @@ func (u *UUID) UnmarshalText(b []byte) error {
case 36: // canonical
case 34, 38:
if b[0] != '{' || b[len(b)-1] != '}' {
return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b)
}
b = b[1 : len(b)-1]
case 41, 45:
if string(b[:9]) != "urn:uuid:" {
return fmt.Errorf("uuid: incorrect UUID format in string %q", b[:9])
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b[:9])
}
b = b[9:]
default:
return fmt.Errorf("uuid: incorrect UUID length %d in string %q", len(b), b)
return fmt.Errorf("%w %d in string %q", ErrIncorrectLength, len(b), b)
}
if len(b) == 36 {
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return fmt.Errorf("uuid: incorrect UUID format in string %q", b)
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b)
}
for i, x := range [16]byte{
0, 2, 4, 6,
Expand All @@ -200,7 +195,7 @@ func (u *UUID) UnmarshalText(b []byte) error {
v1 := fromHexChar(b[x])
v2 := fromHexChar(b[x+1])
if v1|v2 == 255 {
return errInvalidFormat
return ErrInvalidFormat
}
u[i] = (v1 << 4) | v2
}
Expand All @@ -210,7 +205,7 @@ func (u *UUID) UnmarshalText(b []byte) error {
v1 := fromHexChar(b[i])
v2 := fromHexChar(b[i+1])
if v1|v2 == 255 {
return errInvalidFormat
return ErrInvalidFormat
}
u[i/2] = (v1 << 4) | v2
}
Expand All @@ -226,7 +221,7 @@ func (u UUID) MarshalBinary() ([]byte, error) {
// It will return an error if the slice isn't 16 bytes long.
func (u *UUID) UnmarshalBinary(data []byte) error {
if len(data) != Size {
return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
return fmt.Errorf("%w, got %d bytes", ErrIncorrectByteLength, len(data))
}
copy(u[:], data)

Expand Down
22 changes: 22 additions & 0 deletions error.go
@@ -0,0 +1,22 @@
package uuid

type Error string
PatrLind marked this conversation as resolved.
Show resolved Hide resolved

const (
ErrInvalidFormat = Error("uuid: invalid UUID format")
ErrIncorrectFormatInString = Error("uuid: incorrect UUID format in string")
ErrIncorrectLength = Error("uuid: incorrect UUID length")
ErrIncorrectByteLength = Error("uuid: UUID must be exactly 16 bytes long")
ErrNoHwAddressFound = Error("uuid: no HW address found")
ErrTypeConvertError = Error("uuid: cannot convert")
ErrInvalidVersion = Error("uuid:")
)

func (e Error) Error() string {
return string(e)
}

func (e Error) Is(target error) bool {
PatrLind marked this conversation as resolved.
Show resolved Hide resolved
_, ok := target.(*Error)
return ok
}
200 changes: 200 additions & 0 deletions error_test.go
@@ -0,0 +1,200 @@
package uuid

import (
"errors"
"fmt"
"net"
"reflect"
"testing"
)

func TestError(t *testing.T) {
var e Error
tcs := []struct {
err error
expected string
expectedTarget error
}{
{
err: fmt.Errorf("%w sample error: %v", ErrInvalidVersion, 123),
expected: "uuid: sample error: 123",
expectedTarget: &e,
},
{
err: fmt.Errorf("%w", ErrInvalidFormat),
expected: "uuid: invalid UUID format",
expectedTarget: ErrInvalidFormat,
},
{
err: fmt.Errorf("%w %q", ErrIncorrectFormatInString, "test"),
expected: "uuid: incorrect UUID format in string \"test\"",
expectedTarget: ErrIncorrectFormatInString,
},
}
for i, tc := range tcs {
t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) {
var e2 Error
if !errors.Is(tc.err, &e2) {
t.Error("expected error to be of a wrapped type of Error")
}
if !errors.Is(tc.err, tc.expectedTarget) {
t.Errorf("expected error to be of type %v, but was %v", reflect.TypeOf(tc.expectedTarget), reflect.TypeOf(tc.err))
}
if tc.err.Error() != tc.expected {
t.Errorf("expected err.Error() to be '%s' but was '%s'", tc.expected, tc.err.Error())
}
var uuidErr Error
if !errors.As(tc.err, &uuidErr) {
dylan-bourque marked this conversation as resolved.
Show resolved Hide resolved
t.Error("expected errors.As() to work")
}
})
}
}

func TestAllErrorMessages(t *testing.T) {
PatrLind marked this conversation as resolved.
Show resolved Hide resolved
tcs := []struct {
function string
uuidStr string
expected string
}{
{ // 34 chars - With brackets
function: "parse",
uuidStr: "..................................",
expected: "uuid: incorrect UUID format in string \"..................................\"",
},
{ // 41 chars - urn:uuid:
function: "parse",
uuidStr: "123456789................................",
expected: "uuid: incorrect UUID format in string \"123456789\"",
},
{ // other
function: "parse",
uuidStr: "....",
expected: "uuid: incorrect UUID length 4 in string \"....\"",
},
{ // 36 chars - canonical, but not correct format
function: "parse",
uuidStr: "....................................",
expected: "uuid: incorrect UUID format in string \"....................................\"",
},
{ // 36 chars - canonical, invalid data
function: "parse",
uuidStr: "xx00ae9e-dae3-459f-ad0e-6b574be3f950",
expected: "uuid: invalid UUID format",
},
{ // Hash like
function: "parse",
uuidStr: "................................",
expected: "uuid: invalid UUID format",
},
{ // Hash like, invalid
function: "parse",
uuidStr: "xx00ae9edae3459fad0e6b574be3f950",
expected: "uuid: invalid UUID format",
},
{ // Hash like, invalid
function: "parse",
uuidStr: "xx00ae9edae3459fad0e6b574be3f950",
expected: "uuid: invalid UUID format",
},
}
for i, tc := range tcs {
t.Run(fmt.Sprintf("Test case %d", i), func(t *testing.T) {
id := UUID{}
err := id.Parse(tc.uuidStr)
if err == nil {
t.Error("expected an error")
return
}
if err.Error() != tc.expected {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected)
}
err = id.UnmarshalText([]byte(tc.uuidStr))
if err == nil {
t.Error("expected an error")
return
}
if err.Error() != tc.expected {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), tc.expected)
}
})
}

// Unmarshal binary
id := UUID{}
b := make([]byte, 33)
expectedErr := "uuid: UUID must be exactly 16 bytes long, got 33 bytes"
err := id.UnmarshalBinary([]byte(b))
if err == nil {
t.Error("expected an error")
return
}
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}

// no hw address error
netInterfaces = func() ([]net.Interface, error) {
PatrLind marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil
}
defer func() {
netInterfaces = net.Interfaces
}()
_, err = defaultHWAddrFunc()
if err == nil {
t.Error("expected an error")
return
}
expectedErr = "uuid: no HW address found"
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}

// scan error
err = id.Scan(123)
if err == nil {
t.Error("expected an error")
return
}
expectedErr = "uuid: cannot convert int to UUID"
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}

// UUId V1 Version
id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e")
_, err = TimestampFromV1(id)
if err == nil {
t.Error("expected an error")
return
}
expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 1"
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}

// UUId V2 Version
id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e")
_, err = TimestampFromV6(id)
if err == nil {
t.Error("expected an error")
return
}
expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 6"
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}

// UUId V7 Version
id = FromStringOrNil("e86160d3-beff-443c-b9b5-1f8197ccb12e")
_, err = TimestampFromV7(id)
if err == nil {
t.Error("expected an error")
return
}
// There is a "bug" in the error message, this should probably be fixed (6 -> 7)
expectedErr = "uuid: e86160d3-beff-443c-b9b5-1f8197ccb12e is version 4, not version 6"
if err.Error() != expectedErr {
t.Errorf("unexpected error '%s' != '%s'", err.Error(), expectedErr)
}
}
3 changes: 1 addition & 2 deletions generator.go
Expand Up @@ -26,7 +26,6 @@ import (
"crypto/rand"
"crypto/sha1"
"encoding/binary"
"fmt"
"hash"
"io"
"net"
Expand Down Expand Up @@ -452,5 +451,5 @@ func defaultHWAddrFunc() (net.HardwareAddr, error) {
return iface.HardwareAddr, nil
}
}
return []byte{}, fmt.Errorf("uuid: no HW address found")
return []byte{}, ErrNoHwAddressFound
}
2 changes: 1 addition & 1 deletion sql.go
Expand Up @@ -56,7 +56,7 @@ func (u *UUID) Scan(src interface{}) error {
return err
}

return fmt.Errorf("uuid: cannot convert %T to UUID", src)
return fmt.Errorf("%w %T to UUID", ErrTypeConvertError, src)
}

// NullUUID can be used with the standard sql package to represent a
Expand Down
6 changes: 3 additions & 3 deletions uuid.go
Expand Up @@ -100,7 +100,7 @@ func (t Timestamp) Time() (time.Time, error) {
// Returns an error if the UUID is any version other than 1.
func TimestampFromV1(u UUID) (Timestamp, error) {
if u.Version() != 1 {
err := fmt.Errorf("uuid: %s is version %d, not version 1", u, u.Version())
err := fmt.Errorf("%w %s is version %d, not version 1", ErrInvalidVersion, u, u.Version())
return 0, err
}

Expand All @@ -121,7 +121,7 @@ func TimestampFromV1(u UUID) (Timestamp, error) {
// releases until the spec is final.
func TimestampFromV6(u UUID) (Timestamp, error) {
if u.Version() != 6 {
return 0, fmt.Errorf("uuid: %s is version %d, not version 6", u, u.Version())
return 0, fmt.Errorf("%w %s is version %d, not version 6", ErrInvalidVersion, u, u.Version())
}

hi := binary.BigEndian.Uint32(u[0:4])
Expand All @@ -141,7 +141,7 @@ func TimestampFromV6(u UUID) (Timestamp, error) {
// releases until the spec is final.
func TimestampFromV7(u UUID) (Timestamp, error) {
if u.Version() != 7 {
return 0, fmt.Errorf("uuid: %s is version %d, not version 6", u, u.Version())
return 0, fmt.Errorf("%w %s is version %d, not version 6", ErrInvalidVersion, u, u.Version())
}

t := 0 |
Expand Down