11package v1
22
3- var zeroBytes = Bytes {value : 0 , unit : Byte }
3+ import (
4+ "encoding/json"
5+ "fmt"
6+ "math"
7+ "math/big"
48
5- // NewBytes creates a new Bytes with the given value and unit
9+ "github.com/brevdev/cloud/internal/errors"
10+ )
11+
12+ var (
13+ zeroBytes = Bytes {value : 0 , unit : Byte }
14+
15+ ErrBytesInvalidUnit = errors .New ("invalid unit" )
16+ ErrBytesNotAnInt64 = errors .New ("byte count is not an int64" )
17+ ErrBytesNotAnInt32 = errors .New ("byte count is not an int32" )
18+ )
19+
20+ // NewBytes creates a new Bytes with the given value and unit.
621func NewBytes (value BytesValue , unit BytesUnit ) Bytes {
7- if value < 0 {
8- return zeroBytes
9- }
10- return Bytes {
11- value : value ,
12- unit : unit ,
13- }
22+ return Bytes {value : value , unit : unit }
1423}
1524
1625type (
1726 BytesValue int64
18- BytesUnit string
1927)
2028
2129// Bytes represents a number of some unit of bytes
@@ -24,6 +32,13 @@ type Bytes struct {
2432 unit BytesUnit
2533}
2634
35+ // bytesJSON is the JSON representation of a Bytes. This struct is maintained separately from the core Bytes
36+ // struct to allow for unexported fields to be used in the MarshalJSON and UnmarshalJSON methods.
37+ type bytesJSON struct {
38+ Value int64 `json:"value"`
39+ Unit string `json:"unit"`
40+ }
41+
2742// Value is the whole non-negative number of bytes of the specified unit
2843func (b Bytes ) Value () BytesValue {
2944 return b .value
@@ -34,23 +49,165 @@ func (b Bytes) Unit() BytesUnit {
3449 return b .unit
3550}
3651
37- // ByteUnit is a unit of measurement for bytes
38- const (
39- Byte BytesUnit = "B"
52+ // ByteCount is the total number of bytes in the Bytes
53+ func (b Bytes ) ByteCount () * big.Int {
54+ bytesByteCount := big .NewInt (0 ).SetInt64 (int64 (b .value ))
55+ unitByteCount := big .NewInt (0 ).SetUint64 (b .unit .byteCount )
56+
57+ return big .NewInt (0 ).Mul (bytesByteCount , unitByteCount )
58+ }
59+
60+ // ByteCountInUnit is the number of bytes in the Bytes of the given unit. For example, if
61+ // the Bytes is 1000 MB, then:
62+ //
63+ // 1000 MB -> B = 1000000
64+ // 1000 MB -> KB = 1000
65+ // 1000 MB -> MB = 1
66+ // 1000 MB -> GB = .001
67+ //
68+ // etc.
69+ func (b Bytes ) ByteCountInUnit (unit BytesUnit ) * big.Float {
70+ if b .unit == unit {
71+ // If the units are the same, return the value as a float
72+ return big .NewFloat (0 ).SetInt64 (int64 (b .value ))
73+ }
74+
75+ bytesByteCount := big .NewFloat (0 ).SetInt (b .ByteCount ())
76+ unitByteCount := big .NewFloat (0 ).SetUint64 (unit .byteCount )
77+
78+ return big .NewFloat (0 ).Quo (bytesByteCount , unitByteCount )
79+ }
80+
81+ // ByteCountInUnitInt64 attempts to convert the result of ByteCountInUnit to an int64. If this conversion would
82+ // result in an overflow, it returns an ErrBytesNotAnInt64 error. If the byte count is not an integer, the value
83+ // is truncated towards zero.
84+ func (b Bytes ) ByteCountInUnitInt64 (unit BytesUnit ) (int64 , error ) {
85+ byteCount := b .ByteCountInUnit (unit )
86+
87+ byteCountInt64 , accuracy := byteCount .Int64 ()
88+ if byteCountInt64 == math .MaxInt64 && accuracy == big .Below {
89+ return 0 , errors .WrapAndTrace (errors .Join (ErrBytesNotAnInt64 , fmt .Errorf ("byte count %v is greater than %d" , byteCount , math .MaxInt64 )))
90+ }
91+ if byteCountInt64 == math .MinInt64 && accuracy == big .Above {
92+ return 0 , errors .WrapAndTrace (errors .Join (ErrBytesNotAnInt64 , fmt .Errorf ("byte count %v is less than %d" , byteCount , math .MinInt64 )))
93+ }
94+ return byteCountInt64 , nil
95+ }
96+
97+ // ByteCountInUnitInt32 attempts to convert the result of ByteCountInUnit to an int32. If this conversion would
98+ // result in an overflow, it returns an ErrBytesNotAnInt32 error.
99+ func (b Bytes ) ByteCountInUnitInt32 (unit BytesUnit ) (int32 , error ) {
100+ byteCountInt64 , err := b .ByteCountInUnitInt64 (unit )
101+ if err != nil {
102+ return 0 , errors .WrapAndTrace (err )
103+ }
104+ if byteCountInt64 > math .MaxInt32 {
105+ return 0 , errors .WrapAndTrace (errors .Join (ErrBytesNotAnInt32 , fmt .Errorf ("byte count %v is greater than %d" , byteCountInt64 , math .MaxInt32 )))
106+ }
107+ return int32 (byteCountInt64 ), nil //nolint:gosec // checked above
108+ }
109+
110+ // String returns the string representation of the Bytes
111+ func (b Bytes ) String () string {
112+ return fmt .Sprintf ("%d %s" , b .value , b .unit )
113+ }
114+
115+ // MarshalJSON implements the json.Marshaler interface
116+ func (b Bytes ) MarshalJSON () ([]byte , error ) {
117+ return json .Marshal (bytesJSON {
118+ Value : int64 (b .value ),
119+ Unit : b .unit .name ,
120+ })
121+ }
122+
123+ // UnmarshalJSON implements the json.Unmarshaler interface
124+ func (b * Bytes ) UnmarshalJSON (data []byte ) error {
125+ var bytesJSON bytesJSON
126+ if err := json .Unmarshal (data , & bytesJSON ); err != nil {
127+ return errors .WrapAndTrace (err )
128+ }
129+
130+ unit , err := stringToBytesUnit (bytesJSON .Unit )
131+ if err != nil {
132+ return errors .WrapAndTrace (err )
133+ }
134+
135+ newBytes := NewBytes (BytesValue (bytesJSON .Value ), unit )
136+ * b = newBytes
137+ return nil
138+ }
139+
140+ // LessThan returns true if the Bytes is less than the other Bytes
141+ func (b Bytes ) LessThan (other Bytes ) bool {
142+ return b .ByteCount ().Cmp (other .ByteCount ()) < 0
143+ }
144+
145+ // GreaterThan returns true if the Bytes is greater than the other Bytes
146+ func (b Bytes ) GreaterThan (other Bytes ) bool {
147+ return b .ByteCount ().Cmp (other .ByteCount ()) > 0
148+ }
149+
150+ // Equal returns true if the Bytes is equal to the other Bytes
151+ func (b Bytes ) Equal (other Bytes ) bool {
152+ return b .ByteCount ().Cmp (other .ByteCount ()) == 0
153+ }
154+
155+ // BytesUnit is a unit of measurement for bytes. Note for maintainers: this is defined as a struct rather than a
156+ // type alias to ensure stronger compile-time type checking and to avoid the need for a validation function.
157+ type BytesUnit struct {
158+ name string
159+ byteCount uint64
160+ }
161+
162+ // String returns the string representation of the BytesUnit
163+ func (u BytesUnit ) String () string {
164+ return u .name
165+ }
166+
167+ var (
168+ Byte = BytesUnit {name : "B" , byteCount : 1 }
40169
41170 // Base 10
42- Kilobyte BytesUnit = "KB"
43- Megabyte BytesUnit = "MB"
44- Gigabyte BytesUnit = "GB"
45- Terabyte BytesUnit = "TB"
46- Petabyte BytesUnit = "PB"
47- Exabyte BytesUnit = "EB"
171+ Kilobyte = BytesUnit { name : "KB" , byteCount : 1000 }
172+ Megabyte = BytesUnit { name : "MB" , byteCount : 1000 * 1000 }
173+ Gigabyte = BytesUnit { name : "GB" , byteCount : 1000 * 1000 * 1000 }
174+ Terabyte = BytesUnit { name : "TB" , byteCount : 1000 * 1000 * 1000 * 1000 }
175+ Petabyte = BytesUnit { name : "PB" , byteCount : 1000 * 1000 * 1000 * 1000 * 1000 }
176+ Exabyte = BytesUnit { name : "EB" , byteCount : 1000 * 1000 * 1000 * 1000 * 1000 * 1000 }
48177
49178 // Base 2
50- Kibibyte BytesUnit = "KiB"
51- Mebibyte BytesUnit = "MiB"
52- Gibibyte BytesUnit = "GiB"
53- Tebibyte BytesUnit = "TiB"
54- Pebibyte BytesUnit = "PiB"
55- Exbibyte BytesUnit = "EiB"
179+ Kibibyte = BytesUnit { name : "KiB" , byteCount : 1024 }
180+ Mebibyte = BytesUnit { name : "MiB" , byteCount : 1024 * 1024 }
181+ Gibibyte = BytesUnit { name : "GiB" , byteCount : 1024 * 1024 * 1024 }
182+ Tebibyte = BytesUnit { name : "TiB" , byteCount : 1024 * 1024 * 1024 * 1024 }
183+ Pebibyte = BytesUnit { name : "PiB" , byteCount : 1024 * 1024 * 1024 * 1024 * 1024 }
184+ Exbibyte = BytesUnit { name : "EiB" , byteCount : 1024 * 1024 * 1024 * 1024 * 1024 * 1024 }
56185)
186+
187+ func stringToBytesUnit (unit string ) (BytesUnit , error ) {
188+ switch unit {
189+ case Byte .name :
190+ return Byte , nil
191+ case Kilobyte .name :
192+ return Kilobyte , nil
193+ case Megabyte .name :
194+ return Megabyte , nil
195+ case Gigabyte .name :
196+ return Gigabyte , nil
197+ case Terabyte .name :
198+ return Terabyte , nil
199+ case Petabyte .name :
200+ return Petabyte , nil
201+ case Kibibyte .name :
202+ return Kibibyte , nil
203+ case Mebibyte .name :
204+ return Mebibyte , nil
205+ case Gibibyte .name :
206+ return Gibibyte , nil
207+ case Tebibyte .name :
208+ return Tebibyte , nil
209+ case Pebibyte .name :
210+ return Pebibyte , nil
211+ }
212+ return BytesUnit {}, errors .WrapAndTrace (errors .Join (ErrBytesInvalidUnit , fmt .Errorf ("invalid unit: %s" , unit )))
213+ }
0 commit comments