Skip to content

Commit

Permalink
chore: simplify code
Browse files Browse the repository at this point in the history
One code to rule them all.

This code is pretty similar to the one @ldemailly provided for
https://github.com/fortio/safecast

We worked at the same time on the same code, we came to distinct solutions.
His one was better and simpler than mine, so now I'm simply switching to
something highly inspired from his code.

Co-Authored-By: Laurent Demailly <[email protected]>
  • Loading branch information
ccoVeille and ldemailly committed Nov 17, 2024
1 parent 3e5bb6e commit fff831d
Showing 1 changed file with 120 additions and 91 deletions.
211 changes: 120 additions & 91 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,154 +5,183 @@

package safecast

import "math"
import (
"math"
)

// ToInt attempts to convert any [Type] value to an int.
// If the conversion results in a value outside the range of an int,
// an [ErrConversionIssue] error is returned.
func ToInt[T Number](i T) (int, error) {
if err := checkUpperBoundary(i, int(math.MaxInt)); err != nil {
return 0, err
func negative[T Number](t T) bool {
return t < 0
}

func sameSign[T1, T2 Number](a T1, b T2) bool {
return negative(a) == negative(b)
}

func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOut, err error) {
converted = NumOut(orig)

errBoundary := ErrExceedMaximumValue
boundary := getUpperBoundary(converted)
if negative(orig) {
errBoundary = ErrExceedMinimumValue
boundary = getLowerBoundary(converted)
}

if !sameSign(orig, converted) {
return 0, Error{
value: orig,
err: errBoundary,
boundary: boundary,
}
}

base := orig
switch f := any(orig).(type) {
case float64:
base = NumIn(math.Trunc(f))
case float32:
base = NumIn(math.Trunc(float64(f)))
}

if NumIn(converted) == base {
return converted, nil
}

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

func getUpperBoundary(value any) any {
switch value.(type) {
case float64:
return float64(math.MaxFloat64)
case float32:
return float32(math.MaxFloat32)

Check warning on line 62 in conversion.go

View check run for this annotation

Codecov / codecov/patch

conversion.go#L59-L62

Added lines #L59 - L62 were not covered by tests
case int64:
return int64(math.MaxInt64)
case int32:
return int32(math.MaxInt32)
case int16:
return int16(math.MaxInt16)
case int8:
return int8(math.MaxInt8)
case int:
return int(math.MaxInt)
case uint64:
return uint64(math.MaxUint64)
case uint32:
return uint32(math.MaxUint32)
case uint16:
return uint16(math.MaxUint16)
case uint8:
return uint8(math.MaxUint8)
case uint:
return uint(math.MaxUint)
}

return nil

Check warning on line 85 in conversion.go

View check run for this annotation

Codecov / codecov/patch

conversion.go#L85

Added line #L85 was not covered by tests
}

return int(i), nil
func getLowerBoundary(value any) any {
switch value.(type) {
case float64:
return float64(-math.MaxFloat64)
case float32:
return float32(-math.MaxFloat32)

Check warning on line 93 in conversion.go

View check run for this annotation

Codecov / codecov/patch

conversion.go#L90-L93

Added lines #L90 - L93 were not covered by tests
case int64:
return int64(math.MinInt64)
case int32:
return int32(math.MinInt32)
case int16:
return int16(math.MinInt16)
case int8:
return int8(math.MinInt8)
case int:
return int(math.MinInt)
case uint:
return uint(0)
case uint8:
return uint8(0)
case uint16:
return uint16(0)
case uint32:
return uint32(0)
case uint64:
return uint64(0)
}

return 0

Check warning on line 116 in conversion.go

View check run for this annotation

Codecov / codecov/patch

conversion.go#L116

Added line #L116 was not covered by tests
}

// ToInt attempts to convert any [Type] value to an int.
// If the conversion results in a value outside the range of an int,
// an [ErrConversionIssue] error is returned.
func ToInt[T Number](i T) (int, error) {
return convertFromNumber[int](i)
}

// ToUint attempts to convert any [Type] value to an uint.
// If the conversion results in a value outside the range of an uint,
// an [ErrConversionIssue] error is returned.
func ToUint[T Number](i T) (uint, error) {
if err := checkLowerBoundary(i, uint(0)); err != nil {
return 0, err
}

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

return uint(i), nil
return convertFromNumber[uint](i)
}

// ToInt8 attempts to convert any [Type] value to an int8.
// If the conversion results in a value outside the range of an int8,
// an [ErrConversionIssue] error is returned.
func ToInt8[T Number](i T) (int8, error) {
if err := checkUpperBoundary(i, int8(math.MaxInt8)); err != nil {
return 0, err
}

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

return int8(i), nil
return convertFromNumber[int8](i)
}

// ToUint8 attempts to convert any [Type] value to an uint8.
// If the conversion results in a value outside the range of an uint8,
// an [ErrConversionIssue] error is returned.
func ToUint8[T Number](i T) (uint8, error) {
if err := checkLowerBoundary(i, uint8(0)); err != nil {
return 0, err
}

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

return uint8(i), nil
return convertFromNumber[uint8](i)
}

// ToInt16 attempts to convert any [Type] value to an int16.
// If the conversion results in a value outside the range of an int16,
// an [ErrConversionIssue] error is returned.
func ToInt16[T Number](i T) (int16, error) {
if err := checkUpperBoundary(i, int16(math.MaxInt16)); err != nil {
return 0, err
}

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

return int16(i), nil
return convertFromNumber[int16](i)
}

// ToUint16 attempts to convert any [Type] value to an uint16.
// If the conversion results in a value outside the range of an uint16,
// an [ErrConversionIssue] error is returned.
func ToUint16[T Number](i T) (uint16, error) {
if err := checkLowerBoundary(i, uint16(0)); err != nil {
return 0, err
}

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

return uint16(i), nil
return convertFromNumber[uint16](i)
}

// ToInt32 attempts to convert any [Type] value to an int32.
// If the conversion results in a value outside the range of an int32,
// an [ErrConversionIssue] error is returned.
func ToInt32[T Number](i T) (int32, error) {
if err := checkUpperBoundary(i, int32(math.MaxInt32)); err != nil {
return 0, err
}

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

return int32(i), nil
return convertFromNumber[int32](i)
}

// ToUint32 attempts to convert any [Type] value to an uint32.
// If the conversion results in a value outside the range of an uint32,
// an [ErrConversionIssue] error is returned.
func ToUint32[T Number](i T) (uint32, error) {
if err := checkLowerBoundary(i, uint32(0)); err != nil {
return 0, err
}

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

return uint32(i), nil
return convertFromNumber[uint32](i)
}

// ToInt64 attempts to convert any [Type] value to an int64.
// If the conversion results in a value outside the range of an int64,
// an [ErrConversionIssue] error is returned.
func ToInt64[T Number](i T) (int64, error) {
if err := checkLowerBoundary(i, int64(math.MinInt64)); err != nil {
return 0, err
}

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

return int64(i), nil
return convertFromNumber[int64](i)
}

// ToUint64 attempts to convert any [Type] value to an uint64.
// If the conversion results in a value outside the range of an uint64,
// an [ErrConversionIssue] error is returned.
func ToUint64[T Number](i T) (uint64, error) {
if err := checkLowerBoundary(i, uint64(0)); err != nil {
return 0, err
}

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

return uint64(i), nil
return convertFromNumber[uint64](i)
}

0 comments on commit fff831d

Please sign in to comment.