Skip to content

Commit 93cb879

Browse files
authored
feat(BREV-2107): JSON marshalling of core types (#59)
* feat(BREV-2107): JSON marshalling of core types * bytes * use big int for byte count * fixes * simplify
1 parent 13327cb commit 93cb879

File tree

12 files changed

+1275
-103
lines changed

12 files changed

+1275
-103
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: 182 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package 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.
621
func 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

1625
type (
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
2843
func (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

Comments
 (0)