Skip to content

Commit

Permalink
feat: add meaningful error messages
Browse files Browse the repository at this point in the history
Fixes #15
  • Loading branch information
ccoVeille committed Sep 8, 2024
1 parent e05207d commit e374726
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 30 deletions.
30 changes: 20 additions & 10 deletions asserters.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package safecast

import "fmt"

func assertNotNegative[T Type](i T) error {
func assertNotNegative[T Type, T2 Type](i T, zero T2) error {
if i < 0 {
return fmt.Errorf("%w: %v is negative", ErrIntegerOverflow, i)
return Error{
err: ErrExceedLowerBoundary,
value: i,
boundary: zero,
}
}
return nil
}

func checkUpperBoundary[T Type](value T, boundary uint64) error {
func checkUpperBoundary[T Type, T2 Type](value T, boundary T2) error {
if value <= 0 {
return nil
}
Expand All @@ -21,17 +23,21 @@ func checkUpperBoundary[T Type](value T, boundary uint64) error {
case float64:
bigger = float64(f) >= float64(boundary)
default:
bigger = uint64(value) > boundary
bigger = uint64(value) > uint64(boundary)
}

if bigger {
return fmt.Errorf("%w: %v is greater than %v", ErrIntegerOverflow, value, boundary)
return Error{
value: value,
boundary: boundary,
err: ErrExceedUpperBoundary,
}
}

return nil
}

func checkLowerBoundary[T Type](value T, boundary int64) error {
func checkLowerBoundary[T Type, T2 Type](value T, boundary T2) error {
if value >= 0 {
return nil
}
Expand All @@ -43,11 +49,15 @@ func checkLowerBoundary[T Type](value T, boundary int64) error {
case float64:
smaller = float64(f) <= float64(boundary)
default:
smaller = int64(value) < boundary
smaller = int64(value) < int64(boundary)
}

if smaller {
return fmt.Errorf("%w: %v is lower than %v", ErrIntegerOverflow, value, boundary)
return Error{
value: value,
boundary: boundary,
err: ErrExceedLowerBoundary,
}
}

return nil
Expand Down
36 changes: 18 additions & 18 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import "math"
// If the conversion results in a value outside the range of an int,
// an ErrIntegerOverflow error is returned.
func ToInt[T Type](i T) (int, error) {
if err := checkUpperBoundary(i, math.MaxInt); err != nil {
if err := checkUpperBoundary(i, int(math.MaxInt)); err != nil {
return 0, err
}

if err := checkLowerBoundary(i, math.MinInt); err != nil {
if err := checkLowerBoundary(i, int(math.MinInt)); err != nil {
return 0, err
}

Expand All @@ -25,11 +25,11 @@ func ToInt[T Type](i T) (int, error) {
// If the conversion results in a value outside the range of an uint,
// an ErrIntegerOverflow error is returned.
func ToUint[T Type](i T) (uint, error) {
if err := assertNotNegative(i); err != nil {
if err := assertNotNegative(i, uint(0)); err != nil {
return 0, err
}

if err := checkUpperBoundary(i, math.MaxUint64); err != nil {
if err := checkUpperBoundary(i, uint(math.MaxUint)); err != nil {
return 0, err
}

Expand All @@ -40,11 +40,11 @@ func ToUint[T Type](i T) (uint, error) {
// If the conversion results in a value outside the range of an int8,
// an ErrIntegerOverflow error is returned.
func ToInt8[T Type](i T) (int8, error) {
if err := checkUpperBoundary(i, math.MaxInt8); err != nil {
if err := checkUpperBoundary(i, int8(math.MaxInt8)); err != nil {
return 0, err
}

if err := checkLowerBoundary(i, math.MinInt8); err != nil {
if err := checkLowerBoundary(i, int8(math.MinInt8)); err != nil {
return 0, err
}

Expand All @@ -55,11 +55,11 @@ func ToInt8[T Type](i T) (int8, error) {
// If the conversion results in a value outside the range of an uint8,
// an ErrIntegerOverflow error is returned.
func ToUint8[T Type](i T) (uint8, error) {
if err := assertNotNegative(i); err != nil {
if err := assertNotNegative(i, uint8(0)); err != nil {
return 0, err
}

if err := checkUpperBoundary(i, math.MaxUint8); err != nil {
if err := checkUpperBoundary(i, uint8(math.MaxUint8)); err != nil {
return 0, err
}

Expand All @@ -70,11 +70,11 @@ func ToUint8[T Type](i T) (uint8, error) {
// If the conversion results in a value outside the range of an int16,
// an ErrIntegerOverflow error is returned.
func ToInt16[T Type](i T) (int16, error) {
if err := checkUpperBoundary(i, math.MaxInt16); err != nil {
if err := checkUpperBoundary(i, int16(math.MaxInt16)); err != nil {
return 0, err
}

if err := checkLowerBoundary(i, math.MinInt16); err != nil {
if err := checkLowerBoundary(i, int16(math.MinInt16)); err != nil {
return 0, err
}

Expand All @@ -85,11 +85,11 @@ func ToInt16[T Type](i T) (int16, error) {
// If the conversion results in a value outside the range of an uint16,
// an ErrIntegerOverflow error is returned.
func ToUint16[T Type](i T) (uint16, error) {
if err := assertNotNegative(i); err != nil {
if err := assertNotNegative(i, uint16(0)); err != nil {
return 0, err
}

if err := checkUpperBoundary(i, math.MaxUint16); err != nil {
if err := checkUpperBoundary(i, uint16(math.MaxUint16)); err != nil {
return 0, err
}

Expand All @@ -100,11 +100,11 @@ func ToUint16[T Type](i T) (uint16, error) {
// If the conversion results in a value outside the range of an int32,
// an ErrIntegerOverflow error is returned.
func ToInt32[T Type](i T) (int32, error) {
if err := checkUpperBoundary(i, math.MaxInt32); err != nil {
if err := checkUpperBoundary(i, int32(math.MaxInt32)); err != nil {
return 0, err
}

if err := checkLowerBoundary(i, math.MinInt32); err != nil {
if err := checkLowerBoundary(i, int32(math.MinInt32)); err != nil {
return 0, err
}

Expand All @@ -115,11 +115,11 @@ func ToInt32[T Type](i T) (int32, error) {
// If the conversion results in a value outside the range of an uint32,
// an ErrIntegerOverflow error is returned.
func ToUint32[T Type](i T) (uint32, error) {
if err := assertNotNegative(i); err != nil {
if err := assertNotNegative(i, uint32(0)); err != nil {
return 0, err
}

if err := checkUpperBoundary(i, math.MaxUint32); err != nil {
if err := checkUpperBoundary(i, uint32(math.MaxUint32)); err != nil {
return 0, err
}

Expand All @@ -130,7 +130,7 @@ func ToUint32[T Type](i T) (uint32, error) {
// If the conversion results in a value outside the range of an int64,
// an ErrIntegerOverflow error is returned.
func ToInt64[T Type](i T) (int64, error) {
if err := checkUpperBoundary(i, math.MaxInt64); err != nil {
if err := checkUpperBoundary(i, int64(math.MaxInt64)); err != nil {
return 0, err
}

Expand All @@ -141,7 +141,7 @@ func ToInt64[T Type](i T) (int64, error) {
// If the conversion results in a value outside the range of an uint64,
// an ErrIntegerOverflow error is returned.
func ToUint64[T Type](i T) (uint64, error) {
if err := assertNotNegative(i); err != nil {
if err := assertNotNegative(i, uint64(0)); err != nil {
return 0, err
}

Expand Down
25 changes: 25 additions & 0 deletions conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package safecast_test
import (
"errors"
"math"
"strings"
"testing"

"github.com/ccoveille/go-safecast"
Expand Down Expand Up @@ -32,6 +33,16 @@ func requireError(t *testing.T, err error) {
}
}

func requireErrorContains(t *testing.T, err error, text string) {
t.Helper()
requireError(t, err)

errMessage := err.Error()
if !strings.Contains(errMessage, text) {
t.Fatalf("error message should contain %q: %q", text, errMessage)
}
}

func assertNoError(t *testing.T, err error) {
t.Helper()

Expand Down Expand Up @@ -205,6 +216,20 @@ func TestToInt8(t *testing.T) {
})
}

func TestErrorMessage(t *testing.T) {
_, err := safecast.ToUint8(-1)
requireErrorContains(t, err, "lower boundary")
requireErrorContains(t, err, "than 0 (uint8)")

_, err = safecast.ToUint8(math.MaxInt16)
requireErrorContains(t, err, "upper boundary")
requireErrorContains(t, err, "than 255 (uint8)")

_, err = safecast.ToInt8(-math.MaxInt16)
requireErrorContains(t, err, "lower boundary")
requireErrorContains(t, err, "than -128 (int8)")
}

type caseUint8[in safecast.Type] struct {
name string
input in
Expand Down
41 changes: 39 additions & 2 deletions errors.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
package safecast

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

var ErrIntegerOverflow = errors.New("integer overflow")
var (
ErrIntegerOverflow = errors.New("integer overflow")
ErrExceedUpperBoundary = errors.New("exceed upper boundary for this type")
ErrExceedLowerBoundary = errors.New("exceed lower boundary for this type")
)

type Error struct {
value any
boundary any
err error
}

func (e Error) Error() string {
errMessage := ErrIntegerOverflow.Error()

switch {
case errors.Is(e.err, ErrExceedUpperBoundary):
errMessage = fmt.Sprintf("%s: %v (%T) is greater than %v (%T)", errMessage, e.value, e.value, e.boundary, e.boundary)
case errors.Is(e.err, ErrExceedLowerBoundary):
errMessage = fmt.Sprintf("%s: %v (%T) is smaller than %v (%T)", errMessage, e.value, e.value, e.boundary, e.boundary)
}

if e.err != nil {
errMessage = fmt.Sprintf("%s: %s", errMessage, e.err.Error())
}
return errMessage
}

func (e Error) Unwrap() []error {
errs := []error{ErrIntegerOverflow}
if e.err != nil {
errs = append(errs, e.err)
}
return errs
}

0 comments on commit e374726

Please sign in to comment.