blob: ea79e3a000e78469dc6cfbb7a3a7ebdc0ce8bd88 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package value
18
19import (
20 "bytes"
21 "fmt"
22 "io"
23 "strings"
24
25 jsoniter "github.com/json-iterator/go"
26 "gopkg.in/yaml.v2"
27)
28
29var (
30 readPool = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
31 writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
32)
33
34// A Value corresponds to an 'atom' in the schema. It should return true
35// for at least one of the IsXXX methods below, or the value is
36// considered "invalid"
37type Value interface {
38 // IsMap returns true if the Value is a Map, false otherwise.
39 IsMap() bool
40 // IsList returns true if the Value is a List, false otherwise.
41 IsList() bool
42 // IsBool returns true if the Value is a bool, false otherwise.
43 IsBool() bool
44 // IsInt returns true if the Value is a int64, false otherwise.
45 IsInt() bool
46 // IsFloat returns true if the Value is a float64, false
47 // otherwise.
48 IsFloat() bool
49 // IsString returns true if the Value is a string, false
50 // otherwise.
51 IsString() bool
52 // IsMap returns true if the Value is null, false otherwise.
53 IsNull() bool
54
55 // AsMap converts the Value into a Map (or panic if the type
56 // doesn't allow it).
57 AsMap() Map
58 // AsMapUsing uses the provided allocator and converts the Value
59 // into a Map (or panic if the type doesn't allow it).
60 AsMapUsing(Allocator) Map
61 // AsList converts the Value into a List (or panic if the type
62 // doesn't allow it).
63 AsList() List
64 // AsListUsing uses the provided allocator and converts the Value
65 // into a List (or panic if the type doesn't allow it).
66 AsListUsing(Allocator) List
67 // AsBool converts the Value into a bool (or panic if the type
68 // doesn't allow it).
69 AsBool() bool
70 // AsInt converts the Value into an int64 (or panic if the type
71 // doesn't allow it).
72 AsInt() int64
73 // AsFloat converts the Value into a float64 (or panic if the type
74 // doesn't allow it).
75 AsFloat() float64
76 // AsString converts the Value into a string (or panic if the type
77 // doesn't allow it).
78 AsString() string
79
80 // Unstructured converts the Value into an Unstructured interface{}.
81 Unstructured() interface{}
82}
83
84// FromJSON is a helper function for reading a JSON document.
85func FromJSON(input []byte) (Value, error) {
86 return FromJSONFast(input)
87}
88
89// FromJSONFast is a helper function for reading a JSON document.
90func FromJSONFast(input []byte) (Value, error) {
91 iter := readPool.BorrowIterator(input)
92 defer readPool.ReturnIterator(iter)
93 return ReadJSONIter(iter)
94}
95
96// ToJSON is a helper function for producing a JSon document.
97func ToJSON(v Value) ([]byte, error) {
98 buf := bytes.Buffer{}
99 stream := writePool.BorrowStream(&buf)
100 defer writePool.ReturnStream(stream)
101 WriteJSONStream(v, stream)
102 b := stream.Buffer()
103 err := stream.Flush()
104 // Help jsoniter manage its buffers--without this, the next
105 // use of the stream is likely to require an allocation. Look
106 // at the jsoniter stream code to understand why. They were probably
107 // optimizing for folks using the buffer directly.
108 stream.SetBuffer(b[:0])
109 return buf.Bytes(), err
110}
111
112// ReadJSONIter reads a Value from a JSON iterator.
113func ReadJSONIter(iter *jsoniter.Iterator) (Value, error) {
114 v := iter.Read()
115 if iter.Error != nil && iter.Error != io.EOF {
116 return nil, iter.Error
117 }
118 return NewValueInterface(v), nil
119}
120
121// WriteJSONStream writes a value into a JSON stream.
122func WriteJSONStream(v Value, stream *jsoniter.Stream) {
123 stream.WriteVal(v.Unstructured())
124}
125
126// ToYAML marshals a value as YAML.
127func ToYAML(v Value) ([]byte, error) {
128 return yaml.Marshal(v.Unstructured())
129}
130
131// Equals returns true iff the two values are equal.
132func Equals(lhs, rhs Value) bool {
133 return EqualsUsing(HeapAllocator, lhs, rhs)
134}
135
136// EqualsUsing uses the provided allocator and returns true iff the two values are equal.
137func EqualsUsing(a Allocator, lhs, rhs Value) bool {
138 if lhs.IsFloat() || rhs.IsFloat() {
139 var lf float64
140 if lhs.IsFloat() {
141 lf = lhs.AsFloat()
142 } else if lhs.IsInt() {
143 lf = float64(lhs.AsInt())
144 } else {
145 return false
146 }
147 var rf float64
148 if rhs.IsFloat() {
149 rf = rhs.AsFloat()
150 } else if rhs.IsInt() {
151 rf = float64(rhs.AsInt())
152 } else {
153 return false
154 }
155 return lf == rf
156 }
157 if lhs.IsInt() {
158 if rhs.IsInt() {
159 return lhs.AsInt() == rhs.AsInt()
160 }
161 return false
162 } else if rhs.IsInt() {
163 return false
164 }
165 if lhs.IsString() {
166 if rhs.IsString() {
167 return lhs.AsString() == rhs.AsString()
168 }
169 return false
170 } else if rhs.IsString() {
171 return false
172 }
173 if lhs.IsBool() {
174 if rhs.IsBool() {
175 return lhs.AsBool() == rhs.AsBool()
176 }
177 return false
178 } else if rhs.IsBool() {
179 return false
180 }
181 if lhs.IsList() {
182 if rhs.IsList() {
183 lhsList := lhs.AsListUsing(a)
184 defer a.Free(lhsList)
185 rhsList := rhs.AsListUsing(a)
186 defer a.Free(rhsList)
187 return lhsList.EqualsUsing(a, rhsList)
188 }
189 return false
190 } else if rhs.IsList() {
191 return false
192 }
193 if lhs.IsMap() {
194 if rhs.IsMap() {
195 lhsList := lhs.AsMapUsing(a)
196 defer a.Free(lhsList)
197 rhsList := rhs.AsMapUsing(a)
198 defer a.Free(rhsList)
199 return lhsList.EqualsUsing(a, rhsList)
200 }
201 return false
202 } else if rhs.IsMap() {
203 return false
204 }
205 if lhs.IsNull() {
206 if rhs.IsNull() {
207 return true
208 }
209 return false
210 } else if rhs.IsNull() {
211 return false
212 }
213 // No field is set, on either objects.
214 return true
215}
216
217// ToString returns a human-readable representation of the value.
218func ToString(v Value) string {
219 if v.IsNull() {
220 return "null"
221 }
222 switch {
223 case v.IsFloat():
224 return fmt.Sprintf("%v", v.AsFloat())
225 case v.IsInt():
226 return fmt.Sprintf("%v", v.AsInt())
227 case v.IsString():
228 return fmt.Sprintf("%q", v.AsString())
229 case v.IsBool():
230 return fmt.Sprintf("%v", v.AsBool())
231 case v.IsList():
232 strs := []string{}
233 list := v.AsList()
234 for i := 0; i < list.Length(); i++ {
235 strs = append(strs, ToString(list.At(i)))
236 }
237 return "[" + strings.Join(strs, ",") + "]"
238 case v.IsMap():
239 strs := []string{}
240 v.AsMap().Iterate(func(k string, v Value) bool {
241 strs = append(strs, fmt.Sprintf("%v=%v", k, ToString(v)))
242 return true
243 })
244 return strings.Join(strs, "")
245 }
246 // No field is set, on either objects.
247 return "{{undefined}}"
248}
249
250// Less provides a total ordering for Value (so that they can be sorted, even
251// if they are of different types).
252func Less(lhs, rhs Value) bool {
253 return Compare(lhs, rhs) == -1
254}
255
256// Compare provides a total ordering for Value (so that they can be
257// sorted, even if they are of different types). The result will be 0 if
258// v==rhs, -1 if v < rhs, and +1 if v > rhs.
259func Compare(lhs, rhs Value) int {
260 return CompareUsing(HeapAllocator, lhs, rhs)
261}
262
263// CompareUsing uses the provided allocator and provides a total
264// ordering for Value (so that they can be sorted, even if they
265// are of different types). The result will be 0 if v==rhs, -1
266// if v < rhs, and +1 if v > rhs.
267func CompareUsing(a Allocator, lhs, rhs Value) int {
268 if lhs.IsFloat() {
269 if !rhs.IsFloat() {
270 // Extra: compare floats and ints numerically.
271 if rhs.IsInt() {
272 return FloatCompare(lhs.AsFloat(), float64(rhs.AsInt()))
273 }
274 return -1
275 }
276 return FloatCompare(lhs.AsFloat(), rhs.AsFloat())
277 } else if rhs.IsFloat() {
278 // Extra: compare floats and ints numerically.
279 if lhs.IsInt() {
280 return FloatCompare(float64(lhs.AsInt()), rhs.AsFloat())
281 }
282 return 1
283 }
284
285 if lhs.IsInt() {
286 if !rhs.IsInt() {
287 return -1
288 }
289 return IntCompare(lhs.AsInt(), rhs.AsInt())
290 } else if rhs.IsInt() {
291 return 1
292 }
293
294 if lhs.IsString() {
295 if !rhs.IsString() {
296 return -1
297 }
298 return strings.Compare(lhs.AsString(), rhs.AsString())
299 } else if rhs.IsString() {
300 return 1
301 }
302
303 if lhs.IsBool() {
304 if !rhs.IsBool() {
305 return -1
306 }
307 return BoolCompare(lhs.AsBool(), rhs.AsBool())
308 } else if rhs.IsBool() {
309 return 1
310 }
311
312 if lhs.IsList() {
313 if !rhs.IsList() {
314 return -1
315 }
316 lhsList := lhs.AsListUsing(a)
317 defer a.Free(lhsList)
318 rhsList := rhs.AsListUsing(a)
319 defer a.Free(rhsList)
320 return ListCompareUsing(a, lhsList, rhsList)
321 } else if rhs.IsList() {
322 return 1
323 }
324 if lhs.IsMap() {
325 if !rhs.IsMap() {
326 return -1
327 }
328 lhsMap := lhs.AsMapUsing(a)
329 defer a.Free(lhsMap)
330 rhsMap := rhs.AsMapUsing(a)
331 defer a.Free(rhsMap)
332 return MapCompareUsing(a, lhsMap, rhsMap)
333 } else if rhs.IsMap() {
334 return 1
335 }
336 if lhs.IsNull() {
337 if !rhs.IsNull() {
338 return -1
339 }
340 return 0
341 } else if rhs.IsNull() {
342 return 1
343 }
344
345 // Invalid Value-- nothing is set.
346 return 0
347}