mirror of
https://github.com/aykhans/go-utils.git
synced 2025-11-29 14:59:50 +00:00
Compare commits
4 Commits
v1.0.1
...
70b1a8300f
| Author | SHA1 | Date | |
|---|---|---|---|
| 70b1a8300f | |||
| c29e082426 | |||
| 82197ac9b9 | |||
| d1ea7874fe |
58
README.md
58
README.md
@@ -22,6 +22,20 @@ num := 42
|
|||||||
ptr := common.ToPtr(num) // *int
|
ptr := common.ToPtr(num) // *int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**IsNilOrZero** - Check if a pointer is nil or points to a zero value
|
||||||
|
```go
|
||||||
|
import "github.com/aykhans/go-utils/common"
|
||||||
|
|
||||||
|
var ptr *int
|
||||||
|
common.IsNilOrZero(ptr) // true (nil pointer)
|
||||||
|
|
||||||
|
num := 0
|
||||||
|
common.IsNilOrZero(&num) // true (zero value)
|
||||||
|
|
||||||
|
num = 42
|
||||||
|
common.IsNilOrZero(&num) // false (non-zero value)
|
||||||
|
```
|
||||||
|
|
||||||
### parser
|
### parser
|
||||||
|
|
||||||
String parsing utilities with generic type support.
|
String parsing utilities with generic type support.
|
||||||
@@ -94,6 +108,27 @@ maps.UpdateMap(&old, new)
|
|||||||
// old is now: {"a": 1, "b": 3, "c": 4}
|
// old is now: {"a": 1, "b": 3, "c": 4}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### number
|
||||||
|
|
||||||
|
Number utility functions.
|
||||||
|
|
||||||
|
**NumLen** - Calculate the number of digits in an integer
|
||||||
|
```go
|
||||||
|
import "github.com/aykhans/go-utils/number"
|
||||||
|
|
||||||
|
number.NumLen(42) // returns 2
|
||||||
|
number.NumLen(-128) // returns 3
|
||||||
|
number.NumLen(0) // returns 1
|
||||||
|
number.NumLen(1000) // returns 4
|
||||||
|
|
||||||
|
// Works with all integer types
|
||||||
|
number.NumLen(int8(99)) // returns 2
|
||||||
|
number.NumLen(int64(123456789)) // returns 9
|
||||||
|
number.NumLen(uint32(4294967295)) // returns 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported types: `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `uintptr`
|
||||||
|
|
||||||
### errors
|
### errors
|
||||||
|
|
||||||
Advanced error handling utilities.
|
Advanced error handling utilities.
|
||||||
@@ -121,6 +156,29 @@ result := errors.HandleErrorOrDie(err,
|
|||||||
) // Panics if err doesn't match any handler
|
) // Panics if err doesn't match any handler
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**HandleErrorOrDefault** - Handle errors with a default fallback
|
||||||
|
```go
|
||||||
|
result := errors.HandleErrorOrDefault(err,
|
||||||
|
func(e error) error {
|
||||||
|
// Default handler for unmatched errors
|
||||||
|
return fmt.Errorf("unexpected error: %w", e)
|
||||||
|
},
|
||||||
|
errors.OnSentinelError(context.Canceled, func(e error) error {
|
||||||
|
return fmt.Errorf("operation canceled")
|
||||||
|
}),
|
||||||
|
errors.OnCustomError(func(e *ValidationError) error {
|
||||||
|
return fmt.Errorf("validation failed: %w", e)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pass nil to suppress unmatched errors
|
||||||
|
result := errors.HandleErrorOrDefault(err, nil,
|
||||||
|
errors.OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handled")
|
||||||
|
}),
|
||||||
|
) // Returns nil for unmatched errors
|
||||||
|
```
|
||||||
|
|
||||||
**OnSentinelError** - Create matcher for sentinel errors (like `io.EOF`)
|
**OnSentinelError** - Create matcher for sentinel errors (like `io.EOF`)
|
||||||
```go
|
```go
|
||||||
matcher := errors.OnSentinelError(io.EOF, func(e error) error {
|
matcher := errors.OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ tasks:
|
|||||||
|
|
||||||
tidy: go mod tidy {{.CLI_ARGS}}
|
tidy: go mod tidy {{.CLI_ARGS}}
|
||||||
|
|
||||||
test: go test ./... {{.CLI_ARGS}}
|
test: go test -race ./... {{.CLI_ARGS}}
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
desc: Run linters
|
desc: Run linters
|
||||||
|
|||||||
10
common/compare.go
Normal file
10
common/compare.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
func IsNilOrZero[T comparable](value *T) bool {
|
||||||
|
if value == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var zero T
|
||||||
|
return *value == zero
|
||||||
|
}
|
||||||
350
common/compare_test.go
Normal file
350
common/compare_test.go
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsNilOrZero(t *testing.T) {
|
||||||
|
t.Run("nil pointer returns true", func(t *testing.T) {
|
||||||
|
var intPtr *int
|
||||||
|
assert.True(t, IsNilOrZero(intPtr))
|
||||||
|
|
||||||
|
var strPtr *string
|
||||||
|
assert.True(t, IsNilOrZero(strPtr))
|
||||||
|
|
||||||
|
var boolPtr *bool
|
||||||
|
assert.True(t, IsNilOrZero(boolPtr))
|
||||||
|
|
||||||
|
var floatPtr *float64
|
||||||
|
assert.True(t, IsNilOrZero(floatPtr))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to zero value returns true", func(t *testing.T) {
|
||||||
|
t.Run("int zero", func(t *testing.T) {
|
||||||
|
val := 0
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("string zero", func(t *testing.T) {
|
||||||
|
val := ""
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bool zero", func(t *testing.T) {
|
||||||
|
val := false
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float64 zero", func(t *testing.T) {
|
||||||
|
val := 0.0
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint zero", func(t *testing.T) {
|
||||||
|
val := uint(0)
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to non-zero value returns false", func(t *testing.T) {
|
||||||
|
t.Run("int non-zero", func(t *testing.T) {
|
||||||
|
val := 42
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
negVal := -1
|
||||||
|
assert.False(t, IsNilOrZero(&negVal))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("string non-zero", func(t *testing.T) {
|
||||||
|
val := "hello"
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
spaceVal := " "
|
||||||
|
assert.False(t, IsNilOrZero(&spaceVal))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bool non-zero", func(t *testing.T) {
|
||||||
|
val := true
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float64 non-zero", func(t *testing.T) {
|
||||||
|
val := 3.14
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
negVal := -2.5
|
||||||
|
assert.False(t, IsNilOrZero(&negVal))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint non-zero", func(t *testing.T) {
|
||||||
|
val := uint(100)
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("works with all integer types", func(t *testing.T) {
|
||||||
|
t.Run("int8", func(t *testing.T) {
|
||||||
|
var nilPtr *int8
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := int8(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := int8(127)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int16", func(t *testing.T) {
|
||||||
|
var nilPtr *int16
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := int16(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := int16(1000)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int32", func(t *testing.T) {
|
||||||
|
var nilPtr *int32
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := int32(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := int32(100000)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int64", func(t *testing.T) {
|
||||||
|
var nilPtr *int64
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := int64(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := int64(9223372036854775807)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint8", func(t *testing.T) {
|
||||||
|
var nilPtr *uint8
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := uint8(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := uint8(255)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint16", func(t *testing.T) {
|
||||||
|
var nilPtr *uint16
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := uint16(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := uint16(65535)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint32", func(t *testing.T) {
|
||||||
|
var nilPtr *uint32
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := uint32(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := uint32(4294967295)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint64", func(t *testing.T) {
|
||||||
|
var nilPtr *uint64
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := uint64(0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := uint64(18446744073709551615)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("works with float types", func(t *testing.T) {
|
||||||
|
t.Run("float32", func(t *testing.T) {
|
||||||
|
var nilPtr *float32
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := float32(0.0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := float32(1.5)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float64", func(t *testing.T) {
|
||||||
|
var nilPtr *float64
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
|
||||||
|
zero := float64(0.0)
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
|
||||||
|
nonZero := float64(123.456)
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("works with struct types", func(t *testing.T) {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("nil struct pointer", func(t *testing.T) {
|
||||||
|
var nilPtr *Person
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to zero struct", func(t *testing.T) {
|
||||||
|
zero := Person{}
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to non-zero struct", func(t *testing.T) {
|
||||||
|
nonZero := Person{Name: "John", Age: 30}
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
|
||||||
|
partiallyFilled := Person{Name: "Jane"}
|
||||||
|
assert.False(t, IsNilOrZero(&partiallyFilled))
|
||||||
|
|
||||||
|
onlyAge := Person{Age: 25}
|
||||||
|
assert.False(t, IsNilOrZero(&onlyAge))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("works with array types", func(t *testing.T) {
|
||||||
|
t.Run("nil array pointer", func(t *testing.T) {
|
||||||
|
var nilPtr *[3]int
|
||||||
|
assert.True(t, IsNilOrZero(nilPtr))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to zero array", func(t *testing.T) {
|
||||||
|
zero := [3]int{0, 0, 0}
|
||||||
|
assert.True(t, IsNilOrZero(&zero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to non-zero array", func(t *testing.T) {
|
||||||
|
nonZero := [3]int{1, 2, 3}
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
|
||||||
|
partiallyFilled := [3]int{1, 0, 0}
|
||||||
|
assert.False(t, IsNilOrZero(&partiallyFilled))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("edge cases", func(t *testing.T) {
|
||||||
|
t.Run("pointer to negative number", func(t *testing.T) {
|
||||||
|
val := -1
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to whitespace string", func(t *testing.T) {
|
||||||
|
val := " "
|
||||||
|
assert.False(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
tab := "\t"
|
||||||
|
assert.False(t, IsNilOrZero(&tab))
|
||||||
|
|
||||||
|
newline := "\n"
|
||||||
|
assert.False(t, IsNilOrZero(&newline))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to byte (uint8) zero", func(t *testing.T) {
|
||||||
|
val := byte(0)
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
nonZero := byte('A')
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to rune (int32) zero", func(t *testing.T) {
|
||||||
|
val := rune(0)
|
||||||
|
assert.True(t, IsNilOrZero(&val))
|
||||||
|
|
||||||
|
nonZero := rune('Z')
|
||||||
|
assert.False(t, IsNilOrZero(&nonZero))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("consistency with ToPtr", func(t *testing.T) {
|
||||||
|
t.Run("ToPtr of zero value is nil or zero", func(t *testing.T) {
|
||||||
|
intPtr := ToPtr(0)
|
||||||
|
assert.True(t, IsNilOrZero(intPtr))
|
||||||
|
|
||||||
|
strPtr := ToPtr("")
|
||||||
|
assert.True(t, IsNilOrZero(strPtr))
|
||||||
|
|
||||||
|
boolPtr := ToPtr(false)
|
||||||
|
assert.True(t, IsNilOrZero(boolPtr))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ToPtr of non-zero value is not nil or zero", func(t *testing.T) {
|
||||||
|
intPtr := ToPtr(42)
|
||||||
|
assert.False(t, IsNilOrZero(intPtr))
|
||||||
|
|
||||||
|
strPtr := ToPtr("hello")
|
||||||
|
assert.False(t, IsNilOrZero(strPtr))
|
||||||
|
|
||||||
|
boolPtr := ToPtr(true)
|
||||||
|
assert.False(t, IsNilOrZero(boolPtr))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("real-world usage scenarios", func(t *testing.T) {
|
||||||
|
t.Run("optional configuration value", func(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port *int
|
||||||
|
Timeout *int
|
||||||
|
}
|
||||||
|
|
||||||
|
// No port configured (nil)
|
||||||
|
config1 := Config{}
|
||||||
|
assert.True(t, IsNilOrZero(config1.Port))
|
||||||
|
|
||||||
|
// Port explicitly set to 0
|
||||||
|
zero := 0
|
||||||
|
config2 := Config{Port: &zero}
|
||||||
|
assert.True(t, IsNilOrZero(config2.Port))
|
||||||
|
|
||||||
|
// Port configured to 8080
|
||||||
|
port := 8080
|
||||||
|
config3 := Config{Port: &port}
|
||||||
|
assert.False(t, IsNilOrZero(config3.Port))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional string field", func(t *testing.T) {
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Nickname *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// No nickname provided
|
||||||
|
user1 := User{Name: "John"}
|
||||||
|
assert.True(t, IsNilOrZero(user1.Nickname))
|
||||||
|
|
||||||
|
// Nickname explicitly empty
|
||||||
|
emptyNick := ""
|
||||||
|
user2 := User{Name: "Jane", Nickname: &emptyNick}
|
||||||
|
assert.True(t, IsNilOrZero(user2.Nickname))
|
||||||
|
|
||||||
|
// Nickname provided
|
||||||
|
nick := "Johnny"
|
||||||
|
user3 := User{Name: "John", Nickname: &nick}
|
||||||
|
assert.False(t, IsNilOrZero(user3.Nickname))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -81,6 +81,45 @@ func HandleErrorOrDie(err error, matchers ...ErrorMatcher) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleErrorOrDefault processes an error against a list of matchers and executes the appropriate handler.
|
||||||
|
// If a matching handler is found, it returns the handler's result.
|
||||||
|
// If no matcher matches the error, it executes the default handler (dft) and returns its result.
|
||||||
|
// If dft is nil, unmatched errors return nil (effectively suppressing the error).
|
||||||
|
// This function is useful when you want to handle specific error cases explicitly
|
||||||
|
// while providing a fallback handler for all other errors.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// result := HandleErrorOrDefault(err,
|
||||||
|
// func(e error) error {
|
||||||
|
// // Default handler for unmatched errors
|
||||||
|
// return fmt.Errorf("unexpected error: %w", e)
|
||||||
|
// },
|
||||||
|
// OnSentinelError(context.Canceled, func(e error) error {
|
||||||
|
// return fmt.Errorf("operation canceled")
|
||||||
|
// }),
|
||||||
|
// OnCustomError(func(e *ValidationError) error {
|
||||||
|
// return fmt.Errorf("validation failed: %w", e)
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Suppress unmatched errors by passing nil as default handler
|
||||||
|
// result := HandleErrorOrDefault(err, nil,
|
||||||
|
// OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
// return errors.New("EOF handled")
|
||||||
|
// }),
|
||||||
|
// ) // Returns nil for unmatched errors
|
||||||
|
func HandleErrorOrDefault(err error, dft ErrorHandler, matchers ...ErrorMatcher) error {
|
||||||
|
ok, err := HandleError(err, matchers...)
|
||||||
|
if !ok {
|
||||||
|
if dft == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return dft(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// OnSentinelError creates an ErrorMatcher for sentinel errors.
|
// OnSentinelError creates an ErrorMatcher for sentinel errors.
|
||||||
// Sentinel errors are predefined error values that are compared using errors.Is.
|
// Sentinel errors are predefined error values that are compared using errors.Is.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -251,6 +251,288 @@ func TestHandleErrorOrDie(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleErrorOrDefault(t *testing.T) {
|
||||||
|
t.Run("HandleErrorOrDefault with nil error", func(t *testing.T) {
|
||||||
|
defaultCalled := false
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
nil,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, result)
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with matched error", func(t *testing.T) {
|
||||||
|
err := io.EOF
|
||||||
|
defaultCalled := false
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
err,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("handled EOF")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
require.EqualError(t, result, "handled EOF")
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with unmatched error calls default", func(t *testing.T) {
|
||||||
|
err := errors.New("unmatched error")
|
||||||
|
defaultCalled := false
|
||||||
|
var capturedErr error
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
err,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
capturedErr = e
|
||||||
|
return fmt.Errorf("default: %w", e)
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, defaultCalled)
|
||||||
|
assert.Equal(t, err, capturedErr)
|
||||||
|
assert.EqualError(t, result, "default: unmatched error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with custom error match", func(t *testing.T) {
|
||||||
|
customErr := &CustomError{Code: 404, Message: "not found"}
|
||||||
|
defaultCalled := false
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
customErr,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
OnCustomError(func(e *CustomError) error {
|
||||||
|
return fmt.Errorf("custom handler: code=%d", e.Code)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
assert.EqualError(t, result, "custom handler: code=404")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with custom error no match", func(t *testing.T) {
|
||||||
|
customErr := &CustomError{Code: 500, Message: "server error"}
|
||||||
|
defaultCalled := false
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
customErr,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return fmt.Errorf("default for custom: %v", e)
|
||||||
|
},
|
||||||
|
OnCustomError(func(e *ValidationError) error {
|
||||||
|
return errors.New("validation handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, defaultCalled)
|
||||||
|
assert.EqualError(t, result, "default for custom: custom error 500: server error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with multiple matchers", func(t *testing.T) {
|
||||||
|
validationErr := &ValidationError{Field: "email", Value: "invalid"}
|
||||||
|
defaultCalled := false
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
validationErr,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
OnCustomError(func(e *CustomError) error {
|
||||||
|
return errors.New("custom handler")
|
||||||
|
}),
|
||||||
|
OnCustomError(func(e *ValidationError) error {
|
||||||
|
return fmt.Errorf("validation handler: field=%s", e.Field)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
assert.EqualError(t, result, "validation handler: field=email")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault default returns nil", func(t *testing.T) {
|
||||||
|
err := errors.New("some error")
|
||||||
|
defaultCalled := false
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
err,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return nil // Default handler suppresses error
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.True(t, defaultCalled)
|
||||||
|
assert.NoError(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with wrapped error", func(t *testing.T) {
|
||||||
|
wrappedErr := fmt.Errorf("wrapped: %w", io.EOF)
|
||||||
|
defaultCalled := false
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
wrappedErr,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("handled wrapped EOF")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
assert.EqualError(t, result, "handled wrapped EOF")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with context errors", func(t *testing.T) {
|
||||||
|
// Test matched context.Canceled
|
||||||
|
defaultCalled := false
|
||||||
|
result1 := HandleErrorOrDefault(
|
||||||
|
context.Canceled,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default handler")
|
||||||
|
},
|
||||||
|
OnSentinelError(context.Canceled, func(e error) error {
|
||||||
|
return errors.New("operation canceled")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assert.False(t, defaultCalled)
|
||||||
|
require.EqualError(t, result1, "operation canceled")
|
||||||
|
|
||||||
|
// Test unmatched context.DeadlineExceeded
|
||||||
|
defaultCalled = false
|
||||||
|
result2 := HandleErrorOrDefault(
|
||||||
|
context.DeadlineExceeded,
|
||||||
|
func(e error) error {
|
||||||
|
defaultCalled = true
|
||||||
|
return errors.New("default: deadline exceeded")
|
||||||
|
},
|
||||||
|
OnSentinelError(context.Canceled, func(e error) error {
|
||||||
|
return errors.New("operation canceled")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assert.True(t, defaultCalled)
|
||||||
|
assert.EqualError(t, result2, "default: deadline exceeded")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault preserves error in default handler", func(t *testing.T) {
|
||||||
|
originalErr := &CustomError{Code: 403, Message: "forbidden"}
|
||||||
|
var capturedErr error
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
originalErr,
|
||||||
|
func(e error) error {
|
||||||
|
capturedErr = e
|
||||||
|
return fmt.Errorf("default handled: %w", e)
|
||||||
|
},
|
||||||
|
OnCustomError(func(e *ValidationError) error {
|
||||||
|
return errors.New("validation handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, originalErr, capturedErr)
|
||||||
|
assert.EqualError(t, result, "default handled: custom error 403: forbidden")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault default handler can access error details", func(t *testing.T) {
|
||||||
|
customErr := &CustomError{Code: 500, Message: "internal error"}
|
||||||
|
wrappedErr := fmt.Errorf("operation failed: %w", customErr)
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
wrappedErr,
|
||||||
|
func(e error) error {
|
||||||
|
// Default handler can still use errors.As to extract details
|
||||||
|
var ce *CustomError
|
||||||
|
if errors.As(e, &ce) {
|
||||||
|
return fmt.Errorf("default: found code %d", ce.Code)
|
||||||
|
}
|
||||||
|
return errors.New("default: unknown error")
|
||||||
|
},
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.EqualError(t, result, "default: found code 500")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with nil default handler and no match", func(t *testing.T) {
|
||||||
|
err := errors.New("unmatched error")
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
err,
|
||||||
|
nil, // nil default handler
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, result) // Should return nil for unmatched errors
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with nil default handler and match", func(t *testing.T) {
|
||||||
|
err := io.EOF
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
err,
|
||||||
|
nil, // nil default handler (should not be called)
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handled")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.EqualError(t, result, "EOF handled") // Should use the matcher, not default
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with nil default handler and nil error", func(t *testing.T) {
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
nil,
|
||||||
|
nil, // nil default handler
|
||||||
|
OnSentinelError(io.EOF, func(e error) error {
|
||||||
|
return errors.New("EOF handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, result) // nil error should return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("HandleErrorOrDefault with nil default handler suppresses custom errors", func(t *testing.T) {
|
||||||
|
customErr := &CustomError{Code: 404, Message: "not found"}
|
||||||
|
|
||||||
|
result := HandleErrorOrDefault(
|
||||||
|
customErr,
|
||||||
|
nil, // nil default handler suppresses unmatched errors
|
||||||
|
OnCustomError(func(e *ValidationError) error {
|
||||||
|
return errors.New("validation handler")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, result) // Should return nil since no matcher matches
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestOnSentinelError(t *testing.T) {
|
func TestOnSentinelError(t *testing.T) {
|
||||||
t.Run("OnSentinelError creates proper matcher", func(t *testing.T) {
|
t.Run("OnSentinelError creates proper matcher", func(t *testing.T) {
|
||||||
handler := func(e error) error { return e }
|
handler := func(e error) error { return e }
|
||||||
|
|||||||
26
number/base.go
Normal file
26
number/base.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
type Number interface {
|
||||||
|
int | int8 | int16 | int32 | int64 |
|
||||||
|
uint | uint8 | uint16 | uint32 | uint64 | uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NumLen[T Number](number T) T {
|
||||||
|
if number == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var count T = 0
|
||||||
|
if number < 0 {
|
||||||
|
for number < 0 {
|
||||||
|
number /= 10
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for number > 0 {
|
||||||
|
number /= 10
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
343
number/base_test.go
Normal file
343
number/base_test.go
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
package number
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNumLen(t *testing.T) {
|
||||||
|
t.Run("NumLen with zero", func(t *testing.T) {
|
||||||
|
assert.Equal(t, 1, NumLen(0))
|
||||||
|
assert.Equal(t, int8(1), NumLen(int8(0)))
|
||||||
|
assert.Equal(t, int16(1), NumLen(int16(0)))
|
||||||
|
assert.Equal(t, int32(1), NumLen(int32(0)))
|
||||||
|
assert.Equal(t, int64(1), NumLen(int64(0)))
|
||||||
|
assert.Equal(t, uint(1), NumLen(uint(0)))
|
||||||
|
assert.Equal(t, uint8(1), NumLen(uint8(0)))
|
||||||
|
assert.Equal(t, uint16(1), NumLen(uint16(0)))
|
||||||
|
assert.Equal(t, uint32(1), NumLen(uint32(0)))
|
||||||
|
assert.Equal(t, uint64(1), NumLen(uint64(0)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with single digit positive numbers", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"one", 1, 1},
|
||||||
|
{"five", 5, 1},
|
||||||
|
{"nine", 9, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with single digit negative numbers", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"negative one", -1, 1},
|
||||||
|
{"negative five", -5, 1},
|
||||||
|
{"negative nine", -9, 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with multi-digit positive numbers", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"two digits", 42, 2},
|
||||||
|
{"three digits", 123, 3},
|
||||||
|
{"four digits", 9999, 4},
|
||||||
|
{"five digits", 12345, 5},
|
||||||
|
{"six digits", 100000, 6},
|
||||||
|
{"seven digits", 9876543, 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with multi-digit negative numbers", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"negative two digits", -42, 2},
|
||||||
|
{"negative three digits", -123, 3},
|
||||||
|
{"negative four digits", -9999, 4},
|
||||||
|
{"negative five digits", -12345, 5},
|
||||||
|
{"negative six digits", -100000, 6},
|
||||||
|
{"negative seven digits", -9876543, 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with int8", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int8
|
||||||
|
expected int8
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"positive single", 9, 1},
|
||||||
|
{"positive double", 99, 2},
|
||||||
|
{"positive triple", 127, 3},
|
||||||
|
{"negative single", -9, 1},
|
||||||
|
{"negative double", -99, 2},
|
||||||
|
{"negative triple", -128, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with int16", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int16
|
||||||
|
expected int16
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"positive single", 5, 1},
|
||||||
|
{"positive double", 42, 2},
|
||||||
|
{"positive triple", 999, 3},
|
||||||
|
{"positive max", 32767, 5},
|
||||||
|
{"negative single", -5, 1},
|
||||||
|
{"negative double", -42, 2},
|
||||||
|
{"negative triple", -999, 3},
|
||||||
|
{"negative min", -32768, 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with int32", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int32
|
||||||
|
expected int32
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"positive single", 7, 1},
|
||||||
|
{"positive five", 12345, 5},
|
||||||
|
{"positive max", 2147483647, 10},
|
||||||
|
{"negative single", -7, 1},
|
||||||
|
{"negative five", -12345, 5},
|
||||||
|
{"negative min", -2147483648, 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with int64", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input int64
|
||||||
|
expected int64
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"positive single", 3, 1},
|
||||||
|
{"positive ten", 1234567890, 10},
|
||||||
|
{"positive max", 9223372036854775807, 19},
|
||||||
|
{"negative single", -3, 1},
|
||||||
|
{"negative ten", -1234567890, 10},
|
||||||
|
{"negative min", -9223372036854775808, 19},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with uint", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input uint
|
||||||
|
expected uint
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"single digit", 5, 1},
|
||||||
|
{"double digit", 42, 2},
|
||||||
|
{"triple digit", 999, 3},
|
||||||
|
{"large number", 123456789, 9},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with uint8", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input uint8
|
||||||
|
expected uint8
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"single digit", 9, 1},
|
||||||
|
{"double digit", 99, 2},
|
||||||
|
{"max value", 255, 3},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with uint16", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input uint16
|
||||||
|
expected uint16
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"single digit", 8, 1},
|
||||||
|
{"double digit", 50, 2},
|
||||||
|
{"triple digit", 500, 3},
|
||||||
|
{"max value", 65535, 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with uint32", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input uint32
|
||||||
|
expected uint32
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"single digit", 6, 1},
|
||||||
|
{"five digits", 54321, 5},
|
||||||
|
{"max value", 4294967295, 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with uint64", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input uint64
|
||||||
|
expected uint64
|
||||||
|
}{
|
||||||
|
{"zero", 0, 1},
|
||||||
|
{"single digit", 4, 1},
|
||||||
|
{"ten digits", 9876543210, 10},
|
||||||
|
{"max value", 18446744073709551615, 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result := NumLen(test.input)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen with boundary values", func(t *testing.T) {
|
||||||
|
t.Run("powers of 10", func(t *testing.T) {
|
||||||
|
assert.Equal(t, 1, NumLen(1))
|
||||||
|
assert.Equal(t, 2, NumLen(10))
|
||||||
|
assert.Equal(t, 3, NumLen(100))
|
||||||
|
assert.Equal(t, 4, NumLen(1000))
|
||||||
|
assert.Equal(t, 5, NumLen(10000))
|
||||||
|
assert.Equal(t, 6, NumLen(100000))
|
||||||
|
assert.Equal(t, 7, NumLen(1000000))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one less than powers of 10", func(t *testing.T) {
|
||||||
|
assert.Equal(t, 1, NumLen(9))
|
||||||
|
assert.Equal(t, 2, NumLen(99))
|
||||||
|
assert.Equal(t, 3, NumLen(999))
|
||||||
|
assert.Equal(t, 4, NumLen(9999))
|
||||||
|
assert.Equal(t, 5, NumLen(99999))
|
||||||
|
assert.Equal(t, 6, NumLen(999999))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative powers of 10", func(t *testing.T) {
|
||||||
|
assert.Equal(t, 1, NumLen(-1))
|
||||||
|
assert.Equal(t, 2, NumLen(-10))
|
||||||
|
assert.Equal(t, 3, NumLen(-100))
|
||||||
|
assert.Equal(t, 4, NumLen(-1000))
|
||||||
|
assert.Equal(t, 5, NumLen(-10000))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NumLen consistency across types", func(t *testing.T) {
|
||||||
|
// Same value, different types should return same digit count
|
||||||
|
assert.Equal(t, 2, NumLen(42))
|
||||||
|
assert.Equal(t, int8(2), NumLen(int8(42)))
|
||||||
|
assert.Equal(t, int16(2), NumLen(int16(42)))
|
||||||
|
assert.Equal(t, int32(2), NumLen(int32(42)))
|
||||||
|
assert.Equal(t, int64(2), NumLen(int64(42)))
|
||||||
|
assert.Equal(t, uint(2), NumLen(uint(42)))
|
||||||
|
assert.Equal(t, uint8(2), NumLen(uint8(42)))
|
||||||
|
assert.Equal(t, uint16(2), NumLen(uint16(42)))
|
||||||
|
assert.Equal(t, uint32(2), NumLen(uint32(42)))
|
||||||
|
assert.Equal(t, uint64(2), NumLen(uint64(42)))
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user