348 lines
8.7 KiB
Go
348 lines
8.7 KiB
Go
|
/*
|
||
|
Copyright 2018 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package value
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strings"
|
||
|
|
||
|
jsoniter "github.com/json-iterator/go"
|
||
|
"gopkg.in/yaml.v2"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
readPool = jsoniter.NewIterator(jsoniter.ConfigCompatibleWithStandardLibrary).Pool()
|
||
|
writePool = jsoniter.NewStream(jsoniter.ConfigCompatibleWithStandardLibrary, nil, 1024).Pool()
|
||
|
)
|
||
|
|
||
|
// A Value corresponds to an 'atom' in the schema. It should return true
|
||
|
// for at least one of the IsXXX methods below, or the value is
|
||
|
// considered "invalid"
|
||
|
type Value interface {
|
||
|
// IsMap returns true if the Value is a Map, false otherwise.
|
||
|
IsMap() bool
|
||
|
// IsList returns true if the Value is a List, false otherwise.
|
||
|
IsList() bool
|
||
|
// IsBool returns true if the Value is a bool, false otherwise.
|
||
|
IsBool() bool
|
||
|
// IsInt returns true if the Value is a int64, false otherwise.
|
||
|
IsInt() bool
|
||
|
// IsFloat returns true if the Value is a float64, false
|
||
|
// otherwise.
|
||
|
IsFloat() bool
|
||
|
// IsString returns true if the Value is a string, false
|
||
|
// otherwise.
|
||
|
IsString() bool
|
||
|
// IsMap returns true if the Value is null, false otherwise.
|
||
|
IsNull() bool
|
||
|
|
||
|
// AsMap converts the Value into a Map (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsMap() Map
|
||
|
// AsMapUsing uses the provided allocator and converts the Value
|
||
|
// into a Map (or panic if the type doesn't allow it).
|
||
|
AsMapUsing(Allocator) Map
|
||
|
// AsList converts the Value into a List (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsList() List
|
||
|
// AsListUsing uses the provided allocator and converts the Value
|
||
|
// into a List (or panic if the type doesn't allow it).
|
||
|
AsListUsing(Allocator) List
|
||
|
// AsBool converts the Value into a bool (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsBool() bool
|
||
|
// AsInt converts the Value into an int64 (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsInt() int64
|
||
|
// AsFloat converts the Value into a float64 (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsFloat() float64
|
||
|
// AsString converts the Value into a string (or panic if the type
|
||
|
// doesn't allow it).
|
||
|
AsString() string
|
||
|
|
||
|
// Unstructured converts the Value into an Unstructured interface{}.
|
||
|
Unstructured() interface{}
|
||
|
}
|
||
|
|
||
|
// FromJSON is a helper function for reading a JSON document.
|
||
|
func FromJSON(input []byte) (Value, error) {
|
||
|
return FromJSONFast(input)
|
||
|
}
|
||
|
|
||
|
// FromJSONFast is a helper function for reading a JSON document.
|
||
|
func FromJSONFast(input []byte) (Value, error) {
|
||
|
iter := readPool.BorrowIterator(input)
|
||
|
defer readPool.ReturnIterator(iter)
|
||
|
return ReadJSONIter(iter)
|
||
|
}
|
||
|
|
||
|
// ToJSON is a helper function for producing a JSon document.
|
||
|
func ToJSON(v Value) ([]byte, error) {
|
||
|
buf := bytes.Buffer{}
|
||
|
stream := writePool.BorrowStream(&buf)
|
||
|
defer writePool.ReturnStream(stream)
|
||
|
WriteJSONStream(v, stream)
|
||
|
b := stream.Buffer()
|
||
|
err := stream.Flush()
|
||
|
// Help jsoniter manage its buffers--without this, the next
|
||
|
// use of the stream is likely to require an allocation. Look
|
||
|
// at the jsoniter stream code to understand why. They were probably
|
||
|
// optimizing for folks using the buffer directly.
|
||
|
stream.SetBuffer(b[:0])
|
||
|
return buf.Bytes(), err
|
||
|
}
|
||
|
|
||
|
// ReadJSONIter reads a Value from a JSON iterator.
|
||
|
func ReadJSONIter(iter *jsoniter.Iterator) (Value, error) {
|
||
|
v := iter.Read()
|
||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||
|
return nil, iter.Error
|
||
|
}
|
||
|
return NewValueInterface(v), nil
|
||
|
}
|
||
|
|
||
|
// WriteJSONStream writes a value into a JSON stream.
|
||
|
func WriteJSONStream(v Value, stream *jsoniter.Stream) {
|
||
|
stream.WriteVal(v.Unstructured())
|
||
|
}
|
||
|
|
||
|
// ToYAML marshals a value as YAML.
|
||
|
func ToYAML(v Value) ([]byte, error) {
|
||
|
return yaml.Marshal(v.Unstructured())
|
||
|
}
|
||
|
|
||
|
// Equals returns true iff the two values are equal.
|
||
|
func Equals(lhs, rhs Value) bool {
|
||
|
return EqualsUsing(HeapAllocator, lhs, rhs)
|
||
|
}
|
||
|
|
||
|
// EqualsUsing uses the provided allocator and returns true iff the two values are equal.
|
||
|
func EqualsUsing(a Allocator, lhs, rhs Value) bool {
|
||
|
if lhs.IsFloat() || rhs.IsFloat() {
|
||
|
var lf float64
|
||
|
if lhs.IsFloat() {
|
||
|
lf = lhs.AsFloat()
|
||
|
} else if lhs.IsInt() {
|
||
|
lf = float64(lhs.AsInt())
|
||
|
} else {
|
||
|
return false
|
||
|
}
|
||
|
var rf float64
|
||
|
if rhs.IsFloat() {
|
||
|
rf = rhs.AsFloat()
|
||
|
} else if rhs.IsInt() {
|
||
|
rf = float64(rhs.AsInt())
|
||
|
} else {
|
||
|
return false
|
||
|
}
|
||
|
return lf == rf
|
||
|
}
|
||
|
if lhs.IsInt() {
|
||
|
if rhs.IsInt() {
|
||
|
return lhs.AsInt() == rhs.AsInt()
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsInt() {
|
||
|
return false
|
||
|
}
|
||
|
if lhs.IsString() {
|
||
|
if rhs.IsString() {
|
||
|
return lhs.AsString() == rhs.AsString()
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsString() {
|
||
|
return false
|
||
|
}
|
||
|
if lhs.IsBool() {
|
||
|
if rhs.IsBool() {
|
||
|
return lhs.AsBool() == rhs.AsBool()
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsBool() {
|
||
|
return false
|
||
|
}
|
||
|
if lhs.IsList() {
|
||
|
if rhs.IsList() {
|
||
|
lhsList := lhs.AsListUsing(a)
|
||
|
defer a.Free(lhsList)
|
||
|
rhsList := rhs.AsListUsing(a)
|
||
|
defer a.Free(rhsList)
|
||
|
return lhsList.EqualsUsing(a, rhsList)
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsList() {
|
||
|
return false
|
||
|
}
|
||
|
if lhs.IsMap() {
|
||
|
if rhs.IsMap() {
|
||
|
lhsList := lhs.AsMapUsing(a)
|
||
|
defer a.Free(lhsList)
|
||
|
rhsList := rhs.AsMapUsing(a)
|
||
|
defer a.Free(rhsList)
|
||
|
return lhsList.EqualsUsing(a, rhsList)
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsMap() {
|
||
|
return false
|
||
|
}
|
||
|
if lhs.IsNull() {
|
||
|
if rhs.IsNull() {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
} else if rhs.IsNull() {
|
||
|
return false
|
||
|
}
|
||
|
// No field is set, on either objects.
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// ToString returns a human-readable representation of the value.
|
||
|
func ToString(v Value) string {
|
||
|
if v.IsNull() {
|
||
|
return "null"
|
||
|
}
|
||
|
switch {
|
||
|
case v.IsFloat():
|
||
|
return fmt.Sprintf("%v", v.AsFloat())
|
||
|
case v.IsInt():
|
||
|
return fmt.Sprintf("%v", v.AsInt())
|
||
|
case v.IsString():
|
||
|
return fmt.Sprintf("%q", v.AsString())
|
||
|
case v.IsBool():
|
||
|
return fmt.Sprintf("%v", v.AsBool())
|
||
|
case v.IsList():
|
||
|
strs := []string{}
|
||
|
list := v.AsList()
|
||
|
for i := 0; i < list.Length(); i++ {
|
||
|
strs = append(strs, ToString(list.At(i)))
|
||
|
}
|
||
|
return "[" + strings.Join(strs, ",") + "]"
|
||
|
case v.IsMap():
|
||
|
strs := []string{}
|
||
|
v.AsMap().Iterate(func(k string, v Value) bool {
|
||
|
strs = append(strs, fmt.Sprintf("%v=%v", k, ToString(v)))
|
||
|
return true
|
||
|
})
|
||
|
return strings.Join(strs, "")
|
||
|
}
|
||
|
// No field is set, on either objects.
|
||
|
return "{{undefined}}"
|
||
|
}
|
||
|
|
||
|
// Less provides a total ordering for Value (so that they can be sorted, even
|
||
|
// if they are of different types).
|
||
|
func Less(lhs, rhs Value) bool {
|
||
|
return Compare(lhs, rhs) == -1
|
||
|
}
|
||
|
|
||
|
// Compare provides a total ordering for Value (so that they can be
|
||
|
// sorted, even if they are of different types). The result will be 0 if
|
||
|
// v==rhs, -1 if v < rhs, and +1 if v > rhs.
|
||
|
func Compare(lhs, rhs Value) int {
|
||
|
return CompareUsing(HeapAllocator, lhs, rhs)
|
||
|
}
|
||
|
|
||
|
// CompareUsing uses the provided allocator and provides a total
|
||
|
// ordering for Value (so that they can be sorted, even if they
|
||
|
// are of different types). The result will be 0 if v==rhs, -1
|
||
|
// if v < rhs, and +1 if v > rhs.
|
||
|
func CompareUsing(a Allocator, lhs, rhs Value) int {
|
||
|
if lhs.IsFloat() {
|
||
|
if !rhs.IsFloat() {
|
||
|
// Extra: compare floats and ints numerically.
|
||
|
if rhs.IsInt() {
|
||
|
return FloatCompare(lhs.AsFloat(), float64(rhs.AsInt()))
|
||
|
}
|
||
|
return -1
|
||
|
}
|
||
|
return FloatCompare(lhs.AsFloat(), rhs.AsFloat())
|
||
|
} else if rhs.IsFloat() {
|
||
|
// Extra: compare floats and ints numerically.
|
||
|
if lhs.IsInt() {
|
||
|
return FloatCompare(float64(lhs.AsInt()), rhs.AsFloat())
|
||
|
}
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if lhs.IsInt() {
|
||
|
if !rhs.IsInt() {
|
||
|
return -1
|
||
|
}
|
||
|
return IntCompare(lhs.AsInt(), rhs.AsInt())
|
||
|
} else if rhs.IsInt() {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if lhs.IsString() {
|
||
|
if !rhs.IsString() {
|
||
|
return -1
|
||
|
}
|
||
|
return strings.Compare(lhs.AsString(), rhs.AsString())
|
||
|
} else if rhs.IsString() {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if lhs.IsBool() {
|
||
|
if !rhs.IsBool() {
|
||
|
return -1
|
||
|
}
|
||
|
return BoolCompare(lhs.AsBool(), rhs.AsBool())
|
||
|
} else if rhs.IsBool() {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if lhs.IsList() {
|
||
|
if !rhs.IsList() {
|
||
|
return -1
|
||
|
}
|
||
|
lhsList := lhs.AsListUsing(a)
|
||
|
defer a.Free(lhsList)
|
||
|
rhsList := rhs.AsListUsing(a)
|
||
|
defer a.Free(rhsList)
|
||
|
return ListCompareUsing(a, lhsList, rhsList)
|
||
|
} else if rhs.IsList() {
|
||
|
return 1
|
||
|
}
|
||
|
if lhs.IsMap() {
|
||
|
if !rhs.IsMap() {
|
||
|
return -1
|
||
|
}
|
||
|
lhsMap := lhs.AsMapUsing(a)
|
||
|
defer a.Free(lhsMap)
|
||
|
rhsMap := rhs.AsMapUsing(a)
|
||
|
defer a.Free(rhsMap)
|
||
|
return MapCompareUsing(a, lhsMap, rhsMap)
|
||
|
} else if rhs.IsMap() {
|
||
|
return 1
|
||
|
}
|
||
|
if lhs.IsNull() {
|
||
|
if !rhs.IsNull() {
|
||
|
return -1
|
||
|
}
|
||
|
return 0
|
||
|
} else if rhs.IsNull() {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// Invalid Value-- nothing is set.
|
||
|
return 0
|
||
|
}
|