1/*
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}
348