1// Package bytesize provides human-friendly byte size parsing and formatting.
2package bytesize
3
4import (
5 "fmt"
6 "math"
7 "strconv"
8 "strings"
9)
10
11// unitMultipliers maps human-friendly unit suffixes to their byte values.
12var unitMultipliers = map[string]int64{
13 "B": 1,
14 "KB": 1 << 10, // 1024
15 "MB": 1 << 20, // 1048576
16 "GB": 1 << 30, // 1073741824
17 "TB": 1 << 40, // 1099511627776
18}
19
20// Format converts a byte count into a human-friendly string.
21//
22// Uses binary (1024-based) units: B, KB, MB, GB, TB.
23// Returns one decimal place for KB and above.
24//
25// Examples:
26//
27// Format(512) // "512 B"
28// Format(1048576) // "1.0 MB"
29// Format(3609629152) // "3.4 GB"
30func Format(bytes int64) string {
31 if bytes <= 0 {
32 return "0 B"
33 }
34 units := []string{"B", "KB", "MB", "GB", "TB"}
35 size := float64(bytes)
36 unitIdx := 0
37 for size >= 1024 && unitIdx < len(units)-1 {
38 size /= 1024
39 unitIdx++
40 }
41 if unitIdx == 0 {
42 return fmt.Sprintf("%d B", bytes)
43 }
44 return fmt.Sprintf("%.1f %s", size, units[unitIdx])
45}
46
47// Parse parses a human-friendly byte size string.
48//
49// Supported units: B, KB, MB, GB, TB (case-insensitive).
50// The decimal prefix (1024-based) is used (KiB, MiB, etc.).
51//
52// Returns int64 to safely integrate with standard library functions like http.MaxBytesReader.
53// Maximum representable value is 9,223,372,036,854,775,807 bytes (~8 exabytes).
54//
55// Examples:
56//
57// Parse("512MB") // 536870912 bytes
58// Parse("1GB") // 1073741824 bytes
59// Parse("100KB") // 102400 bytes
60// Parse("1.5GB") // 1610612736 bytes
61func Parse(s string) (int64, error) {
62 s = strings.TrimSpace(s)
63
64 if s == "" {
65 return 0, fmt.Errorf("empty size string")
66 }
67
68 s = strings.ToUpper(s)
69
70 // Find the unit suffix - try longest first to avoid matching "B" before "KB"
71 units := []string{"TB", "GB", "MB", "KB", "B"}
72 var unit string
73 var valueStr string
74
75 for _, u := range units {
76 if strings.HasSuffix(s, u) {
77 unit = u
78 valueStr = strings.TrimSuffix(s, u)
79 break
80 }
81 }
82
83 if unit == "" {
84 return 0, fmt.Errorf("invalid size %q: missing unit (supported: B, KB, MB, GB, TB)", s)
85 }
86
87 valueStr = strings.TrimSpace(valueStr)
88 if valueStr == "" {
89 return 0, fmt.Errorf("invalid size %q: missing numeric value", s)
90 }
91
92 value, err := strconv.ParseFloat(valueStr, 64)
93 if err != nil {
94 return 0, fmt.Errorf("invalid size value %q in %q: %w", valueStr, s, err)
95 }
96
97 if value < 0 {
98 return 0, fmt.Errorf("invalid size %q: negative value not allowed", s)
99 }
100
101 multiplier := unitMultipliers[unit]
102 result := value * float64(multiplier)
103
104 // Check for overflow before converting to int64
105 if result > math.MaxInt64 {
106 return 0, fmt.Errorf("size %q exceeds maximum allowed value (9.2 EB)", s)
107 }
108
109 return int64(result), nil
110}