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