1
16
17 package intstr
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "math"
24 "runtime/debug"
25 "strconv"
26 "strings"
27
28 "k8s.io/klog/v2"
29 )
30
31
32
33
34
35
36
37
38
39
40 type IntOrString struct {
41 Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"`
42 IntVal int32 `protobuf:"varint,2,opt,name=intVal"`
43 StrVal string `protobuf:"bytes,3,opt,name=strVal"`
44 }
45
46
47 type Type int64
48
49 const (
50 Int Type = iota
51 String
52 )
53
54
55
56
57
58 func FromInt(val int) IntOrString {
59 if val > math.MaxInt32 || val < math.MinInt32 {
60 klog.Errorf("value: %d overflows int32\n%s\n", val, debug.Stack())
61 }
62 return IntOrString{Type: Int, IntVal: int32(val)}
63 }
64
65
66 func FromInt32(val int32) IntOrString {
67 return IntOrString{Type: Int, IntVal: val}
68 }
69
70
71 func FromString(val string) IntOrString {
72 return IntOrString{Type: String, StrVal: val}
73 }
74
75
76
77 func Parse(val string) IntOrString {
78 i, err := strconv.ParseInt(val, 10, 32)
79 if err != nil {
80 return FromString(val)
81 }
82 return FromInt32(int32(i))
83 }
84
85
86 func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
87 if value[0] == '"' {
88 intstr.Type = String
89 return json.Unmarshal(value, &intstr.StrVal)
90 }
91 intstr.Type = Int
92 return json.Unmarshal(value, &intstr.IntVal)
93 }
94
95
96 func (intstr *IntOrString) String() string {
97 if intstr == nil {
98 return "<nil>"
99 }
100 if intstr.Type == String {
101 return intstr.StrVal
102 }
103 return strconv.Itoa(intstr.IntValue())
104 }
105
106
107
108
109 func (intstr *IntOrString) IntValue() int {
110 if intstr.Type == String {
111 i, _ := strconv.Atoi(intstr.StrVal)
112 return i
113 }
114 return int(intstr.IntVal)
115 }
116
117
118 func (intstr IntOrString) MarshalJSON() ([]byte, error) {
119 switch intstr.Type {
120 case Int:
121 return json.Marshal(intstr.IntVal)
122 case String:
123 return json.Marshal(intstr.StrVal)
124 default:
125 return []byte{}, fmt.Errorf("impossible IntOrString.Type")
126 }
127 }
128
129
130
131
132
133 func (IntOrString) OpenAPISchemaType() []string { return []string{"string"} }
134
135
136
137 func (IntOrString) OpenAPISchemaFormat() string { return "int-or-string" }
138
139
140
141 func (IntOrString) OpenAPIV3OneOfTypes() []string { return []string{"integer", "string"} }
142
143 func ValueOrDefault(intOrPercent *IntOrString, defaultValue IntOrString) *IntOrString {
144 if intOrPercent == nil {
145 return &defaultValue
146 }
147 return intOrPercent
148 }
149
150
151
152
153
154
155 func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
156 if intOrPercent == nil {
157 return 0, errors.New("nil value for IntOrString")
158 }
159 value, isPercent, err := getIntOrPercentValueSafely(intOrPercent)
160 if err != nil {
161 return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
162 }
163 if isPercent {
164 if roundUp {
165 value = int(math.Ceil(float64(value) * (float64(total)) / 100))
166 } else {
167 value = int(math.Floor(float64(value) * (float64(total)) / 100))
168 }
169 }
170 return value, nil
171 }
172
173
174
175
176
177 func GetValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) {
178 if intOrPercent == nil {
179 return 0, errors.New("nil value for IntOrString")
180 }
181 value, isPercent, err := getIntOrPercentValue(intOrPercent)
182 if err != nil {
183 return 0, fmt.Errorf("invalid value for IntOrString: %v", err)
184 }
185 if isPercent {
186 if roundUp {
187 value = int(math.Ceil(float64(value) * (float64(total)) / 100))
188 } else {
189 value = int(math.Floor(float64(value) * (float64(total)) / 100))
190 }
191 }
192 return value, nil
193 }
194
195
196
197 func getIntOrPercentValue(intOrStr *IntOrString) (int, bool, error) {
198 switch intOrStr.Type {
199 case Int:
200 return intOrStr.IntValue(), false, nil
201 case String:
202 s := strings.Replace(intOrStr.StrVal, "%", "", -1)
203 v, err := strconv.Atoi(s)
204 if err != nil {
205 return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
206 }
207 return int(v), true, nil
208 }
209 return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
210 }
211
212 func getIntOrPercentValueSafely(intOrStr *IntOrString) (int, bool, error) {
213 switch intOrStr.Type {
214 case Int:
215 return intOrStr.IntValue(), false, nil
216 case String:
217 isPercent := false
218 s := intOrStr.StrVal
219 if strings.HasSuffix(s, "%") {
220 isPercent = true
221 s = strings.TrimSuffix(intOrStr.StrVal, "%")
222 } else {
223 return 0, false, fmt.Errorf("invalid type: string is not a percentage")
224 }
225 v, err := strconv.Atoi(s)
226 if err != nil {
227 return 0, false, fmt.Errorf("invalid value %q: %v", intOrStr.StrVal, err)
228 }
229 return int(v), isPercent, nil
230 }
231 return 0, false, fmt.Errorf("invalid type: neither int nor percentage")
232 }
233
View as plain text