[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
18 KiB
Size Package
Type-safe, human-readable size representation library for Go with parsing, formatting, arithmetic operations, and comprehensive marshaling support.
Table of Contents
- Overview
- Key Features
- Installation
- Architecture
- Quick Start
- Performance
- Use Cases
- API Reference
- Best Practices
- Testing
- Contributing
- Future Enhancements
- License
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
- Type-Safe: Size wraps
uint64to prevent accidental misuse as plain integers - Overflow-Protected: All arithmetic operations detect and handle overflow/underflow
- Human-Readable: Automatic formatting with appropriate units (KB, MB, GB, etc.)
- Format-Agnostic: Marshaling/unmarshaling for JSON, YAML, TOML, CBOR, and plain text
- 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
- Issues: GitHub Issues
- Documentation: GoDoc
- Testing Guide: TESTING.md
- Contributing: CONTRIBUTING.md
- Related Packages:
- duration - Time duration handling with similar API
- duration/big - Arbitrary-precision durations
- viper - Configuration management helpers
- config - Complete configuration system