@@ -2,26 +2,37 @@ package v1
22
33import (
44 "encoding/json"
5- "errors"
65 "fmt"
6+ "math/bits"
7+
8+ "github.com/brevdev/cloud/internal/errors"
79)
810
911var (
1012 zeroBytes = Bytes {value : 0 , unit : Byte }
1113
12- ErrNegativeValue = errors .New ("value must be non-negative" )
13- ErrEmptyUnit = errors .New ("unit must be set" )
14- ErrInvalidUnit = errors .New ("invalid unit" )
14+ ErrBytesNegativeValue = errors .New ("value must be non-negative" )
15+ ErrBytesEmptyUnit = errors .New ("unit must be set" )
16+ ErrBytesInvalidUnit = errors .New ("invalid unit" )
1517)
1618
17- // NewBytes creates a new Bytes with the given value and unit
19+ // NewBytes creates a new Bytes with the given value and unit.
20+ // Returns zeroBytes if value is negative or if the byte count calculation would overflow.
1821func NewBytes (value BytesValue , unit BytesUnit ) Bytes {
1922 if value < 0 {
2023 return zeroBytes
2124 }
25+
26+ // Check for multiplication overflow using bits.Mul64. If more bits are needed than the lower 64
27+ // bits, then we know the full result doesn't fit in uint64.
28+ hi , lo := bits .Mul64 (uint64 (value ), unit .byteCount ) //nolint:gosec // 'value' can safely be converted to uint64
29+ if hi != 0 {
30+ return zeroBytes
31+ }
2232 return Bytes {
23- value : value ,
24- unit : unit ,
33+ value : value ,
34+ unit : unit ,
35+ byteCount : lo ,
2536 }
2637}
2738
3142
3243// Bytes represents a number of some unit of bytes
3344type Bytes struct {
34- value BytesValue
35- unit BytesUnit
45+ value BytesValue
46+ unit BytesUnit
47+ byteCount uint64 // TODO: consider using "https://pkg.go.dev/math/big" for this
3648}
3749
3850// bytesJSON is the JSON representation of a Bytes. This struct is maintained separately from the core Bytes
@@ -52,6 +64,11 @@ func (b Bytes) Unit() BytesUnit {
5264 return b .unit
5365}
5466
67+ // ByteCount is the number of bytes
68+ func (b Bytes ) ByteCount () uint64 {
69+ return b .byteCount
70+ }
71+
5572// String returns the string representation of the Bytes
5673func (b Bytes ) String () string {
5774 return fmt .Sprintf ("%d %s" , b .value , b .unit )
@@ -61,94 +78,104 @@ func (b Bytes) String() string {
6178func (b Bytes ) MarshalJSON () ([]byte , error ) {
6279 return json .Marshal (bytesJSON {
6380 Value : int64 (b .value ),
64- Unit : b .unit .value ,
81+ Unit : b .unit .name ,
6582 })
6683}
6784
6885// UnmarshalJSON implements the json.Unmarshaler interface
6986func (b * Bytes ) UnmarshalJSON (data []byte ) error {
7087 var bytesJSON bytesJSON
7188 if err := json .Unmarshal (data , & bytesJSON ); err != nil {
72- return err
89+ return errors . WrapAndTrace ( err )
7390 }
7491
7592 if bytesJSON .Value < 0 {
76- return ErrNegativeValue
93+ return errors . WrapAndTrace ( ErrBytesNegativeValue )
7794 }
7895
7996 if bytesJSON .Unit == "" {
80- return ErrEmptyUnit
97+ return errors . WrapAndTrace ( ErrBytesEmptyUnit )
8198 }
8299
83100 unit , err := stringToBytesUnit (bytesJSON .Unit )
84101 if err != nil {
85- return err
102+ return errors . WrapAndTrace ( err )
86103 }
87104
88- b . value = BytesValue (bytesJSON .Value )
89- b . unit = unit
105+ newBytes := NewBytes ( BytesValue (bytesJSON .Value ), unit )
106+ * b = newBytes
90107 return nil
91108}
92109
93- // BytesUnit is a unit of measurement for bytes. Note for implementers: this is defined as a struct rather than a
110+ // LessThan returns true if the Bytes is less than the other Bytes
111+ func (b Bytes ) LessThan (other Bytes ) bool {
112+ return b .byteCount < other .byteCount
113+ }
114+
115+ // GreaterThan returns true if the Bytes is greater than the other Bytes
116+ func (b Bytes ) GreaterThan (other Bytes ) bool {
117+ return b .byteCount > other .byteCount
118+ }
119+
120+ // BytesUnit is a unit of measurement for bytes. Note for maintainers: this is defined as a struct rather than a
94121// type alias to ensure stronger compile-time type checking and to avoid the need for a validation function.
95122type BytesUnit struct {
96- value string
123+ name string
124+ byteCount uint64
125+ }
126+
127+ // ByteCountInUnit is the number of bytes in the BytesUnit in the given unit
128+ func (u BytesUnit ) ByteCount () uint64 {
129+ return u .byteCount
97130}
98131
99132// String returns the string representation of the BytesUnit
100133func (u BytesUnit ) String () string {
101- return u .value
134+ return u .name
102135}
103136
104137var (
105- Byte = BytesUnit {value : "B" }
138+ Byte = BytesUnit {name : "B" , byteCount : 1 }
106139
107140 // Base 10
108- Kilobyte = BytesUnit {value : "KB" }
109- Megabyte = BytesUnit {value : "MB" }
110- Gigabyte = BytesUnit {value : "GB" }
111- Terabyte = BytesUnit {value : "TB" }
112- Petabyte = BytesUnit {value : "PB" }
113- Exabyte = BytesUnit {value : "EB" }
141+ Kilobyte = BytesUnit {name : "KB" , byteCount : 1000 }
142+ Megabyte = BytesUnit {name : "MB" , byteCount : 1000 * 1000 }
143+ Gigabyte = BytesUnit {name : "GB" , byteCount : 1000 * 1000 * 1000 }
144+ Terabyte = BytesUnit {name : "TB" , byteCount : 1000 * 1000 * 1000 * 1000 }
145+ Petabyte = BytesUnit {name : "PB" , byteCount : 1000 * 1000 * 1000 * 1000 * 1000 }
114146
115147 // Base 2
116- Kibibyte = BytesUnit {value : "KiB" }
117- Mebibyte = BytesUnit {value : "MiB" }
118- Gibibyte = BytesUnit {value : "GiB" }
119- Tebibyte = BytesUnit {value : "TiB" }
120- Pebibyte = BytesUnit {value : "PiB" }
121- Exbibyte = BytesUnit {value : "EiB" }
148+ Kibibyte = BytesUnit {name : "KiB" , byteCount : 1024 }
149+ Mebibyte = BytesUnit {name : "MiB" , byteCount : 1024 * 1024 }
150+ Gibibyte = BytesUnit {name : "GiB" , byteCount : 1024 * 1024 * 1024 }
151+ Tebibyte = BytesUnit {name : "TiB" , byteCount : 1024 * 1024 * 1024 * 1024 }
152+ Pebibyte = BytesUnit {name : "PiB" , byteCount : 1024 * 1024 * 1024 * 1024 * 1024 }
122153)
123154
124155func stringToBytesUnit (unit string ) (BytesUnit , error ) {
125156 switch unit {
126- case "B" :
157+ case Byte . name :
127158 return Byte , nil
128- case "KB" :
159+ case Kilobyte . name :
129160 return Kilobyte , nil
130- case "MB" :
161+ case Megabyte . name :
131162 return Megabyte , nil
132- case "GB" :
163+ case Gigabyte . name :
133164 return Gigabyte , nil
134- case "TB" :
165+ case Terabyte . name :
135166 return Terabyte , nil
136- case "PB" :
167+ case Petabyte . name :
137168 return Petabyte , nil
138- case "EB" :
139- return Exabyte , nil
140- case "KiB" :
169+ case Kibibyte .name :
141170 return Kibibyte , nil
142- case "MiB" :
171+ case Mebibyte . name :
143172 return Mebibyte , nil
144- case "GiB" :
173+ case Gibibyte . name :
145174 return Gibibyte , nil
146- case "TiB" :
175+ case Tebibyte . name :
147176 return Tebibyte , nil
148- case "PiB" :
177+ case Pebibyte . name :
149178 return Pebibyte , nil
150- case "EiB" :
151- return Exbibyte , nil
152179 }
153- return BytesUnit {}, errors .Join (ErrInvalidUnit , fmt .Errorf ("invalid unit: %s" , unit ))
180+ return BytesUnit {}, errors .WrapAndTrace ( errors . Join (ErrBytesInvalidUnit , fmt .Errorf ("invalid unit: %s" , unit ) ))
154181}
0 commit comments