Slim

misc - Utility Library

misc is a utility library providing common utility functions and types, including password hashing, function composition, MIME type handling, template interpolation, generic utilities, math functions, and more. Minimum supported Go version: 1.21.0.

Installation

go get -u go-slim.dev/misc

Quick Start

package mainimport "go-slim.dev/misc"func main() {    // Password hashing    hash, _ := misc.PasswordHash("mypassword")    ok := misc.PasswordVerify("mypassword", hash)    // Template substitution    result, _ := misc.Substitute("Hello {name}!", map[string]any{"name": "World"})    // Generic utilities    value := misc.Coalesce("", "default", "fallback")  // "default"    // Math functions    min, max := misc.MinMax(5, 3)  // (3, 5)}

Core Features

1. Password Hashing and Digests

Password Hashing (Bcrypt)

Use bcrypt algorithm for secure password hashing:

import "go-slim.dev/misc"// Generate password hashhash, err := misc.PasswordHash("mypassword")if err != nil {    // Handle error}// Verify passwordok := misc.PasswordVerify("mypassword", hash)if ok {    // Password correct} else {    // Password incorrect}

Features:

  • Uses bcrypt.DefaultCost (currently 10)
  • Automatically generates salt
  • Protects against rainbow table attacks
  • Adjustable computational cost

Fast Hash Functions

For data integrity checking and non-security scenarios:

// MD5 (not recommended for security scenarios)md5Hash := misc.MD5("hello world")// Returns uppercase hex: "5EB63BBBE01EEED093CB22BB8F5ACDC3"// SHA-1sha1Hash := misc.Sha1("hello world")// Returns lowercase hex: "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"// SHA-256 (recommended)sha256Hash := misc.Sha256("hello world")// Returns lowercase hex: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Use Cases:

  • File integrity checking
  • Cache key generation
  • Data fingerprinting
  • ETag generation

Note: MD5 and SHA-1 should not be used for security-sensitive scenarios (like password storage). Use SHA-256 or bcrypt instead.

2. Function Composition

Call - Sequential Execution

Execute functions sequentially, returns immediately on first error:

import "go-slim.dev/misc"err := misc.Call(    func() error {        fmt.Println("Step 1")        return nil    },    func() error {        fmt.Println("Step 2")        return nil    },    func() error {        fmt.Println("Step 3")        return errors.New("failed")    },    func() error {        fmt.Println("Step 4")  // Won't execute        return nil    },)// Output: Step 1, Step 2, Step 3// err is "failed"

CallG - Sequential Execution with Parameters

Execute multiple functions that receive the same parameter:

type User struct {    Name string    Age  int}user := &User{Name: "Alice", Age: 30}err := misc.CallG(user,    func(u *User) error {        fmt.Printf("Validating user: %s\n", u.Name)        return nil    },    func(u *User) error {        fmt.Printf("Saving user: %s\n", u.Name)        return db.Save(u)    },    func(u *User) error {        fmt.Printf("Sending welcome email to: %s\n", u.Name)        return mailer.Send(u)    },)

Wrap and WrapG - Function Wrapping

Combine multiple functions into one reusable function:

// Wrap - no parametersinitFunc := misc.Wrap(    initDatabase,    initCache,    initLogger,)// Call multiple timesif err := initFunc(); err != nil {    log.Fatal(err)}// WrapG - with parametersprocessUser := misc.WrapG(    validateUser,    saveUser,    notifyUser,)// Use multiple timesfor _, user := range users {    if err := processUser(user); err != nil {        log.Printf("Failed to process user: %v", err)    }}

3. Template Interpolation

Substitute - Simple Template Replacement

Use {} as placeholders:

// Basic usageresult, err := misc.Substitute("Hello {name}!", map[string]any{    "name": "World",})// result: "Hello World!"// Multiple placeholdersresult, _ := misc.Substitute(    "User {name} is {age} years old",    map[string]any{        "name": "Alice",        "age":  30,    },)// result: "User Alice is 30 years old"// Missing placeholders remain unchangedresult, _ := misc.Substitute(    "Hello {name}, {missing}",    map[string]any{"name": "World"},)// result: "Hello World, {missing}"

Interpolate - Custom Delimiters

Use custom start and end markers:

// Use {{ }} delimitersresult, _ := misc.Interpolate(    "/user/{{ID}}/posts/{{PostID}}",    "{{", "}}",    map[string]any{        "ID":     123,        "PostID": 456,    },)// result: "/user/123/posts/456"// Use [[ ]] delimitersresult, _ := misc.Interpolate(    "SELECT * FROM users WHERE id = [[id]]",    "[[", "]]",    map[string]any{"id": 42},)// result: "SELECT * FROM users WHERE id = 42"// Wildcard "*" as default valueresult, _ := misc.Interpolate(    "{greeting} {name}!",    "{", "}",    map[string]any{        "name": "Alice",        "*":    "Unknown",  // Default value    },)// result: "Unknown Alice!"

Tmpl - Advanced Template Processing

Use custom TagFunc to process placeholders:

import (    "io"    "strings"    "go-slim.dev/misc")var output strings.Builder// Custom tag handler functiontagFunc := func(w io.Writer, tag string) (int, error) {    switch tag {    case "date":        return w.Write([]byte(time.Now().Format("2006-01-02")))    case "time":        return w.Write([]byte(time.Now().Format("15:04:05")))    case "upper":        // Can access context or execute complex logic        return w.Write([]byte("HELLO"))    default:        return w.Write([]byte("Unknown tag"))    }}n, err := misc.Tmpl(    "Date: {date}, Time: {time}, Message: {upper}",    "{", "}",    &output,    tagFunc,)fmt.Println(output.String())// Output: Date: 2024-01-15, Time: 14:30:45, Message: HELLO

4. Generic Utility Functions

Zero - Get Zero Value

Returns the zero value of any type:

import "go-slim.dev/misc"zeroInt := misc.Zero[int]()           // 0zeroStr := misc.Zero[string]()        // ""zeroBool := misc.Zero[bool]()         // falsezeroPtr := misc.Zero[*int]()          // nilzeroSlice := misc.Zero[[]int]()       // nilzeroMap := misc.Zero[map[string]int]() // nil// Use in generic functionsfunc GetOrDefault[T any](m map[string]T, key string) T {    if v, ok := m[key]; ok {        return v    }    return misc.Zero[T]()}

Ptr - Create Pointer

Returns a pointer to a copy of the value:

// Basic typesintPtr := misc.Ptr(42)           // *intstrPtr := misc.Ptr("hello")      // *stringboolPtr := misc.Ptr(true)        // *bool// Use in struct literalstype Config struct {    Port    *int    Host    *string    Enabled *bool}cfg := Config{    Port:    misc.Ptr(8080),    Host:    misc.Ptr("localhost"),    Enabled: misc.Ptr(true),}// API call scenariosupdateUser(&User{    ID:   123,    Name: misc.Ptr("Alice"),  // Only update Name field})

Nil - Get Typed Nil Pointer

Returns a nil pointer of specific type:

var intPtr *int = misc.Nil[int]()var strPtr *string = misc.Nil[string]()var slicePtr *[]int = misc.Nil[[]int]()// Use in generic functionsfunc MaybeValue[T any](hasValue bool, value T) *T {    if hasValue {        return &value    }    return misc.Nil[T]()}

IsZero - Zero Value Check

Check if any value is zero (supports deep checking):

misc.IsZero(0)                  // truemisc.IsZero("")                 // truemisc.IsZero(false)              // truemisc.IsZero((*int)(nil))        // truemisc.IsZero([]int(nil))         // truemisc.IsZero(map[string]int(nil)) // truemisc.IsZero(42)                 // falsemisc.IsZero("hello")            // falsemisc.IsZero(true)               // falsemisc.IsZero(&User{})            // false (pointer not nil)// Pointer is recursively dereferencedptr := new(int)  // *int pointing to 0misc.IsZero(ptr) // true (dereferenced value is 0)*ptr = 42misc.IsZero(ptr) // false (dereferenced value is 42)

IsNil - Nil Check

Check if value is nil (applicable to reference types):

misc.IsNil(nil)                    // truemisc.IsNil((*int)(nil))            // truemisc.IsNil([]int(nil))             // truemisc.IsNil(map[string]int(nil))    // truemisc.IsNil((chan int)(nil))        // truemisc.IsNil((func())(nil))          // truemisc.IsNil(0)                      // falsemisc.IsNil("")                     // falsemisc.IsNil(make([]int, 0))         // false (empty slice, not nil)misc.IsNil(make(map[string]int))   // false (empty map, not nil)// Interface valuesvar err error = nilmisc.IsNil(err)                    // trueerr = fmt.Errorf("error")misc.IsNil(err)                    // false

Coalesce - Get First Non-Zero Value

Returns the first non-zero value in the parameter list:

// String default valuename := misc.Coalesce("", "default", "fallback")// name = "default"// Integer default valueport := misc.Coalesce(0, 8080, 3000)// port = 8080// Configuration priority: CLI > environment variable > config file > defaultport := misc.Coalesce(    cliPort,      // CLI argument    envPort,      // Environment variable    configPort,   // Config file    8080,         // Default)// User input fallbackusername := misc.Coalesce(    userInput,    savedUsername,    "guest",)// When all are zero values, return zero valueresult := misc.Coalesce[int]()  // 0result := misc.Coalesce("", "", "")  // ""

5. Math Functions

MinMax - Get Min and Max Simultaneously

Returns the minimum and maximum of two values:

import "go-slim.dev/misc"// Integermin, max := misc.MinMax(5, 3)// min = 3, max = 5// Floatmin, max := misc.MinMax(1.5, 2.7)// min = 1.5, max = 2.7// String (lexicographic order)min, max := misc.MinMax("banana", "apple")// min = "apple", max = "banana"// Equal valuesmin, max := misc.MinMax(10, 10)// min = 10, max = 10// Practical applicationfunc normalizeRange(a, b int) (start, end int) {    return misc.MinMax(a, b)}start, end := normalizeRange(100, 50)// start = 50, end = 100

Clamp - Constrain Value in Range

Constrains value within [min, max] range:

// Basic usageresult := misc.Clamp(15, 10, 20)   // 15 (within range)result := misc.Clamp(5, 10, 20)    // 10 (below minimum)result := misc.Clamp(25, 10, 20)   // 20 (above maximum)// Auto-handle boundary reversalresult := misc.Clamp(15, 20, 10)   // 15 (auto-adjusts boundaries to 10, 20)// Floatopacity := misc.Clamp(1.5, 0.0, 1.0)  // 1.0// Practical: constrain percentagefunc setVolume(vol int) int {    return misc.Clamp(vol, 0, 100)}// Constrain color valuefunc normalizeColor(c int) int {    return misc.Clamp(c, 0, 255)}// Constrain coordinatesx := misc.Clamp(mouseX, 0, screenWidth)y := misc.Clamp(mouseY, 0, screenHeight)

6. Zero-Copy Conversion

Use unsafe for zero-copy string and byte slice conversion:

import "go-slim.dev/misc"// Byte slice to stringbytes := []byte("hello world")str := misc.BytesToString(bytes)// Zero-copy conversion, shares underlying data// String to byte slicestr := "hello world"bytes := misc.StringToBytes(str)// Zero-copy conversion, shares underlying data

⚠️ Important Warnings:

  1. Don’t modify source data: Converted values share underlying memory, modification causes undefined behavior
  2. Don’t modify result: StringToBytes returns a read-only slice
  3. Lifetime: Ensure source data remains valid while using result

Correct Usage:

// ✅ Correct: read-only usebytes := []byte("hello")str := misc.BytesToString(bytes)fmt.Println(str)  // Safe// ✅ Correct: temporary useif misc.BytesToString(data) == "expected" {    // Safe}// ❌ Wrong: modify source databytes := []byte("hello")str := misc.BytesToString(bytes)bytes[0] = 'H'  // Undefined behavior! str may change// ❌ Wrong: modify resultstr := "hello"bytes := misc.StringToBytes(str)bytes[0] = 'H'  // Undefined behavior! May crash or corrupt memory

Use Cases:

// Performance-sensitive string comparisonfunc fastCompare(a []byte, b string) bool {    return misc.BytesToString(a) == b}// HTTP header parsing (read-only)func parseHeader(line []byte) (key, value string) {    parts := bytes.SplitN(line, []byte(":"), 2)    return misc.BytesToString(parts[0]),           misc.BytesToString(bytes.TrimSpace(parts[1]))}// JSON parsing optimizationfunc parseField(data []byte) string {    // data obtained from pool, returned to pool immediately after use    return misc.BytesToString(data)  // Dangerous! data may be reused}// Safer approachfunc parseFieldSafe(data []byte) string {    return string(data)  // Standard conversion, creates copy}

7. MIME Type Handling

Deprecated: Recommend using github.com/h2non/filetype library.

// ExtensionByType - Get extension from MIME typeext := misc.ExtensionByType("image/png")  // ".png"ext := misc.ExtensionByType("video/mp4")  // ".mp4"// TypeByExtension - Get MIME type from extensionmimeType := misc.TypeByExtension(".jpg")   // "image/jpeg"mimeType := misc.TypeByExtension("png")    // "image/png" (can omit dot)// CharsetByType - Get charset (limited functionality)charset := misc.CharsetByType("text/html")  // "charset=utf-8"charset := misc.CharsetByType("image/png")  // ""

8. Stack Trace

Deprecated: Recommend using runtime.Stack or debug.Stack.

// Stack - Get stack trace with source linestrace := misc.Stack(0)  // 0 means start from callerfmt.Println(trace)

Use Cases

1. User Authentication in Web Applications

import (    "go-slim.dev/misc"    "go-slim.dev/slim")type User struct {    Username string    Password string}// User registrationfunc registerUser(c slim.Context) error {    var user User    if err := c.Bind(&user); err != nil {        return err    }    // Generate password hash    hash, err := misc.PasswordHash(user.Password)    if err != nil {        return c.String(500, "Password encryption failed")    }    // Save to database    user.Password = hash    if err := db.Save(&user); err != nil {        return c.String(500, "Save failed")    }    return c.String(200, "Registration successful")}// User loginfunc loginUser(c slim.Context) error {    var input User    if err := c.Bind(&input); err != nil {        return err    }    // Get user from database    var user User    if err := db.FindByUsername(input.Username, &user); err != nil {        return c.String(401, "Incorrect username or password")    }    // Verify password    if !misc.PasswordVerify(input.Password, user.Password) {        return c.String(401, "Incorrect username or password")    }    // Generate token and return    token := generateToken(user)    return c.JSON(200, map[string]string{"token": token})}

2. Configuration Management

import (    "os"    "strconv"    "go-slim.dev/misc")type Config struct {    Host     string    Port     int    Database string    Debug    bool}func loadConfig() *Config {    // Use Coalesce to implement configuration priority    // CLI > environment variable > default    portStr := misc.Coalesce(        os.Getenv("APP_PORT"),        "8080",    )    port, _ := strconv.Atoi(portStr)    return &Config{        Host: misc.Coalesce(            os.Getenv("APP_HOST"),            "localhost",        ),        Port: port,        Database: misc.Coalesce(            os.Getenv("DATABASE_URL"),            "postgres://localhost/myapp",        ),        Debug: os.Getenv("DEBUG") == "true",    }}

3. URL Routing Templates

import "go-slim.dev/misc"type Router struct {    baseURL string}func (r *Router) UserProfile(userID int) string {    url, _ := misc.Substitute(        r.baseURL+"/users/{id}/profile",        map[string]any{"id": userID},    )    return url}func (r *Router) UserPosts(userID, page int) string {    url, _ := misc.Interpolate(        r.baseURL+"/users/{{userID}}/posts?page={{page}}",        "{{", "}}",        map[string]any{            "userID": userID,            "page":   page,        },    )    return url}// Usagerouter := &Router{baseURL: "https://api.example.com"}profileURL := router.UserProfile(123)// "https://api.example.com/users/123/profile"postsURL := router.UserPosts(123, 2)// "https://api.example.com/users/123/posts?page=2"

4. Data Validation Pipeline

import "go-slim.dev/misc"type Order struct {    ID       int    UserID   int    Amount   float64    Status   string}func validateOrder(order *Order) error {    return misc.Call(        func() error {            if order.UserID <= 0 {                return errors.New("Invalid user ID")            }            return nil        },        func() error {            if order.Amount <= 0 {                return errors.New("Amount must be greater than 0")            }            return nil        },        func() error {            if order.Status == "" {                return errors.New("Status cannot be empty")            }            return nil        },    )}func processOrder(order *Order) error {    return misc.CallG(order,        validateOrder,        saveOrder,        sendConfirmation,        updateInventory,    )}

5. Email Templates

import "go-slim.dev/misc"type EmailTemplate struct {    template string}func (t *EmailTemplate) Render(data map[string]any) (string, error) {    return misc.Substitute(t.template, data)}// Welcome emailwelcomeEmail := &EmailTemplate{    template: `Dear {name},Welcome to {company}!Your account has been successfully created:- Username: {username}- Email: {email}Please visit {url} to complete activation.Best regards,{company} Team`,}content, _ := welcomeEmail.Render(map[string]any{    "name":     "Alice",    "company":  "TechCorp",    "username": "alice123",    "email":    "alice@example.com",    "url":      "https://example.com/activate",})

6. Input Range Limiting

import "go-slim.dev/misc"// Image processingtype Image struct {    Width  int    Height int}func (img *Image) SetBrightness(value float64) {    // Limit brightness between 0.0 and 2.0    img.brightness = misc.Clamp(value, 0.0, 2.0)}func (img *Image) SetPixel(x, y int, color RGB) {    // Limit coordinates within image bounds    x = misc.Clamp(x, 0, img.Width-1)    y = misc.Clamp(y, 0, img.Height-1)    // Limit color values to 0-255 range    r := misc.Clamp(color.R, 0, 255)    g := misc.Clamp(color.G, 0, 255)    b := misc.Clamp(color.B, 0, 255)    img.setPixelUnsafe(x, y, RGB{r, g, b})}// Volume controltype AudioPlayer struct {    volume int}func (p *AudioPlayer) SetVolume(vol int) {    p.volume = misc.Clamp(vol, 0, 100)}func (p *AudioPlayer) IncreaseVolume(delta int) {    p.volume = misc.Clamp(p.volume+delta, 0, 100)}

7. File Integrity Checking

import (    "io"    "os"    "go-slim.dev/misc")func calculateFileHash(filename string) (string, error) {    data, err := os.ReadFile(filename)    if err != nil {        return "", err    }    // Use SHA-256 to calculate file hash    return misc.Sha256(string(data)), nil}func verifyFileIntegrity(filename, expectedHash string) (bool, error) {    actualHash, err := calculateFileHash(filename)    if err != nil {        return false, err    }    return actualHash == expectedHash, nil}// Usage exampleok, err := verifyFileIntegrity("download.zip", "b94d27b99...")if !ok {    log.Fatal("File corrupted or tampered with")}

API Reference

Password and Digests

// Password hashing (bcrypt)func PasswordHash(password string) (string, error)func PasswordVerify(password, hash string) bool// Fast hashingfunc MD5(str string) string      // Returns uppercase hexfunc Sha1(str string) string     // Returns lowercase hexfunc Sha256(str string) string   // Returns lowercase hex

Function Composition

func Call(fns ...func() error) errorfunc CallG[T any](val T, fns ...func(T) error) errorfunc Wrap(fns ...func() error) func() errorfunc WrapG[T any](fns ...func(T) error) func(val T) error

Template Interpolation

func Substitute(template string, data map[string]any) (string, error)func Interpolate(template, startTag, endTag string, data map[string]any) (string, error)func Tmpl(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error)type TagFunc func(w io.Writer, tag string) (int, error)

Generic Utilities

func Zero[T any]() Tfunc Ptr[T any](x T) *Tfunc Nil[T any]() *Tfunc IsZero[T any](v T) boolfunc IsNil(x any) boolfunc Coalesce[T any](values ...T) T

Math Functions

func MinMax[T cmp.Ordered](a, b T) (T, T)func Clamp[T cmp.Ordered](val, minT, maxT T) T

Zero-Copy Conversion

func BytesToString(b []byte) stringfunc StringToBytes(s string) []byte

MIME Types (Deprecated)

func ExtensionByType(mimeType string) stringfunc TypeByExtension(ext string) stringfunc CharsetByType(typ string) string

Stack Trace (Deprecated)

func Stack(skip int) string

Notes

  1. Password Hashing Performance:

    • bcrypt is intentionally slow to prevent brute-force attacks
    • Don’t call frequently in performance-sensitive code paths
    • Default cost is 10, each increment doubles the time
  2. Hash Function Selection:

    • Security scenarios (passwords, signatures): Use PasswordHash or SHA-256
    • Non-security scenarios (cache keys, ETag): Can use MD5 or SHA-1
    • MD5 and SHA-1 should not be used for password storage
  3. Zero-Copy Conversion Risks:

    • BytesToString and StringToBytes use unsafe
    • Share underlying memory, modifying source data causes undefined behavior
    • Only use when certain data won’t be modified
    • Significant performance improvement but requires caution
  4. IsZero vs IsNil:

    • IsZero: Checks if it’s the type’s zero value (like 0, "", false, nil)
    • IsNil: Only checks if reference type is nil
    • IsZero recursively dereferences pointers
  5. Coalesce Behavior:

    • Returns first non-zero value, not first non-nil value
    • Empty string "" is zero value, will be skipped
    • Empty slice []int{} is not zero value, will be returned
  6. Function Composition Order:

    • Call and CallG execute functions in order
    • Stops at first error
    • Subsequent functions won’t execute
  7. Template Interpolation Security:

    • Substitute doesn’t perform HTML escaping
    • Need to escape manually if using in HTML
    • Avoid directly inserting untrusted input into templates
  8. Clamp Boundary Handling:

    • Automatically handles min > max case (will swap boundaries)
    • No need to manually check boundary order before use

Performance Recommendations

  1. Password Hashing: Process asynchronously, avoid blocking main flow
  2. Zero-Copy Conversion: Only use in performance-critical paths, mind security
  3. Function Composition: Avoid using Wrap in loops, should create outside loop
  4. Template Interpolation: Consider caching results or using text/template for frequent use
Navigate Select esc Close search