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