5 Commits

6 changed files with 891 additions and 25 deletions

View File

@@ -5,7 +5,7 @@ A collection of generic utility functions for Go projects.
## Installation ## Installation
```bash ```bash
go get github.com/aykhans/go-utils go get go.aykhans.me/go-utils
``` ```
## Packages ## Packages
@@ -16,7 +16,7 @@ Generic type conversion utilities.
**ToPtr** - Convert any value to a pointer **ToPtr** - Convert any value to a pointer
```go ```go
import "github.com/aykhans/go-utils/common" import "go.aykhans.me/go-utils/common"
num := 42 num := 42
ptr := common.ToPtr(num) // *int ptr := common.ToPtr(num) // *int
@@ -24,7 +24,7 @@ ptr := common.ToPtr(num) // *int
**IsNilOrZero** - Check if a pointer is nil or points to a zero value **IsNilOrZero** - Check if a pointer is nil or points to a zero value
```go ```go
import "github.com/aykhans/go-utils/common" import "go.aykhans.me/go-utils/common"
var ptr *int var ptr *int
common.IsNilOrZero(ptr) // true (nil pointer) common.IsNilOrZero(ptr) // true (nil pointer)
@@ -42,7 +42,7 @@ String parsing utilities with generic type support.
**ParseString** - Parse string to various types **ParseString** - Parse string to various types
```go ```go
import "github.com/aykhans/go-utils/parser" import "go.aykhans.me/go-utils/parser"
num, err := parser.ParseString[int]("42") num, err := parser.ParseString[int]("42")
duration, err := parser.ParseString[time.Duration]("5s") duration, err := parser.ParseString[time.Duration]("5s")
@@ -72,7 +72,7 @@ Slice manipulation utilities.
**Cycle** - Create an infinite cycler through items **Cycle** - Create an infinite cycler through items
```go ```go
import "github.com/aykhans/go-utils/slice" import "go.aykhans.me/go-utils/slice"
next := slice.Cycle(1, 2, 3) next := slice.Cycle(1, 2, 3)
fmt.Println(next()) // 1 fmt.Println(next()) // 1
@@ -93,7 +93,7 @@ Map utility functions.
**InitMap** - Initialize a map pointer if nil **InitMap** - Initialize a map pointer if nil
```go ```go
import "github.com/aykhans/go-utils/maps" import "go.aykhans.me/go-utils/maps"
var m map[string]int var m map[string]int
maps.InitMap(&m) maps.InitMap(&m)
@@ -108,72 +108,93 @@ 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 "go.aykhans.me/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.
**HandleError** - Process errors with custom matchers **Handle** - Process errors with custom matchers
```go ```go
import "github.com/aykhans/go-utils/errors" import "go.aykhans.me/go-utils/errors"
handled, result := errors.HandleError(err, handled, result := errors.Handle(err,
errors.OnSentinelError(io.EOF, func(e error) error { errors.OnSentinel(io.EOF, func(e error) error {
return nil // EOF is expected, ignore it return nil // EOF is expected, ignore it
}), }),
errors.OnCustomError(func(e *CustomError) error { errors.OnType(func(e *CustomError) error {
return fmt.Errorf("custom error: %w", e) return fmt.Errorf("custom error: %w", e)
}), }),
) )
``` ```
**HandleErrorOrDie** - Handle errors or panic if unhandled **MustHandle** - Handle errors or panic if unhandled
```go ```go
result := errors.HandleErrorOrDie(err, result := errors.MustHandle(err,
errors.OnSentinelError(context.Canceled, func(e error) error { errors.OnSentinel(context.Canceled, func(e error) error {
return fmt.Errorf("operation canceled") return fmt.Errorf("operation canceled")
}), }),
) // Panics if err doesn't match any handler ) // Panics if err doesn't match any handler
``` ```
**HandleErrorOrDefault** - Handle errors with a default fallback **HandleOr** - Handle errors with a default fallback
```go ```go
result := errors.HandleErrorOrDefault(err, result := errors.HandleOr(err,
func(e error) error { func(e error) error {
// Default handler for unmatched errors // Default handler for unmatched errors
return fmt.Errorf("unexpected error: %w", e) return fmt.Errorf("unexpected error: %w", e)
}, },
errors.OnSentinelError(context.Canceled, func(e error) error { errors.OnSentinel(context.Canceled, func(e error) error {
return fmt.Errorf("operation canceled") return fmt.Errorf("operation canceled")
}), }),
errors.OnCustomError(func(e *ValidationError) error { errors.OnType(func(e *ValidationError) error {
return fmt.Errorf("validation failed: %w", e) return fmt.Errorf("validation failed: %w", e)
}), }),
) )
// Pass nil to suppress unmatched errors // Pass nil to suppress unmatched errors
result := errors.HandleErrorOrDefault(err, nil, result := errors.HandleOr(err, nil,
errors.OnSentinelError(io.EOF, func(e error) error { errors.OnSentinel(io.EOF, func(e error) error {
return errors.New("EOF handled") return errors.New("EOF handled")
}), }),
) // Returns nil for unmatched errors ) // Returns nil for unmatched errors
``` ```
**OnSentinelError** - Create matcher for sentinel errors (like `io.EOF`) **OnSentinel** - Create matcher for sentinel errors (like `io.EOF`)
```go ```go
matcher := errors.OnSentinelError(io.EOF, func(e error) error { matcher := errors.OnSentinel(io.EOF, func(e error) error {
log.Println("reached end of file") log.Println("reached end of file")
return nil return nil
}) })
``` ```
**OnCustomError** - Create matcher for custom error types **OnType** - Create matcher for custom error types
```go ```go
type ValidationError struct { type ValidationError struct {
Field string Field string
Msg string Msg string
} }
matcher := errors.OnCustomError(func(e *ValidationError) error { matcher := errors.OnType(func(e *ValidationError) error {
log.Printf("validation failed on field %s", e.Field) log.Printf("validation failed on field %s", e.Field)
return fmt.Errorf("invalid input: %w", e) return fmt.Errorf("invalid input: %w", e)
}) })

350
common/compare_test.go Normal file
View 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))
})
})
}

View File

@@ -21,6 +21,9 @@ type ErrorMatcher struct {
// or (false, nil) if no matcher matches the error. // or (false, nil) if no matcher matches the error.
// If err is nil, returns (true, nil). // If err is nil, returns (true, nil).
// //
// Deprecated: HandleError is deprecated and will be removed in a future release.
// Use Handle instead, which provides the same functionality.
//
// Example: // Example:
// //
// handled, result := HandleError(err, // handled, result := HandleError(err,
@@ -58,11 +61,31 @@ func HandleError(err error, matchers ...ErrorMatcher) (bool, error) {
return false, err // No matcher found return false, err // No matcher found
} }
// Handle processes an error against a list of matchers and executes the appropriate handler.
// It returns (true, handlerResult) if a matching handler is found and executed,
// or (false, nil) if no matcher matches the error.
// If err is nil, returns (true, nil).
//
// Example:
//
// handled, result := Handle(err,
// OnSentinel(io.EOF, func(e error) error {
// return nil // EOF is expected, ignore it
// }),
// OnType(func(e *CustomError) error {
// return fmt.Errorf("custom error: %w", e)
// }),
// )
var Handle = HandleError
// HandleErrorOrDie processes an error against a list of matchers and executes the appropriate handler. // HandleErrorOrDie 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 a matching handler is found, it returns the handler's result.
// If no matcher matches the error, it panics with a descriptive message. // If no matcher matches the error, it panics with a descriptive message.
// This function is useful when all expected error types must be handled explicitly. // This function is useful when all expected error types must be handled explicitly.
// //
// Deprecated: HandleErrorOrDie is deprecated and will be removed in a future release.
// Use MustHandle instead, which provides the same functionality.
//
// Example: // Example:
// //
// result := HandleErrorOrDie(err, // result := HandleErrorOrDie(err,
@@ -81,6 +104,23 @@ func HandleErrorOrDie(err error, matchers ...ErrorMatcher) error {
return err return err
} }
// MustHandle 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 panics with a descriptive message.
// This function is useful when all expected error types must be handled explicitly.
//
// Example:
//
// result := MustHandle(err,
// OnSentinel(context.Canceled, func(e error) error {
// return fmt.Errorf("operation canceled")
// }),
// OnType(func(e *ValidationError) error {
// return fmt.Errorf("validation failed: %w", e)
// }),
// ) // Panics if err doesn't match any handler
var MustHandle = HandleErrorOrDie
// HandleErrorOrDefault processes an error against a list of matchers and executes the appropriate handler. // 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 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 no matcher matches the error, it executes the default handler (dft) and returns its result.
@@ -88,6 +128,9 @@ func HandleErrorOrDie(err error, matchers ...ErrorMatcher) error {
// This function is useful when you want to handle specific error cases explicitly // This function is useful when you want to handle specific error cases explicitly
// while providing a fallback handler for all other errors. // while providing a fallback handler for all other errors.
// //
// Deprecated: HandleErrorOrDefault is deprecated and will be removed in a future release.
// Use HandleOr instead, which provides the same functionality.
//
// Example: // Example:
// //
// result := HandleErrorOrDefault(err, // result := HandleErrorOrDefault(err,
@@ -120,6 +163,36 @@ func HandleErrorOrDefault(err error, dft ErrorHandler, matchers ...ErrorMatcher)
return err return err
} }
// HandleOr 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 := HandleOr(err,
// func(e error) error {
// // Default handler for unmatched errors
// return fmt.Errorf("unexpected error: %w", e)
// },
// OnSentinel(context.Canceled, func(e error) error {
// return fmt.Errorf("operation canceled")
// }),
// OnType(func(e *ValidationError) error {
// return fmt.Errorf("validation failed: %w", e)
// }),
// )
//
// // Suppress unmatched errors by passing nil as default handler
// result := HandleOr(err, nil,
// OnSentinel(io.EOF, func(e error) error {
// return errors.New("EOF handled")
// }),
// ) // Returns nil for unmatched errors
var HandleOr = HandleErrorOrDefault
// 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.
// //
@@ -130,6 +203,9 @@ func HandleErrorOrDefault(err error, dft ErrorHandler, matchers ...ErrorMatcher)
// The handler function receives the original error and can return a new error // The handler function receives the original error and can return a new error
// or nil to suppress it. // or nil to suppress it.
// //
// Deprecated: OnSentinelError is deprecated and will be removed in a future release.
// Use OnSentinel instead, which provides the same functionality.
//
// Example: // Example:
// //
// matcher := OnSentinelError(io.EOF, func(e error) error { // matcher := OnSentinelError(io.EOF, func(e error) error {
@@ -144,6 +220,24 @@ func OnSentinelError(sentinelErr error, handler ErrorHandler) ErrorMatcher {
} }
} }
// OnSentinel creates an ErrorMatcher for sentinel errors.
// Sentinel errors are predefined error values that are compared using errors.Is.
//
// This is used with Handle or MustHandle to match specific error
// values like io.EOF, context.Canceled, or custom sentinel errors defined with
// errors.New or fmt.Errorf.
//
// The handler function receives the original error and can return a new error
// or nil to suppress it.
//
// Example:
//
// matcher := OnSentinel(io.EOF, func(e error) error {
// log.Println("reached end of file")
// return nil // suppress EOF error
// })
var OnSentinel = OnSentinelError
// OnCustomError creates an ErrorMatcher for custom error types. // OnCustomError creates an ErrorMatcher for custom error types.
// Custom error types are struct types that implement the error interface, // Custom error types are struct types that implement the error interface,
// and are matched using errors.As to unwrap error chains. // and are matched using errors.As to unwrap error chains.
@@ -155,6 +249,9 @@ func OnSentinelError(sentinelErr error, handler ErrorHandler) ErrorMatcher {
// This is particularly useful for handling errors with additional context or data, // This is particularly useful for handling errors with additional context or data,
// such as validation errors, network errors, or domain-specific errors. // such as validation errors, network errors, or domain-specific errors.
// //
// Deprecated: OnCustomError is deprecated and will be removed in a future release.
// Use OnType instead, which provides the same functionality.
//
// Example: // Example:
// //
// type ValidationError struct { // type ValidationError struct {
@@ -183,3 +280,32 @@ func OnCustomError[T error](handler func(T) error) ErrorMatcher {
IsSentinel: false, IsSentinel: false,
} }
} }
// OnType creates an ErrorMatcher for custom error types.
// Custom error types are struct types that implement the error interface,
// and are matched using errors.As to unwrap error chains.
//
// The type parameter T specifies the error type to match. The handler function
// receives the unwrapped typed error, allowing you to access type-specific fields
// and methods.
//
// This is particularly useful for handling errors with additional context or data,
// such as validation errors, network errors, or domain-specific errors.
//
// Example:
//
// type ValidationError struct {
// Field string
// Msg string
// }
// func (e *ValidationError) Error() string {
// return fmt.Sprintf("%s: %s", e.Field, e.Msg)
// }
//
// matcher := OnType(func(e *ValidationError) error {
// log.Printf("validation failed on field %s: %s", e.Field, e.Msg)
// return fmt.Errorf("invalid input: %w", e)
// })
func OnType[T error](handler func(T) error) ErrorMatcher {
return OnCustomError(handler)
}

2
go.mod
View File

@@ -1,4 +1,4 @@
module github.com/aykhans/go-utils module go.aykhans.me/go-utils
go 1.25.0 go 1.25.0

26
number/base.go Normal file
View 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
View 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)))
})
}