Skip to content

Commit cb3bd23

Browse files
committed
bytes
1 parent 312bf99 commit cb3bd23

File tree

12 files changed

+801
-132
lines changed

12 files changed

+801
-132
lines changed

internal/validation/suite.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ type KubernetesValidationNodeGroupOpts struct {
246246
MinNodeCount int
247247
MaxNodeCount int
248248
InstanceType string
249-
DiskSizeGiB int
249+
DiskSize v1.Bytes
250250
}
251251

252252
type KubernetesValidationNetworkOpts struct {
@@ -420,7 +420,7 @@ func RunKubernetesValidation(t *testing.T, config ProviderConfig, opts Kubernete
420420
MinNodeCount: opts.NodeGroupOpts.MinNodeCount,
421421
MaxNodeCount: opts.NodeGroupOpts.MaxNodeCount,
422422
InstanceType: opts.NodeGroupOpts.InstanceType,
423-
DiskSizeGiB: opts.NodeGroupOpts.DiskSizeGiB,
423+
DiskSize: opts.NodeGroupOpts.DiskSize,
424424
Tags: opts.Tags,
425425
})
426426
require.NoError(t, err, "ValidateCreateKubernetesNodeGroup should pass")

v1/bytes.go

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,37 @@ package v1
22

33
import (
44
"encoding/json"
5-
"errors"
65
"fmt"
6+
"math/bits"
7+
8+
"github.com/brevdev/cloud/internal/errors"
79
)
810

911
var (
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.
1821
func 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

@@ -31,8 +42,9 @@ type (
3142

3243
// Bytes represents a number of some unit of bytes
3344
type 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
5673
func (b Bytes) String() string {
5774
return fmt.Sprintf("%d %s", b.value, b.unit)
@@ -61,94 +78,104 @@ func (b Bytes) String() string {
6178
func (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
6986
func (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.
95122
type 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
100133
func (u BytesUnit) String() string {
101-
return u.value
134+
return u.name
102135
}
103136

104137
var (
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

124155
func 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

Comments
 (0)