Skip to content

Commit

Permalink
Updated error type based on feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrik Lindahl committed Apr 8, 2024
1 parent a3a3081 commit 09b5842
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 70 deletions.
28 changes: 15 additions & 13 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

package uuid

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.
func FromBytes(input []byte) (UUID, error) {
Expand Down Expand Up @@ -59,21 +61,21 @@ func (u *UUID) Parse(s string) error {
case 36: // canonical
case 34, 38:
if s[0] != '{' || s[len(s)-1] != '}' {
return invalidFormatf("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 invalidFormatf("incorrect UUID format in string %q", s[:9])
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, s[:9])
}
s = s[9:]
default:
return invalidFormatf("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 invalidFormatf("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 @@ -85,7 +87,7 @@ func (u *UUID) Parse(s string) error {
v1 := fromHexChar(s[x])
v2 := fromHexChar(s[x+1])
if v1|v2 == 255 {
return invalidFormat()
return ErrInvalidFormat
}
u[i] = (v1 << 4) | v2
}
Expand All @@ -96,7 +98,7 @@ func (u *UUID) Parse(s string) error {
v1 := fromHexChar(s[i])
v2 := fromHexChar(s[i+1])
if v1|v2 == 255 {
return invalidFormat()
return ErrInvalidFormat
}
u[i/2] = (v1 << 4) | v2
}
Expand Down Expand Up @@ -168,20 +170,20 @@ func (u *UUID) UnmarshalText(b []byte) error {
case 36: // canonical
case 34, 38:
if b[0] != '{' || b[len(b)-1] != '}' {
return invalidFormatf("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 invalidFormatf("incorrect UUID format in string %q", b[:9])
return fmt.Errorf("%w %q", ErrIncorrectFormatInString, b[:9])
}
b = b[9:]
default:
return invalidFormatf("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 invalidFormatf("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 @@ -193,7 +195,7 @@ func (u *UUID) UnmarshalText(b []byte) error {
v1 := fromHexChar(b[x])
v2 := fromHexChar(b[x+1])
if v1|v2 == 255 {
return invalidFormat()
return ErrInvalidFormat
}
u[i] = (v1 << 4) | v2
}
Expand All @@ -203,7 +205,7 @@ func (u *UUID) UnmarshalText(b []byte) error {
v1 := fromHexChar(b[i])
v2 := fromHexChar(b[i+1])
if v1|v2 == 255 {
return invalidFormat()
return ErrInvalidFormat
}
u[i/2] = (v1 << 4) | v2
}
Expand All @@ -219,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 invalidFormatf("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
51 changes: 14 additions & 37 deletions error.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
package uuid

import (
"fmt"
type Error string

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:")
)

// ErrUUID is the base error type for errors generated by this package
type ErrUUID struct{}

func (e *ErrUUID) Error() string {
return "uuid"
}

// ErrUUIDInvalidFormat is a specialized error that is returned when there is
// a problem parsing UUID data
type ErrUUIDInvalidFormat struct {
err error
}

func (e *ErrUUIDInvalidFormat) Error() string {
return e.err.Error()
}

func (e *ErrUUIDInvalidFormat) Unwrap() error {
return e.err
func (e Error) Error() string {
return string(e)
}

func (e *ErrUUIDInvalidFormat) Is(target error) bool {
_, ok := target.(*ErrUUIDInvalidFormat)
func (e Error) Is(target error) bool {
_, ok := target.(*Error)
return ok
}

func invalidFormat() error {
return &ErrUUIDInvalidFormat{
err: fmt.Errorf("%w: invalid UUID format", &ErrUUID{}),
}
}

func invalidFormatf(format string, a ...any) error {
formattedError := fmt.Errorf(format, a...)

return &ErrUUIDInvalidFormat{
err: fmt.Errorf("%w: %v", &ErrUUID{}, formattedError),
}
}
174 changes: 160 additions & 14 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,38 @@ 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", &ErrUUID{}, 123),
err: fmt.Errorf("%w sample error: %v", ErrInvalidVersion, 123),
expected: "uuid: sample error: 123",
expectedTarget: &ErrUUID{},
expectedTarget: &e,
},
{
err: invalidFormat(),
err: fmt.Errorf("%w", ErrInvalidFormat),
expected: "uuid: invalid UUID format",
expectedTarget: &ErrUUIDInvalidFormat{},
expectedTarget: ErrInvalidFormat,
},
{
err: invalidFormatf("sample error: %v", 123),
expected: "uuid: sample error: 123",
expectedTarget: &ErrUUIDInvalidFormat{},
},
{
err: fmt.Errorf("uuid error: %w", invalidFormatf("sample error: %v", 123)),
expected: "uuid error: uuid: sample error: 123",
expectedTarget: &ErrUUIDInvalidFormat{},
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) {
if !errors.Is(tc.err, &ErrUUID{}) {
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) {
Expand All @@ -45,10 +43,158 @@ func TestError(t *testing.T) {
if tc.err.Error() != tc.expected {
t.Errorf("expected err.Error() to be '%s' but was '%s'", tc.expected, tc.err.Error())
}
uuidErr := &ErrUUID{}
var uuidErr Error
if !errors.As(tc.err, &uuidErr) {
t.Error("expected errors.As() to work")
}
})
}
}

func TestAllErrorMessages(t *testing.T) {
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) {
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
Original file line number Diff line number Diff line change
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("%w: no HW address found", &ErrUUID{})
return []byte{}, ErrNoHwAddressFound
}

0 comments on commit 09b5842

Please sign in to comment.