1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
17 // ByteSizeOrPercent indicates either a number of bytes or a
18 // percentage from 1 to 100.
19 type ByteSizeOrPercent ByteSize
21 var prefixValue = map[string]int64{
31 "P": 1000000000000000,
33 "E": 1000000000000000000,
37 func (n *ByteSize) UnmarshalJSON(data []byte) error {
38 if len(data) == 0 || data[0] != '"' {
40 err := json.Unmarshal(data, &i)
48 err := json.Unmarshal(data, &s)
52 split := strings.LastIndexAny(s, "0123456789.+-eE") + 1
54 return fmt.Errorf("invalid byte size %q", s)
56 if s[split-1] == 'E' {
57 // We accepted an E as if it started the exponent part
58 // of a json number, but if the next char isn't +, -,
59 // or digit, then the E must have meant Exa. Instead
60 // of "4.5E"+"iB" we want "4.5"+"EiB".
64 dec := json.NewDecoder(strings.NewReader(s[:split]))
66 err = dec.Decode(&val)
73 prefix := strings.Trim(s[split:], " ")
74 if strings.HasSuffix(prefix, "B") {
75 prefix = prefix[:len(prefix)-1]
77 pval, ok := prefixValue[prefix]
79 return fmt.Errorf("invalid unit %q", strings.Trim(s[split:], " "))
81 if intval, err := val.Int64(); err == nil {
82 if pval > 1 && (intval*pval)/pval != intval {
83 return fmt.Errorf("size %q overflows int64", s)
85 *n = ByteSize(intval * pval)
87 } else if floatval, err := val.Float64(); err == nil {
88 if floatval*float64(pval) > math.MaxInt64 {
89 return fmt.Errorf("size %q overflows int64", s)
91 *n = ByteSize(int64(floatval * float64(pval)))
94 return fmt.Errorf("bug: json.Number for %q is not int64 or float64: %s", s, err)
98 func (n ByteSizeOrPercent) MarshalJSON() ([]byte, error) {
99 if n < 0 && n >= -100 {
100 return []byte(fmt.Sprintf("\"%d%%\"", -n)), nil
102 return json.Marshal(int64(n))
106 func (n *ByteSizeOrPercent) UnmarshalJSON(data []byte) error {
107 if len(data) == 0 || data[0] != '"' {
108 return (*ByteSize)(n).UnmarshalJSON(data)
111 err := json.Unmarshal(data, &s)
115 if s := strings.TrimSpace(s); len(s) > 0 && s[len(s)-1] == '%' {
116 pct, err := strconv.ParseInt(strings.TrimSpace(s[:len(s)-1]), 10, 64)
120 if pct < 0 || pct > 100 {
121 return fmt.Errorf("invalid value %q (percentage must be between 0 and 100)", s)
123 *n = ByteSizeOrPercent(-pct)
126 return (*ByteSize)(n).UnmarshalJSON(data)
129 // ByteSize returns the absolute byte size specified by n, or 0 if n
130 // specifies a percent.
131 func (n ByteSizeOrPercent) ByteSize() ByteSize {
132 if n >= -100 && n < 0 {
139 // ByteSize returns the percentage specified by n, or 0 if n specifies
140 // an absolute byte size.
141 func (n ByteSizeOrPercent) Percent() int64 {
142 if n >= -100 && n < 0 {