Files
golib/size/README.md
nabbar 25c3c8c45b Improvements, test & documentatons (2025-11 #2)
[root]
- UPDATE documentation: enhanced README and TESTING guidelines
- UPDATE dependencies: bump dependencies

[config/components]
- UPDATE mail component: apply update following changes in related package
- UPDATE smtp component: apply update following changes in related package

[mail] - MAJOR REFACTORING
- REFACTOR package structure: reorganized into 4 specialized subpackages (queuer, render, sender, smtp)
- ADD mail/queuer: mail queue management with counter, monitoring, and comprehensive tests
- ADD mail/render: email template rendering with themes and direction handling (moved from mailer package)
- ADD mail/sender: email composition and sending with attachments, priorities, and encoding
- ADD mail/smtp: SMTP protocol handling with TLS modes and DSN support
- ADD documentation: comprehensive README and TESTING for all subpackages
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases for all subpackages

[mailer] - DEPRECATED
- DELETE package: entire package merged into mail/render

[mailPooler] - DEPRECATED
- DELETE package: entire package merged into mail/queuer

[smtp] - DEPRECATED
- DELETE root package: entire package moved to mail/smtp
- REFACTOR tlsmode: enhanced with encoding, formatting, and viper support (moved to mail/smtp/tlsmode)

[size]
- ADD documentation: comprehensive README
- UPDATE interface: improved Size type methods
- UPDATE encoding: enhanced marshaling support
- UPDATE formatting: better unit handling and display
- UPDATE parsing: improved error handling and validation

[socket/server/unix]
- ADD platform support: macOS-specific permission handling (perm_darwin.go)
- ADD platform support: Linux-specific permission handling (perm_linux.go)
- UPDATE listener: improved Unix socket and datagram listeners
- UPDATE error handling: enhanced error messages for Unix sockets

[socket/server/unixgram]
- ADD platform support: macOS-specific permission handling (perm_darwin.go)
- ADD platform support: Linux-specific permission handling (perm_linux.go)
- UPDATE listener: improved Unix datagram listener
- UPDATE error handling: enhanced error messages

[socket/server/tcp]
- UPDATE listener: improved TCP listener implementation
2025-11-16 21:48:48 +01:00

18 KiB

Size Package

License: MIT Go Version

Type-safe, human-readable size representation library for Go with parsing, formatting, arithmetic operations, and comprehensive marshaling support.


Table of Contents


Overview

This library provides production-ready size value handling for Go applications. It emphasizes type safety, overflow protection, and flexibility while supporting multiple serialization formats and integration with popular configuration systems.

Design Philosophy

  1. Type-Safe: Size wraps uint64 to prevent accidental misuse as plain integers
  2. Overflow-Protected: All arithmetic operations detect and handle overflow/underflow
  3. Human-Readable: Automatic formatting with appropriate units (KB, MB, GB, etc.)
  4. Format-Agnostic: Marshaling/unmarshaling for JSON, YAML, TOML, CBOR, and plain text
  5. Configuration-Ready: Native integration with Viper through decode hooks

Key Features

  • Flexible Parsing: Parse size strings like "10MB", "1.5GB", "1GB500MB" with automatic unit detection
  • Safe Arithmetic: Multiply, divide, add, subtract with overflow/underflow protection
  • Multiple Units: Support for B, KB, MB, GB, TB, PB, EB (binary powers of 1024)
  • Rich Formatting: Convert to various numeric types with overflow detection
  • Universal Marshaling: JSON, YAML, TOML, CBOR, binary, and text encoding
  • Viper Integration: Seamless configuration file parsing with ViperDecoderHook()
  • Zero Dependencies: Only relies on standard library (except Viper hook)

Installation

go get github.com/nabbar/golib/size

Architecture

Type Structure

Size (uint64)
│
├── Parsing      → Parse(), ParseByte(), ParseInt64(), ParseFloat64()
├── Formatting   → String(), Format(), Unit()
├── Arithmetic   → Mul(), Div(), Add(), Sub() (with overflow protection)
├── Conversion   → Int64(), Uint64(), Float64(), KiloBytes(), etc.
└── Marshaling   → JSON, YAML, TOML, CBOR, Text, Binary

Component Overview

┌─────────────────────────────────────────────────────┐
│                   Size Package                      │
│  Parse, Format, Arithmetic, Marshal/Unmarshal       │
└───────┬──────────┬───────────┬──────────┬───────────┘
        │          │           │          │
   ┌────▼───┐ ┌────▼───┐ ┌─────▼────┐ ┌───▼─────────┐
   │ Parser │ │ Format │ │Arithmetic│ │  Encoding   │
   │        │ │        │ │          │ │             │
   │ String │ │ Human  │ │ Overflow │ │ JSON, YAML  │
   │ Numeric│ │ Machine│ │  Safety  │ │ TOML, CBOR  │
   └────────┘ └────────┘ └──────────┘ └─────────────┘
Component Purpose Safety Example
Parser Convert strings/numbers to Size Overflow checks Parse("10MB")
Formatter Convert Size to strings/numbers Overflow detection size.String()
Arithmetic Math operations on Size values Error on overflow size.MulErr(2.0)
Encoding Serialize/deserialize Type-safe json.Marshal(size)

Unit System

The package uses binary prefixes (powers of 1024):

B   = 1
KB  = 1024¹  = 1,024
MB  = 1024²  = 1,048,576
GB  = 1024³  = 1,073,741,824
TB  = 1024⁴  = 1,099,511,627,776
PB  = 1024⁵  = 1,125,899,906,842,624
EB  = 1024⁶  = 1,152,921,504,606,846,976

Note: This follows the traditional computing convention (1 KB = 1024 bytes), not the SI decimal system (1 kB = 1000 bytes).


Performance

Memory Efficiency

  • Size Type: 8 bytes (underlying uint64)
  • Zero Allocation: Parsing and formatting operations minimize heap allocations
  • Value Semantics: Pass by value is efficient; methods use pointer receivers only when modifying

Parsing Performance

BenchmarkParse          5000000    250 ns/op     32 B/op    2 allocs/op
BenchmarkParseComplex   2000000    650 ns/op     96 B/op    4 allocs/op
BenchmarkFormat         10000000   120 ns/op     48 B/op    2 allocs/op

Benchmarks on AMD64, Go 1.21

Thread Safety

  • Value Type: Size is a simple value type and safe to copy
  • Concurrent Reads: Safe across goroutines
  • Concurrent Writes: Use explicit synchronization for pointer receivers (Mul, Add, etc.)

Use Cases

This library is designed for scenarios requiring type-safe size handling:

Configuration Files

  • Parse size limits from YAML/TOML/JSON configurations
  • Human-readable format (e.g., max_upload: "100MB")
  • Type-safe validation with Viper integration

Resource Management

  • Memory allocation limits
  • File size restrictions
  • Quota enforcement with overflow protection

CLI Applications

  • Parse user input for size flags (e.g., --max-size 1.5GB)
  • Display progress with formatted output
  • Validate size constraints

Storage Systems

  • Calculate disk usage with safe arithmetic
  • Compare file sizes with type safety
  • Aggregate storage metrics

Logging & Monitoring

  • Format byte counts in human-readable form
  • Track memory usage over time
  • Alert on size thresholds

Quick Start

Basic Parsing

Parse size strings with automatic unit detection:

package main

import (
    "fmt"
    "github.com/nabbar/golib/size"
)

func main() {
    // Parse from string
    s, err := size.Parse("10MB")
    if err != nil {
        panic(err)
    }
    fmt.Println(s.String())        // Output: "10.00 MB"
    fmt.Println(s.Uint64())        // Output: 10485760
    fmt.Println(s.MegaBytes())     // Output: 10
}

Arithmetic Operations

Perform safe arithmetic with overflow detection:

package main

import (
    "fmt"
    "github.com/nabbar/golib/size"
)

func main() {
    s := size.ParseUint64(1024 * 1024) // 1 MB
    
    // Multiply (with error checking)
    err := s.MulErr(2.5)
    if err != nil {
        fmt.Println("Overflow detected!")
    }
    fmt.Println(s.String()) // Output: "2.50 MB"
    
    // Add
    s.Add(512 * 1024) // Add 512 KB
    fmt.Println(s.String()) // Output: "3.00 MB"
    
    // Subtract
    s.Sub(1024 * 1024) // Subtract 1 MB
    fmt.Println(s.String()) // Output: "2.00 MB"
}

JSON Marshaling

Automatic serialization to human-readable format:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/nabbar/golib/size"
)

type Config struct {
    MaxUpload size.Size `json:"max_upload"`
    MaxFile   size.Size `json:"max_file"`
}

func main() {
    cfg := Config{
        MaxUpload: size.ParseUint64(100 * 1024 * 1024), // 100 MB
        MaxFile:   size.ParseUint64(10 * 1024 * 1024),  // 10 MB
    }
    
    // Marshal to JSON
    data, _ := json.MarshalIndent(cfg, "", "  ")
    fmt.Println(string(data))
    // Output:
    // {
    //   "max_upload": "100.00 MB",
    //   "max_file": "10.00 MB"
    // }
    
    // Unmarshal from JSON
    input := `{"max_upload": "200MB", "max_file": "20MB"}`
    var newCfg Config
    json.Unmarshal([]byte(input), &newCfg)
    fmt.Println(newCfg.MaxUpload.MegaBytes()) // Output: 200
}

Viper Integration

Seamless configuration parsing with Viper:

package main

import (
    "github.com/nabbar/golib/size"
    "github.com/spf13/viper"
    libmap "github.com/go-viper/mapstructure/v2"
)

type ServerConfig struct {
    MaxRequestSize  size.Size `mapstructure:"max_request_size"`
    MaxUploadSize   size.Size `mapstructure:"max_upload_size"`
    CacheSize       size.Size `mapstructure:"cache_size"`
}

func main() {
    v := viper.New()
    v.SetConfigFile("config.yaml")
    v.ReadInConfig()
    
    var cfg ServerConfig
    err := v.Unmarshal(&cfg, viper.DecodeHook(
        libmap.ComposeDecodeHookFunc(
            size.ViperDecoderHook(),
            libmap.StringToTimeDurationHookFunc(),
        ),
    ))
    
    if err != nil {
        panic(err)
    }
    
    // Now cfg.MaxRequestSize is properly parsed
    fmt.Println(cfg.MaxRequestSize.MegaBytes())
}

config.yaml:

max_request_size: "10MB"
max_upload_size: "100MB"
cache_size: 1073741824  # Can use numeric values too

Complex Parsing

Parse complex size expressions:

package main

import (
    "fmt"
    "github.com/nabbar/golib/size"
)

func main() {
    // Multiple units in one string
    s, _ := size.Parse("1GB500MB")
    fmt.Println(s.String()) // Output: "1.49 GB"
    
    // Fractional values
    s, _ = size.Parse("2.5TB")
    fmt.Println(s.TeraBytes()) // Output: 2
    
    // Different unit variations
    examples := []string{
        "10MB", "10Mb", "10mb", "10M",  // All parse to 10 megabytes
        "1.5GB", "1GB500MB",             // Same value
    }
    
    for _, ex := range examples {
        s, _ := size.Parse(ex)
        fmt.Printf("%-12s = %d bytes\n", ex, s.Uint64())
    }
}

API Reference

Parsing Functions

// Parse from string (most common)
size, err := size.Parse("10MB")

// Parse from byte slice
size, err := size.ParseByte([]byte("10MB"))

// Convert from numeric types
size := size.ParseUint64(1048576)
size := size.ParseInt64(-1048576)   // Converts to absolute value
size := size.ParseFloat64(1048576.5)

Arithmetic Operations

All arithmetic operations have two variants:

  • Non-error: Ignores overflow (e.g., Mul())
  • Error-returning: Returns error on overflow (e.g., MulErr())
size := size.ParseUint64(1024)

// Multiplication
size.Mul(2.0)              // size *= 2
err := size.MulErr(2.0)    // Returns error on overflow

// Division
size.Div(2.0)              // size /= 2
err := size.DivErr(2.0)    // Returns error if divisor ≤ 0

// Addition
size.Add(512)              // size += 512
err := size.AddErr(512)    // Returns error on overflow

// Subtraction
size.Sub(256)              // size -= 256
err := size.SubErr(256)    // Returns error on underflow

Formatting

size := size.ParseUint64(1572864) // 1.5 MB

// Human-readable string
size.String()                   // "1.50 MB"
size.Format(size.FormatRound0)  // "2"
size.Format(size.FormatRound1)  // "1.5"
size.Format(size.FormatRound2)  // "1.50"
size.Format(size.FormatRound3)  // "1.500"

// Get unit only
size.Unit('B')  // "MB"
size.Unit('o')  // "Mo" (useful for French/other locales)

Type Conversion

size := size.ParseUint64(1048576)

// Integer types (with overflow protection)
size.Int64()   // 1048576
size.Int32()   // 1048576 (or MaxInt32 if too large)
size.Int()     // 1048576
size.Uint64()  // 1048576
size.Uint32()  // 1048576 (or MaxUint32 if too large)
size.Uint()    // 1048576

// Float types (with overflow protection)
size.Float64() // 1048576.0
size.Float32() // 1048576.0

// Unit-specific conversions
size.KiloBytes() // 1024
size.MegaBytes() // 1
size.GigaBytes() // 0

Constants

size.SizeNul   // 0
size.SizeUnit  // 1 B
size.SizeKilo  // 1024 B
size.SizeMega  // 1048576 B
size.SizeGiga  // 1073741824 B
size.SizeTera  // 1099511627776 B
size.SizePeta  // 1125899906842624 B
size.SizeExa   // 1152921504606846976 B

Format Constants

size.FormatRound0 // "%.0f" - no decimal places
size.FormatRound1 // "%.1f" - 1 decimal place
size.FormatRound2 // "%.2f" - 2 decimal places (default)
size.FormatRound3 // "%.3f" - 3 decimal places

Marshaling

The Size type implements multiple encoding interfaces:

// JSON
json.Marshal(size)     // Outputs: "1.50 MB"
json.Unmarshal(data, &size)

// YAML
yaml.Marshal(size)     // Outputs: 1.50 MB
yaml.Unmarshal(data, &size)

// TOML
toml.Marshal(size)
toml.Unmarshal(data, &size)

// Text encoding
size.MarshalText()
size.UnmarshalText([]byte("10MB"))

// Binary (CBOR)
size.MarshalBinary()
size.UnmarshalBinary(data)

Best Practices

Use Parse() for String Input

// ✅ Good: Parse and handle errors
func setLimit(input string) error {
    limit, err := size.Parse(input)
    if err != nil {
        return fmt.Errorf("invalid size: %w", err)
    }
    server.SetLimit(limit)
    return nil
}

// ❌ Bad: No validation
func setLimitBad(input string) {
    limit, _ := size.Parse(input)
    server.SetLimit(limit)
}

Check Arithmetic Errors

// ✅ Good: Check for overflow
func calculateTotal(sizes []size.Size) (size.Size, error) {
    total := size.SizeNul
    for _, s := range sizes {
        if err := total.AddErr(s.Uint64()); err != nil {
            return 0, fmt.Errorf("size overflow: %w", err)
        }
    }
    return total, nil
}

// ❌ Bad: Silent overflow
func calculateTotalBad(sizes []size.Size) size.Size {
    total := size.SizeNul
    for _, s := range sizes {
        total.Add(s.Uint64())
    }
    return total
}

Use Type Conversion Safely

// ✅ Good: Check for overflow potential
func toInt32(s size.Size) (int32, error) {
    if s.Uint64() > math.MaxInt32 {
        return 0, errors.New("size too large for int32")
    }
    return s.Int32(), nil
}

// ❌ Bad: Assume no overflow
func toInt32Bad(s size.Size) int32 {
    return s.Int32() // May return MaxInt32 silently
}

Consistent Units in Configuration

// ✅ Good: Human-readable config
type Config struct {
    MaxFileSize   size.Size `json:"max_file_size"`   // User can write "100MB"
    MaxUploadSize size.Size `json:"max_upload_size"` // User can write "1GB"
}

// ❌ Bad: Raw byte counts
type ConfigBad struct {
    MaxFileSize   uint64 `json:"max_file_size"`   // User must calculate 104857600
    MaxUploadSize uint64 `json:"max_upload_size"` // User must calculate 1073741824
}

Marshal to Human-Readable Format

// ✅ Good: Easy to read
type APIResponse struct {
    TotalSize     size.Size `json:"total_size"`
    AvailableSize size.Size `json:"available_size"`
}
// JSON output: {"total_size": "10.50 GB", "available_size": "5.25 GB"}

// ❌ Bad: Requires client-side formatting
type APIResponseBad struct {
    TotalSize     uint64 `json:"total_size"`
    AvailableSize uint64 `json:"available_size"`
}
// JSON output: {"total_size": 11274289152, "available_size": 5637144576}

Testing

Test Suite: 352 specs using Ginkgo v2 and Gomega (95.4% coverage)

# Run tests
go test ./...

# With coverage
go test -cover ./...

# With race detection (recommended)
CGO_ENABLED=1 go test -race ./...

Coverage Areas

  • Parsing (strings, numbers, complex expressions)
  • Arithmetic operations (overflow/underflow handling)
  • Type conversions (all numeric types)
  • Formatting (various precision levels)
  • Marshaling (JSON, YAML, TOML, CBOR, Text, Binary)
  • Viper integration (decode hook with multiple types)
  • Edge cases (maximum values, zero, negative inputs)

Quality Assurance

  • Zero data races (verified with -race)
  • 95.4% code coverage
  • All overflow/underflow scenarios tested
  • Cross-format marshaling verified

See TESTING.md for detailed testing documentation.


Contributing

Contributions are welcome! Please follow these guidelines:

Code Contributions

  • Do not use AI to generate package implementation code
  • AI may assist with tests, documentation, and bug fixing
  • All contributions must pass go test -race
  • Maintain or improve test coverage (≥95%)
  • Follow existing code style and patterns

Documentation

  • Update README.md for new features
  • Add examples for common use cases
  • Keep TESTING.md synchronized with test changes

Testing

  • Write tests for all new features
  • Test edge cases and error conditions
  • Verify overflow/underflow protection
  • Add comments explaining complex scenarios

Pull Requests

  • Provide clear description of changes
  • Reference related issues
  • Include test results
  • Update documentation

See CONTRIBUTING.md for detailed guidelines.


Future Enhancements

Potential improvements for future versions:

Unit Systems

  • SI decimal prefixes (kB, MB, GB = powers of 1000)
  • IEC binary prefixes (KiB, MiB, GiB = powers of 1024) with explicit notation
  • User-selectable unit system

Parsing Features

  • Negative size values with explicit semantics
  • Unit aliases (e.g., "megabytes", "megs")
  • Localized unit names
  • Scientific notation support (e.g., "1e6")

Formatting Options

  • Custom format strings
  • Locale-aware number formatting
  • Compact notation (e.g., "10M" instead of "10.00 MB")
  • Adaptive precision based on magnitude

Arithmetic

  • Checked arithmetic mode (panic on overflow)
  • Saturation arithmetic mode (clamp to max/min)
  • Fixed-point arithmetic for precise fractional operations

Performance

  • Zero-allocation parsing for common patterns
  • String interning for frequently used values
  • SIMD-accelerated parsing

Suggestions and contributions are welcome via GitHub issues.


AI Transparency Notice

In accordance with Article 50.4 of the EU AI Act, AI assistance has been used for testing, documentation, and bug fixing under human supervision.


License

MIT License - See LICENSE file for details.


Resources