Utils Package
The utils package provides general-purpose utility functions for common programming tasks including map operations, slice manipulations, string processing, and more.
Table of Contents
- Overview
- Map Operations
- Slice Operations
- String Operations
- Security Utilities
- Type Checking
- Best Practices
- Examples
Overview
Import Path: github.com/primadi/lokstra/common/utils
Key Features:
✓ Type-Safe Map Access - GetValueFromMap with generics
✓ Duration Parsing - Flexible duration extraction
✓ Slice Operations - Concat, convert, sorted insert
✓ String Utilities - Case conversion, parsing
✓ Security Helpers - Password hashing, IP extraction
✓ Type Checking - Nil checking utilities
Map Operations
GetValueFromMap
Extract typed values from map[string]any with defaults:
config := map[string]any{
"host": "localhost",
"port": 8080,
"timeout": "30s",
"enabled": true,
}
// String value
host := utils.GetValueFromMap(config, "host", "127.0.0.1")
// host = "localhost"
// Int value
port := utils.GetValueFromMap(config, "port", 3000)
// port = 8080
// Bool value
enabled := utils.GetValueFromMap(config, "enabled", false)
// enabled = true
// Missing key (returns default)
retries := utils.GetValueFromMap(config, "retries", 3)
// retries = 3
// Wrong type (returns default)
invalidPort := utils.GetValueFromMap[string](config, "port", "3000")
// invalidPort = "3000" (port is int, not string)
Type Safety:
// Type parameter ensures compile-time safety
port := utils.GetValueFromMap[int](config, "port", 8080) // Correct
name := utils.GetValueFromMap[string](config, "name", "") // Correct
// Compiler catches type mismatches
port := utils.GetValueFromMap[string](config, "port", 8080) // Compile error
Pointer Values:
config := map[string]any{
"timeout": new(int),
}
// Handles both direct values and pointers
timeout := utils.GetValueFromMap(config, "timeout", 30)
// Dereferences pointer automatically
GetDurationFromMap
Extract time.Duration from various formats:
config := map[string]any{
"timeout1": "30s", // String format
"timeout2": 45, // Int (seconds)
"timeout3": 60.5, // Float (seconds)
"timeout4": 2 * time.Minute, // Duration
}
// From string
timeout1 := utils.GetDurationFromMap(config, "timeout1", 10*time.Second)
// timeout1 = 30 * time.Second
// From int (interpreted as seconds)
timeout2 := utils.GetDurationFromMap(config, "timeout2", time.Minute)
// timeout2 = 45 * time.Second
// From float (interpreted as seconds)
timeout3 := utils.GetDurationFromMap(config, "timeout3", time.Minute)
// timeout3 = 60.5 * time.Second
// From time.Duration
timeout4 := utils.GetDurationFromMap(config, "timeout4", time.Minute)
// timeout4 = 2 * time.Minute
// Missing key (returns default)
timeout5 := utils.GetDurationFromMap(config, "timeout5", time.Minute)
// timeout5 = time.Minute
Default Value Formats:
// String default
timeout := utils.GetDurationFromMap(config, "timeout", "1m30s")
// Int default (seconds)
timeout := utils.GetDurationFromMap(config, "timeout", 90)
// Duration default
timeout := utils.GetDurationFromMap(config, "timeout", 90*time.Second)
CloneMap
Create a shallow copy of a map:
original := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
// Clone map
cloned := utils.CloneMap(original)
// Modify clone (doesn't affect original)
cloned["a"] = 100
cloned["d"] = 4
// original = {"a": 1, "b": 2, "c": 3}
// cloned = {"a": 100, "b": 2, "c": 3, "d": 4}
Use Cases:
✓ Passing maps to untrusted functions
safeCopy := utils.CloneMap(sensitive Data)
process(safeCopy)
✓ Creating base configurations
baseConfig := map[string]any{"port": 8080}
devConfig := utils.CloneMap(baseConfig)
devConfig["debug"] = true
prodConfig := utils.CloneMap(baseConfig)
prodConfig["debug"] = false
Slice Operations
ToAnySlice
Convert typed slice to []any:
// From []string
names := []string{"Alice", "Bob", "Charlie"}
anySlice := utils.ToAnySlice(names)
// []any{"Alice", "Bob", "Charlie"}
// From []int
numbers := []int{1, 2, 3, 4, 5}
anySlice := utils.ToAnySlice(numbers)
// []any{1, 2, 3, 4, 5}
// From custom types
type User struct {
Name string
}
users := []User{{Name: "Alice"}, {Name: "Bob"}}
anySlice := utils.ToAnySlice(users)
// []any{User{Name: "Alice"}, User{Name: "Bob"}}
SlicesConcat
Concatenate multiple slices:
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5}
slice3 := []int{6, 7, 8}
// Concatenate all
result := utils.SlicesConcat(slice1, slice2, slice3)
// []int{1, 2, 3, 4, 5, 6, 7, 8}
// With empty slices
result := utils.SlicesConcat([]int{1, 2}, []int{}, []int{3})
// []int{1, 2, 3}
// No slices
result := utils.SlicesConcat[int]()
// nil
// All empty slices
result := utils.SlicesConcat([]int{}, []int{}, []int{})
// []int{} (non-nil empty slice)
AppendSorted
Insert element into sorted slice:
type Person struct {
Name string
Age int
}
// Less function for sorting by age
byAge := func(a, b Person) bool {
return a.Age < b.Age
}
// Start with sorted slice
people := []Person{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
// Insert new person (maintains sort order)
people = utils.AppendSorted(people, Person{Name: "David", Age: 28}, byAge)
// [{Alice 25}, {David 28}, {Bob 30}, {Charlie 35}]
// Insert at beginning
people = utils.AppendSorted(people, Person{Name: "Eve", Age: 20}, byAge)
// [{Eve 20}, {Alice 25}, {David 28}, {Bob 30}, {Charlie 35}]
// Insert at end
people = utils.AppendSorted(people, Person{Name: "Frank", Age: 40}, byAge)
// [{Eve 20}, {Alice 25}, {David 28}, {Bob 30}, {Charlie 35}, {Frank 40}]
Simple Types:
// Sort integers
numbers := []int{1, 3, 5, 7}
numbers = utils.AppendSorted(numbers, 4, func(a, b int) bool {
return a < b
})
// []int{1, 3, 4, 5, 7}
// Sort strings
names := []string{"Alice", "Charlie", "David"}
names = utils.AppendSorted(names, "Bob", func(a, b string) bool {
return a < b
})
// []string{"Alice", "Bob", "Charlie", "David"}
AppendSortedOptimize
Optimized sorted insertion using binary search for large slices (>= 16 elements):
// For small slices (< 16), uses linear search
small := []int{1, 2, 3, 4, 5}
small = utils.AppendSortedOptimize(small, 3, func(a, b int) bool {
return a < b
})
// For large slices (>= 16), uses binary search
large := make([]int, 100)
for i := range large {
large[i] = i * 2
}
large = utils.AppendSortedOptimize(large, 77, func(a, b int) bool {
return a < b
})
Performance:
- Linear search: O(n) - Better for small slices
- Binary search: O(log n) - Better for large slices
- Threshold: 16 elements
String Operations
CamelToSnake
Convert camelCase to snake_case:
// Simple conversion
result := utils.CamelToSnake("userId") // "user_id"
result = utils.CamelToSnake("firstName") // "first_name"
result = utils.CamelToSnake("isActive") // "is_active"
// Multiple capitals
result = utils.CamelToSnake("HTTPRequest") // "http_request"
result = utils.CamelToSnake("XMLParser") // "xml_parser"
// Already snake_case (no change)
result = utils.CamelToSnake("user_id") // "user_id"
// PascalCase
result = utils.CamelToSnake("UserProfile") // "user_profile"
Use Cases:
// Database column names
type User struct {
UserID int `db:"user_id"`
FirstName string `db:"first_name"`
}
// Generate column name from field
fieldName := "UserID"
columnName := utils.CamelToSnake(fieldName) // "user_id"
// API field name conversion
jsonField := "createdAt"
dbField := utils.CamelToSnake(jsonField) // "created_at"
ParseClientIP
Extract client IP address from HTTP request:
func handler(w http.ResponseWriter, r *http.Request) {
ip := utils.ParseClientIP(r)
// Log client IP
log.Printf("Request from: %s", ip)
// Use for rate limiting
if rateLimiter.IsBlocked(ip) {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
}
Priority Order:
X-Forwarded-Forheader (first IP)X-Real-IPheaderRemoteAddrfrom request
With Proxy:
Client → Proxy → Server
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip
Result: client_ip (first IP in chain)
Security Utilities
HashPassword
Hash password using bcrypt:
// Hash password for storage
password := "mySecretPassword123"
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
// Store hashedPassword in database
user.PasswordHash = hashedPassword
// Later, verify password
err = bcrypt.CompareHashAndPassword(
[]byte(user.PasswordHash),
[]byte(password),
)
if err != nil {
// Invalid password
return ErrInvalidCredentials
}
// Password valid
Complete Authentication Flow:
// Registration
func Register(username, password string) error {
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return err
}
user := &User{
Username: username,
PasswordHash: hashedPassword,
}
return userRepo.Create(user)
}
// Login
func Login(username, password string) (*User, error) {
user, err := userRepo.GetByUsername(username)
if err != nil {
return nil, ErrInvalidCredentials
}
err = bcrypt.CompareHashAndPassword(
[]byte(user.PasswordHash),
[]byte(password),
)
if err != nil {
return nil, ErrInvalidCredentials
}
return user, nil
}
Type Checking
IsNil
Check if a value is nil (handles interfaces correctly):
// Regular nil check
var ptr *int
utils.IsNil(ptr) // true
ptr = new(int)
utils.IsNil(ptr) // false
// Interface nil check (tricky case)
var service Service
utils.IsNil(service) // true
service = (*MyService)(nil)
utils.IsNil(service) // true (nil interface value)
service = &MyService{}
utils.IsNil(service) // false
Why IsNil is Needed:
// Regular nil check fails on interfaces
var service Service = (*MyService)(nil)
// This is true (interface contains nil value)
fmt.Println(service == nil) // false ❌ (interface type is not nil)
// IsNil checks the underlying value
utils.IsNil(service) // true ✅
Use Cases:
// Service initialization check
if utils.IsNil(service) {
service = NewService()
}
// Optional dependency
if !utils.IsNil(logger) {
logger.Log("Message")
}
// Error handling with custom errors
if !utils.IsNil(err) {
return fmt.Errorf("operation failed: %w", err)
}
Best Practices
Map Access
✓ DO: Use GetValueFromMap with appropriate defaults
port := utils.GetValueFromMap(config, "port", 8080)
host := utils.GetValueFromMap(config, "host", "localhost")
✓ DO: Use type parameters for safety
port := utils.GetValueFromMap[int](config, "port", 8080)
✗ DON'T: Access maps directly without type checking
port := config["port"].(int) // BAD: Panics if wrong type
Duration Parsing
✓ DO: Provide sensible defaults
timeout := utils.GetDurationFromMap(config, "timeout", 30*time.Second)
✓ DO: Support multiple formats
// Config can use any format:
// timeout: "30s" (string)
// timeout: 30 (int seconds)
// timeout: 30.5 (float seconds)
✗ DON'T: Assume specific format
timeout := config["timeout"].(time.Duration) // BAD: Only works for Duration type
Slice Operations
✓ DO: Use SlicesConcat for multiple slices
all := utils.SlicesConcat(slice1, slice2, slice3)
✓ DO: Use AppendSorted for maintaining order
sorted = utils.AppendSorted(sorted, newItem, lessFunc)
✗ DON'T: Manually implement concatenation
// BAD: Inefficient
result := make([]int, 0)
result = append(result, slice1...)
result = append(result, slice2...)
result = append(result, slice3...)
String Conversion
✓ DO: Use CamelToSnake for consistent naming
dbColumn := utils.CamelToSnake(structField)
✗ DON'T: Implement custom conversion
// BAD: Incomplete implementation
func toSnake(s string) string {
return strings.ToLower(s) // Doesn't handle capitals
}
Password Security
✓ DO: Always hash passwords before storage
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return err
}
user.PasswordHash = hashedPassword
✗ DON'T: Store plaintext passwords
user.Password = password // BAD: Security vulnerability
✗ DON'T: Use weak hashing
user.PasswordHash = md5(password) // BAD: MD5 is not secure
Examples
Configuration Loading
func LoadConfig(data map[string]any) *Config {
return &Config{
AppName: utils.GetValueFromMap(data, "app_name", "myapp"),
Port: utils.GetValueFromMap(data, "port", 8080),
Host: utils.GetValueFromMap(data, "host", "0.0.0.0"),
Debug: utils.GetValueFromMap(data, "debug", false),
ReadTimeout: utils.GetDurationFromMap(data, "read_timeout", 30*time.Second),
WriteTimeout: utils.GetDurationFromMap(data, "write_timeout", 30*time.Second),
MaxConnections: utils.GetValueFromMap(data, "max_connections", 100),
}
}
Service Configuration
func ServiceFactory(params map[string]any) Service {
host := utils.GetValueFromMap(params, "host", "localhost")
port := utils.GetValueFromMap(params, "port", 5432)
timeout := utils.GetDurationFromMap(params, "timeout", 30*time.Second)
poolSize := utils.GetValueFromMap(params, "pool_size", 10)
return &MyService{
Host: host,
Port: port,
Timeout: timeout,
PoolSize: poolSize,
}
}
Middleware with IP Logging
func IPLoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := utils.ParseClientIP(r)
log.Printf("[%s] %s %s", ip, r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
Rate Limiter
type RateLimiter struct {
requests map[string][]time.Time
mu sync.Mutex
}
func (rl *RateLimiter) Allow(r *http.Request) bool {
ip := utils.ParseClientIP(r)
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
window := now.Add(-time.Minute)
// Get recent requests for this IP
requests := rl.requests[ip]
// Filter old requests
recent := make([]time.Time, 0)
for _, t := range requests {
if t.After(window) {
recent = append(recent, t)
}
}
// Check limit
if len(recent) >= 60 {
return false
}
// Add new request
recent = append(recent, now)
rl.requests[ip] = recent
return true
}
Sorted Insert Example
type Event struct {
Name string
Timestamp time.Time
}
type EventLog struct {
events []Event
mu sync.Mutex
}
func (el *EventLog) Add(event Event) {
el.mu.Lock()
defer el.mu.Unlock()
// Maintain chronological order
el.events = utils.AppendSorted(el.events, event, func(a, b Event) bool {
return a.Timestamp.Before(b.Timestamp)
})
}
func (el *EventLog) GetRecent(n int) []Event {
el.mu.Lock()
defer el.mu.Unlock()
if len(el.events) <= n {
return el.events
}
return el.events[len(el.events)-n:]
}
User Registration
func Register(username, email, password string) error {
// Hash password
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
// Create user
user := &User{
ID: uuid.New().String(),
Username: username,
Email: email,
PasswordHash: hashedPassword,
CreatedAt: time.Now(),
}
// Save to database
return userRepo.Create(user)
}
Related Documentation
- Helpers Overview - All helper packages
- Cast Package - Type conversion utilities
- Validator Package - Struct validation
Next: Validator Package - Struct validation with tags