Update Structured Merge Diff to V3

This commit is contained in:
jennybuckley
2020-01-21 15:03:56 -08:00
committed by Jennifer Buckley
parent c9b4cf3d25
commit b33fbc84d9
105 changed files with 2848 additions and 1849 deletions

View File

@@ -0,0 +1,42 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"fields.go",
"jsontagutil.go",
"list.go",
"listreflect.go",
"listunstructured.go",
"map.go",
"mapreflect.go",
"mapunstructured.go",
"scalar.go",
"structreflect.go",
"value.go",
"valuereflect.go",
"valueunstructured.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/v3/value",
importpath = "sigs.k8s.io/structured-merge-diff/v3/value",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,21 @@
/*
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 defines types for an in-memory representation of yaml or json
// objects, organized for convenient comparison with a schema (as defined by
// the sibling schema package). Functions for reading and writing the objects
// are also provided.
package value

View File

@@ -0,0 +1,97 @@
/*
Copyright 2019 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 (
"sort"
"strings"
)
// Field is an individual key-value pair.
type Field struct {
Name string
Value Value
}
// FieldList is a list of key-value pairs. Each field is expectUpdated to
// have a different name.
type FieldList []Field
// Sort sorts the field list by Name.
func (f FieldList) Sort() {
if len(f) < 2 {
return
}
if len(f) == 2 {
if f[1].Name < f[0].Name {
f[0], f[1] = f[1], f[0]
}
return
}
sort.SliceStable(f, func(i, j int) bool {
return f[i].Name < f[j].Name
})
}
// Less compares two lists lexically.
func (f FieldList) Less(rhs FieldList) bool {
return f.Compare(rhs) == -1
}
// Compare compares two lists lexically. The result will be 0 if f==rhs, -1
// if f < rhs, and +1 if f > rhs.
func (f FieldList) Compare(rhs FieldList) int {
i := 0
for {
if i >= len(f) && i >= len(rhs) {
// Maps are the same length and all items are equal.
return 0
}
if i >= len(f) {
// F is shorter.
return -1
}
if i >= len(rhs) {
// RHS is shorter.
return 1
}
if c := strings.Compare(f[i].Name, rhs[i].Name); c != 0 {
return c
}
if c := Compare(f[i].Value, rhs[i].Value); c != 0 {
return c
}
// The items are equal; continue.
i++
}
}
// Equals returns true if the two fieldslist are equals, false otherwise.
func (f FieldList) Equals(rhs FieldList) bool {
if len(f) != len(rhs) {
return false
}
for i := range f {
if f[i].Name != rhs[i].Name {
return false
}
if !Equals(f[i].Value, rhs[i].Value) {
return false
}
}
return true
}

View File

@@ -0,0 +1,91 @@
/*
Copyright 2019 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 (
"fmt"
"reflect"
"strings"
)
// TODO: This implements the same functionality as https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L236
// but is based on the highly efficient approach from https://golang.org/src/encoding/json/encode.go
func lookupJsonTags(f reflect.StructField) (name string, omit bool, inline bool, omitempty bool) {
tag := f.Tag.Get("json")
if tag == "-" {
return "", true, false, false
}
name, opts := parseTag(tag)
if name == "" {
name = f.Name
}
return name, false, opts.Contains("inline"), opts.Contains("omitempty")
}
func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Chan, reflect.Func:
panic(fmt.Sprintf("unsupported type: %v", v.Type()))
}
return false
}
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

View File

@@ -0,0 +1,80 @@
/*
Copyright 2019 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
// List represents a list object.
type List interface {
// Length returns how many items can be found in the map.
Length() int
// At returns the item at the given position in the map. It will
// panic if the index is out of range.
At(int) Value
}
// ListEquals compares two lists lexically.
func ListEquals(lhs, rhs List) bool {
if lhs.Length() != rhs.Length() {
return false
}
for i := 0; i < lhs.Length(); i++ {
lv := lhs.At(i)
rv := rhs.At(i)
if !Equals(lv, rv) {
lv.Recycle()
rv.Recycle()
return false
}
lv.Recycle()
rv.Recycle()
}
return true
}
// ListLess compares two lists lexically.
func ListLess(lhs, rhs List) bool {
return ListCompare(lhs, rhs) == -1
}
// ListCompare compares two lists lexically. The result will be 0 if l==rhs, -1
// if l < rhs, and +1 if l > rhs.
func ListCompare(lhs, rhs List) int {
i := 0
for {
if i >= lhs.Length() && i >= rhs.Length() {
// Lists are the same length and all items are equal.
return 0
}
if i >= lhs.Length() {
// LHS is shorter.
return -1
}
if i >= rhs.Length() {
// RHS is shorter.
return 1
}
lv := lhs.At(i)
rv := rhs.At(i)
if c := Compare(lv, rv); c != 0 {
return c
}
lv.Recycle()
rv.Recycle()
// The items are equal; continue.
i++
}
}

View File

@@ -0,0 +1,42 @@
/*
Copyright 2019 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 "reflect"
type listReflect struct {
Value reflect.Value
}
func (r listReflect) Length() int {
val := r.Value
return val.Len()
}
func (r listReflect) At(i int) Value {
val := r.Value
return mustWrapValueReflect(val.Index(i))
}
func (r listReflect) Unstructured() interface{} {
l := r.Length()
result := make([]interface{}, l)
for i := 0; i < l; i++ {
result[i] = r.At(i).Unstructured()
}
return result
}

View File

@@ -0,0 +1,27 @@
/*
Copyright 2019 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
type listUnstructured []interface{}
func (l listUnstructured) Length() int {
return len(l)
}
func (l listUnstructured) At(i int) Value {
return NewValueInterface(l[i])
}

View File

@@ -0,0 +1,113 @@
/*
Copyright 2019 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 (
"sort"
"strings"
)
// Map represents a Map or go structure.
type Map interface {
// Set changes or set the value of the given key.
Set(key string, val Value)
// Get returns the value for the given key, if present, or (nil, false) otherwise.
Get(key string) (Value, bool)
// Has returns true if the key is present, or false otherwise.
Has(key string) bool
// Delete removes the key from the map.
Delete(key string)
// Equals compares the two maps, and return true if they are the same, false otherwise.
// Implementations can use MapEquals as a general implementation for this methods.
Equals(other Map) bool
// Iterate runs the given function for each key/value in the
// map. Returning false in the closure prematurely stops the
// iteration.
Iterate(func(key string, value Value) bool) bool
// Length returns the number of items in the map.
Length() int
}
// MapLess compares two maps lexically.
func MapLess(lhs, rhs Map) bool {
return MapCompare(lhs, rhs) == -1
}
// MapCompare compares two maps lexically.
func MapCompare(lhs, rhs Map) int {
lorder := make([]string, 0, lhs.Length())
lhs.Iterate(func(key string, _ Value) bool {
lorder = append(lorder, key)
return true
})
sort.Strings(lorder)
rorder := make([]string, 0, rhs.Length())
rhs.Iterate(func(key string, _ Value) bool {
rorder = append(rorder, key)
return true
})
sort.Strings(rorder)
i := 0
for {
if i >= len(lorder) && i >= len(rorder) {
// Maps are the same length and all items are equal.
return 0
}
if i >= len(lorder) {
// LHS is shorter.
return -1
}
if i >= len(rorder) {
// RHS is shorter.
return 1
}
if c := strings.Compare(lorder[i], rorder[i]); c != 0 {
return c
}
litem, _ := lhs.Get(lorder[i])
ritem, _ := rhs.Get(rorder[i])
if c := Compare(litem, ritem); c != 0 {
return c
}
litem.Recycle()
ritem.Recycle()
// The items are equal; continue.
i++
}
}
// MapEquals returns true if lhs == rhs, false otherwise. This function
// acts on generic types and should not be used by callers, but can help
// implement Map.Equals.
func MapEquals(lhs, rhs Map) bool {
if lhs.Length() != rhs.Length() {
return false
}
return lhs.Iterate(func(k string, v Value) bool {
vo, ok := rhs.Get(k)
if !ok {
return false
}
if !Equals(v, vo) {
vo.Recycle()
return false
}
vo.Recycle()
return true
})
}

View File

@@ -0,0 +1,107 @@
/*
Copyright 2019 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 "reflect"
type mapReflect struct {
valueReflect
}
func (r mapReflect) Length() int {
val := r.Value
return val.Len()
}
func (r mapReflect) Get(key string) (Value, bool) {
mapKey := r.toMapKey(key)
val := r.Value.MapIndex(mapKey)
if !val.IsValid() {
return nil, false
}
return mustWrapValueReflectMapItem(&r.Value, &mapKey, val), val != reflect.Value{}
}
func (r mapReflect) Has(key string) bool {
var val reflect.Value
val = r.Value.MapIndex(r.toMapKey(key))
if !val.IsValid() {
return false
}
return val != reflect.Value{}
}
func (r mapReflect) Set(key string, val Value) {
r.Value.SetMapIndex(r.toMapKey(key), reflect.ValueOf(val.Unstructured()))
}
func (r mapReflect) Delete(key string) {
val := r.Value
val.SetMapIndex(r.toMapKey(key), reflect.Value{})
}
// TODO: Do we need to support types that implement json.Marshaler and are used as string keys?
func (r mapReflect) toMapKey(key string) reflect.Value {
val := r.Value
return reflect.ValueOf(key).Convert(val.Type().Key())
}
func (r mapReflect) Iterate(fn func(string, Value) bool) bool {
return eachMapEntry(r.Value, func(s string, value reflect.Value) bool {
mapVal := mustWrapValueReflect(value)
defer mapVal.Recycle()
return fn(s, mapVal)
})
}
func eachMapEntry(val reflect.Value, fn func(string, reflect.Value) bool) bool {
iter := val.MapRange()
for iter.Next() {
next := iter.Value()
if !next.IsValid() {
continue
}
if !fn(iter.Key().String(), next) {
return false
}
}
return true
}
func (r mapReflect) Unstructured() interface{} {
result := make(map[string]interface{}, r.Length())
r.Iterate(func(s string, value Value) bool {
result[s] = value.Unstructured()
return true
})
return result
}
func (r mapReflect) Equals(m Map) bool {
if r.Length() != m.Length() {
return false
}
// TODO: Optimize to avoid Iterate looping here by using r.Value.MapRange or similar if it improves performance.
return m.Iterate(func(key string, value Value) bool {
lhsVal, ok := r.Get(key)
if !ok {
return false
}
return Equals(lhsVal, value)
})
}

View File

@@ -0,0 +1,145 @@
/*
Copyright 2019 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
type mapUnstructuredInterface map[interface{}]interface{}
func (m mapUnstructuredInterface) Set(key string, val Value) {
m[key] = val.Unstructured()
}
func (m mapUnstructuredInterface) Get(key string) (Value, bool) {
if v, ok := m[key]; !ok {
return nil, false
} else {
return NewValueInterface(v), true
}
}
func (m mapUnstructuredInterface) Has(key string) bool {
_, ok := m[key]
return ok
}
func (m mapUnstructuredInterface) Delete(key string) {
delete(m, key)
}
func (m mapUnstructuredInterface) Iterate(fn func(key string, value Value) bool) bool {
for k, v := range m {
if ks, ok := k.(string); !ok {
continue
} else {
vv := NewValueInterface(v)
if !fn(ks, vv) {
vv.Recycle()
return false
}
vv.Recycle()
}
}
return true
}
func (m mapUnstructuredInterface) Length() int {
return len(m)
}
func (m mapUnstructuredInterface) Equals(other Map) bool {
if m.Length() != other.Length() {
return false
}
for k, v := range m {
ks, ok := k.(string)
if !ok {
return false
}
vo, ok := other.Get(ks)
if !ok {
return false
}
vv := NewValueInterface(v)
if !Equals(vv, vo) {
vv.Recycle()
vo.Recycle()
return false
}
vo.Recycle()
vv.Recycle()
}
return true
}
type mapUnstructuredString map[string]interface{}
func (m mapUnstructuredString) Set(key string, val Value) {
m[key] = val.Unstructured()
}
func (m mapUnstructuredString) Get(key string) (Value, bool) {
if v, ok := m[key]; !ok {
return nil, false
} else {
return NewValueInterface(v), true
}
}
func (m mapUnstructuredString) Has(key string) bool {
_, ok := m[key]
return ok
}
func (m mapUnstructuredString) Delete(key string) {
delete(m, key)
}
func (m mapUnstructuredString) Iterate(fn func(key string, value Value) bool) bool {
for k, v := range m {
vv := NewValueInterface(v)
if !fn(k, vv) {
vv.Recycle()
return false
}
vv.Recycle()
}
return true
}
func (m mapUnstructuredString) Length() int {
return len(m)
}
func (m mapUnstructuredString) Equals(other Map) bool {
if m.Length() != other.Length() {
return false
}
for k, v := range m {
vo, ok := other.Get(k)
if !ok {
return false
}
vv := NewValueInterface(v)
if !Equals(vv, vo) {
vo.Recycle()
vv.Recycle()
return false
}
vo.Recycle()
vv.Recycle()
}
return true
}

View File

@@ -0,0 +1,50 @@
/*
Copyright 2019 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
// Compare compares floats. The result will be 0 if lhs==rhs, -1 if f <
// rhs, and +1 if f > rhs.
func FloatCompare(lhs, rhs float64) int {
if lhs > rhs {
return 1
} else if lhs < rhs {
return -1
}
return 0
}
// IntCompare compares integers. The result will be 0 if i==rhs, -1 if i <
// rhs, and +1 if i > rhs.
func IntCompare(lhs, rhs int64) int {
if lhs > rhs {
return 1
} else if lhs < rhs {
return -1
}
return 0
}
// Compare compares booleans. The result will be 0 if b==rhs, -1 if b <
// rhs, and +1 if b > rhs.
func BoolCompare(lhs, rhs bool) int {
if lhs == rhs {
return 0
} else if lhs == false {
return -1
}
return 1
}

View File

@@ -0,0 +1,269 @@
/*
Copyright 2019 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 (
"fmt"
"reflect"
"sync"
"sync/atomic"
)
// reflectStructCache keeps track of json tag related data for structs and fields to speed up reflection.
// TODO: This overlaps in functionality with the fieldCache in
// https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L57 but
// is more efficient at lookup by json field name. The logic should be consolidated. Only one copy of the cache needs
// to be kept for each running process.
var (
reflectStructCache = newStructCache()
)
type structCache struct {
// use an atomic and copy-on-write since there are a fixed (typically very small) number of structs compiled into any
// go program using this cache
value atomic.Value
// mu is held by writers when performing load/modify/store operations on the cache, readers do not need to hold a
// read-lock since the atomic value is always read-only
mu sync.Mutex
}
type structCacheMap map[reflect.Type]structCacheEntry
// structCacheEntry contains information about each struct field, keyed by json field name, that is expensive to
// compute using reflection.
type structCacheEntry map[string]*fieldCacheEntry
// Get returns true and fieldCacheEntry for the given type if the type is in the cache. Otherwise Get returns false.
func (c *structCache) Get(t reflect.Type) (map[string]*fieldCacheEntry, bool) {
entry, ok := c.value.Load().(structCacheMap)[t]
return entry, ok
}
// Set sets the fieldCacheEntry for the given type via a copy-on-write update to the struct cache.
func (c *structCache) Set(t reflect.Type, m map[string]*fieldCacheEntry) {
c.mu.Lock()
defer c.mu.Unlock()
currentCacheMap := c.value.Load().(structCacheMap)
if _, ok := currentCacheMap[t]; ok {
// Bail if the entry has been set while waiting for lock acquisition.
// This is safe since setting entries is idempotent.
return
}
newCacheMap := make(structCacheMap, len(currentCacheMap)+1)
for k, v := range currentCacheMap {
newCacheMap[k] = v
}
newCacheMap[t] = m
c.value.Store(newCacheMap)
}
func newStructCache() *structCache {
cache := &structCache{}
cache.value.Store(make(structCacheMap))
return cache
}
type fieldCacheEntry struct {
// isOmitEmpty is true if the field has the json 'omitempty' tag.
isOmitEmpty bool
// fieldPath is the field indices (see FieldByIndex) to lookup the value of
// a field in a reflect.Value struct. A path of field indices is used
// to support traversing to a field field in struct fields that have the 'inline'
// json tag.
fieldPath [][]int
}
func (f *fieldCacheEntry) getFieldFromStruct(structVal reflect.Value) reflect.Value {
// field might be field within 'inline' structs
for _, elem := range f.fieldPath {
structVal = structVal.FieldByIndex(elem)
}
return structVal
}
func getStructCacheEntry(t reflect.Type) structCacheEntry {
if hints, ok := reflectStructCache.Get(t); ok {
return hints
}
hints := map[string]*fieldCacheEntry{}
buildStructCacheEntry(t, hints, nil)
reflectStructCache.Set(t, hints)
return hints
}
func buildStructCacheEntry(t reflect.Type, infos map[string]*fieldCacheEntry, fieldPath [][]int) {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonName, omit, isInline, isOmitempty := lookupJsonTags(field)
if omit {
continue
}
if isInline {
buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
continue
}
info := &fieldCacheEntry{isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index)}
infos[jsonName] = info
}
}
type structReflect struct {
valueReflect
}
func (r structReflect) Length() int {
i := 0
eachStructField(r.Value, func(s string, value reflect.Value) bool {
i++
return true
})
return i
}
func (r structReflect) Get(key string) (Value, bool) {
if val, ok, _ := r.findJsonNameField(key); ok {
return mustWrapValueReflect(val), true
}
return nil, false
}
func (r structReflect) Has(key string) bool {
_, ok, _ := r.findJsonNameField(key)
return ok
}
func (r structReflect) Set(key string, val Value) {
fieldEntry, ok := getStructCacheEntry(r.Value.Type())[key]
if !ok {
panic(fmt.Sprintf("key %s may not be set on struct %T: field does not exist", key, r.Value.Interface()))
}
oldVal := fieldEntry.getFieldFromStruct(r.Value)
newVal := reflect.ValueOf(val.Unstructured())
r.update(fieldEntry, key, oldVal, newVal)
}
func (r structReflect) Delete(key string) {
fieldEntry, ok := getStructCacheEntry(r.Value.Type())[key]
if !ok {
panic(fmt.Sprintf("key %s may not be deleted on struct %T: field does not exist", key, r.Value.Interface()))
}
oldVal := fieldEntry.getFieldFromStruct(r.Value)
if oldVal.Kind() != reflect.Ptr && !fieldEntry.isOmitEmpty {
panic(fmt.Sprintf("key %s may not be deleted on struct: %T: value is neither a pointer nor an omitempty field", key, r.Value.Interface()))
}
r.update(fieldEntry, key, oldVal, reflect.Zero(oldVal.Type()))
}
func (r structReflect) update(fieldEntry *fieldCacheEntry, key string, oldVal, newVal reflect.Value) {
if oldVal.CanSet() {
oldVal.Set(newVal)
return
}
// map items are not addressable, so if a struct is contained in a map, the only way to modify it is
// to write a replacement fieldEntry into the map.
if r.ParentMap != nil {
if r.ParentMapKey == nil {
panic("ParentMapKey must not be nil if ParentMap is not nil")
}
replacement := reflect.New(r.Value.Type()).Elem()
fieldEntry.getFieldFromStruct(replacement).Set(newVal)
r.ParentMap.SetMapIndex(*r.ParentMapKey, replacement)
return
}
// This should never happen since NewValueReflect ensures that the root object reflected on is a pointer and map
// item replacement is handled above.
panic(fmt.Sprintf("key %s may not be modified on struct: %T: struct is not settable", key, r.Value.Interface()))
}
func (r structReflect) Iterate(fn func(string, Value) bool) bool {
return eachStructField(r.Value, func(s string, value reflect.Value) bool {
v := mustWrapValueReflect(value)
defer v.Recycle()
return fn(s, v)
})
}
func eachStructField(structVal reflect.Value, fn func(string, reflect.Value) bool) bool {
for jsonName, fieldCacheEntry := range getStructCacheEntry(structVal.Type()) {
fieldVal := fieldCacheEntry.getFieldFromStruct(structVal)
if fieldCacheEntry.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal)) {
// omit it
continue
}
ok := fn(jsonName, fieldVal)
if !ok {
return false
}
}
return true
}
func (r structReflect) Unstructured() interface{} {
// Use number of struct fields as a cheap way to rough estimate map size
result := make(map[string]interface{}, r.Value.NumField())
r.Iterate(func(s string, value Value) bool {
result[s] = value.Unstructured()
return true
})
return result
}
func (r structReflect) Equals(m Map) bool {
if rhsStruct, ok := m.(structReflect); ok {
return reflect.DeepEqual(r.Value.Interface(), rhsStruct.Value.Interface())
}
if r.Length() != m.Length() {
return false
}
structCacheEntry := getStructCacheEntry(r.Value.Type())
return m.Iterate(func(s string, value Value) bool {
fieldCacheEntry, ok := structCacheEntry[s]
if !ok {
return false
}
lhsVal := fieldCacheEntry.getFieldFromStruct(r.Value)
return Equals(mustWrapValueReflect(lhsVal), value)
})
}
func (r structReflect) findJsonNameFieldAndNotEmpty(jsonName string) (reflect.Value, bool) {
structCacheEntry, ok := getStructCacheEntry(r.Value.Type())[jsonName]
if !ok {
return reflect.Value{}, false
}
fieldVal := structCacheEntry.getFieldFromStruct(r.Value)
omit := structCacheEntry.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal))
return fieldVal, !omit
}
func (r structReflect) findJsonNameField(jsonName string) (val reflect.Value, ok bool, omitEmpty bool) {
structCacheEntry, ok := getStructCacheEntry(r.Value.Type())[jsonName]
if !ok {
return reflect.Value{}, false, false
}
fieldVal := structCacheEntry.getFieldFromStruct(r.Value)
return fieldVal, true, structCacheEntry.isOmitEmpty
}

View File

@@ -0,0 +1,315 @@
/*
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
// AsList converts the Value into a List (or panic if the type
// doesn't allow it).
AsList() 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
// Recycle returns a value of this type that is no longer needed. The
// value shouldn't be used after this call.
Recycle()
// 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 {
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() {
return ListEquals(lhs.AsList(), rhs.AsList())
}
return false
} else if rhs.IsList() {
return false
}
if lhs.IsMap() {
if rhs.IsMap() {
return lhs.AsMap().Equals(rhs.AsMap())
}
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{}
for i := 0; i < v.AsList().Length(); i++ {
strs = append(strs, ToString(v.AsList().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 {
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
}
return ListCompare(lhs.AsList(), rhs.AsList())
} else if rhs.IsList() {
return 1
}
if lhs.IsMap() {
if !rhs.IsMap() {
return -1
}
return MapCompare(lhs.AsMap(), rhs.AsMap())
} 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
}

View File

@@ -0,0 +1,319 @@
/*
Copyright 2019 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"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"sync"
)
var reflectPool = sync.Pool{
New: func() interface{} {
return &valueReflect{}
},
}
// NewValueReflect creates a Value backed by an "interface{}" type,
// typically an structured object in Kubernetes world that is uses reflection to expose.
// The provided "interface{}" value must be a pointer so that the value can be modified via reflection.
// The provided "interface{}" may contain structs and types that are converted to Values
// by the jsonMarshaler interface.
func NewValueReflect(value interface{}) (Value, error) {
if value == nil {
return NewValueInterface(nil), nil
}
v := reflect.ValueOf(value)
if v.Kind() != reflect.Ptr {
// The root value to reflect on must be a pointer so that map.Set() and map.Delete() operations are possible.
return nil, fmt.Errorf("value provided to NewValueReflect must be a pointer")
}
return wrapValueReflect(nil, nil, v)
}
func wrapValueReflect(parentMap, parentMapKey *reflect.Value, value reflect.Value) (Value, error) {
// TODO: conversion of json.Marshaller interface types is expensive. This can be mostly optimized away by
// introducing conversion functions that do not require going through JSON and using those here.
if marshaler, ok := getMarshaler(value); ok {
return toUnstructured(marshaler, value)
}
value = dereference(value)
val := reflectPool.Get().(*valueReflect)
val.Value = value
val.ParentMap = parentMap
val.ParentMapKey = parentMapKey
return Value(val), nil
}
func mustWrapValueReflect(value reflect.Value) Value {
v, err := wrapValueReflect(nil, nil, value)
if err != nil {
panic(err)
}
return v
}
func mustWrapValueReflectMapItem(parentMap, parentMapKey *reflect.Value, value reflect.Value) Value {
v, err := wrapValueReflect(parentMap, parentMapKey, value)
if err != nil {
panic(err)
}
return v
}
func dereference(val reflect.Value) reflect.Value {
kind := val.Kind()
if (kind == reflect.Interface || kind == reflect.Ptr) && !safeIsNil(val) {
return val.Elem()
}
return val
}
type valueReflect struct {
ParentMap *reflect.Value
ParentMapKey *reflect.Value
Value reflect.Value
}
func (r valueReflect) IsMap() bool {
return r.isKind(reflect.Map, reflect.Struct)
}
func (r valueReflect) IsList() bool {
typ := r.Value.Type()
return typ.Kind() == reflect.Slice && !(typ.Elem().Kind() == reflect.Uint8)
}
func (r valueReflect) IsBool() bool {
return r.isKind(reflect.Bool)
}
func (r valueReflect) IsInt() bool {
// Uint64 deliberately excluded, see valueUnstructured.Int.
return r.isKind(reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8)
}
func (r valueReflect) IsFloat() bool {
return r.isKind(reflect.Float64, reflect.Float32)
}
func (r valueReflect) IsString() bool {
kind := r.Value.Kind()
if kind == reflect.String {
return true
}
if kind == reflect.Slice && r.Value.Type().Elem().Kind() == reflect.Uint8 {
return true
}
return false
}
func (r valueReflect) IsNull() bool {
return safeIsNil(r.Value)
}
func (r valueReflect) isKind(kinds ...reflect.Kind) bool {
kind := r.Value.Kind()
for _, k := range kinds {
if kind == k {
return true
}
}
return false
}
// TODO find a cleaner way to avoid panics from reflect.IsNil()
func safeIsNil(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return v.IsNil()
}
return false
}
func (r valueReflect) AsMap() Map {
val := r.Value
switch val.Kind() {
case reflect.Struct:
return structReflect{r}
case reflect.Map:
return mapReflect{r}
default:
panic("value is not a map or struct")
}
}
func (r *valueReflect) Recycle() {
reflectPool.Put(r)
}
func (r valueReflect) AsList() List {
if r.IsList() {
return listReflect{r.Value}
}
panic("value is not a list")
}
func (r valueReflect) AsBool() bool {
if r.IsBool() {
return r.Value.Bool()
}
panic("value is not a bool")
}
func (r valueReflect) AsInt() int64 {
if r.isKind(reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8) {
return r.Value.Int()
}
if r.isKind(reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8) {
return int64(r.Value.Uint())
}
panic("value is not an int")
}
func (r valueReflect) AsFloat() float64 {
if r.IsFloat() {
return r.Value.Float()
}
panic("value is not a float")
}
func (r valueReflect) AsString() string {
kind := r.Value.Kind()
if kind == reflect.String {
return r.Value.String()
}
if kind == reflect.Slice && r.Value.Type().Elem().Kind() == reflect.Uint8 {
return base64.StdEncoding.EncodeToString(r.Value.Bytes())
}
panic("value is not a string")
}
func (r valueReflect) Unstructured() interface{} {
val := r.Value
switch {
case r.IsNull():
return nil
case val.Kind() == reflect.Struct:
return structReflect{r}.Unstructured()
case val.Kind() == reflect.Map:
return mapReflect{r}.Unstructured()
case r.IsList():
return listReflect{Value: r.Value}.Unstructured()
case r.IsString():
return r.AsString()
case r.IsInt():
return r.AsInt()
case r.IsBool():
return r.AsBool()
case r.IsFloat():
return r.AsFloat()
default:
panic(fmt.Sprintf("value of type %s is not a supported by value reflector", val.Type()))
}
}
// The below getMarshaler and toUnstructured functions are based on
// https://github.com/kubernetes/kubernetes/blob/40df9f82d0572a123f5ad13f48312978a2ff5877/staging/src/k8s.io/apimachinery/pkg/runtime/converter.go#L509
// and should somehow be consolidated with it
var marshalerType = reflect.TypeOf(new(json.Marshaler)).Elem()
func getMarshaler(v reflect.Value) (json.Marshaler, bool) {
// Check value receivers if v is not a pointer and pointer receivers if v is a pointer
if v.Type().Implements(marshalerType) {
return v.Interface().(json.Marshaler), true
}
// Check pointer receivers if v is not a pointer
if v.Kind() != reflect.Ptr && v.CanAddr() {
v = v.Addr()
if v.Type().Implements(marshalerType) {
return v.Interface().(json.Marshaler), true
}
}
return nil, false
}
var (
nullBytes = []byte("null")
trueBytes = []byte("true")
falseBytes = []byte("false")
)
func toUnstructured(marshaler json.Marshaler, sv reflect.Value) (Value, error) {
data, err := marshaler.MarshalJSON()
if err != nil {
return nil, err
}
switch {
case len(data) == 0:
return nil, fmt.Errorf("error decoding from json: empty value")
case bytes.Equal(data, nullBytes):
// We're done - we don't need to store anything.
return NewValueInterface(nil), nil
case bytes.Equal(data, trueBytes):
return NewValueInterface(true), nil
case bytes.Equal(data, falseBytes):
return NewValueInterface(false), nil
case data[0] == '"':
var result string
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding string from json: %v", err)
}
return NewValueInterface(result), nil
case data[0] == '{':
result := make(map[string]interface{})
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding object from json: %v", err)
}
return NewValueInterface(result), nil
case data[0] == '[':
result := make([]interface{}, 0)
err := json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("error decoding array from json: %v", err)
}
return NewValueInterface(result), nil
default:
var (
resultInt int64
resultFloat float64
err error
)
if err = json.Unmarshal(data, &resultInt); err == nil {
return NewValueInterface(resultInt), nil
}
if err = json.Unmarshal(data, &resultFloat); err == nil {
return NewValueInterface(resultFloat), nil
}
return nil, fmt.Errorf("error decoding number from json: %v", err)
}
}

View File

@@ -0,0 +1,177 @@
/*
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 (
"fmt"
"sync"
)
var viPool = sync.Pool{
New: func() interface{} {
return &valueUnstructured{}
},
}
// NewValueInterface creates a Value backed by an "interface{}" type,
// typically an unstructured object in Kubernetes world.
// interface{} must be one of: map[string]interface{}, map[interface{}]interface{}, []interface{}, int types, float types,
// string or boolean. Nested interface{} must also be one of these types.
func NewValueInterface(v interface{}) Value {
vi := viPool.Get().(*valueUnstructured)
vi.Value = v
return Value(vi)
}
type valueUnstructured struct {
Value interface{}
}
func (v valueUnstructured) IsMap() bool {
if _, ok := v.Value.(map[string]interface{}); ok {
return true
}
if _, ok := v.Value.(map[interface{}]interface{}); ok {
return true
}
return false
}
func (v valueUnstructured) AsMap() Map {
if v.Value == nil {
panic("invalid nil")
}
switch t := v.Value.(type) {
case map[string]interface{}:
return mapUnstructuredString(t)
case map[interface{}]interface{}:
return mapUnstructuredInterface(t)
}
panic(fmt.Errorf("not a map: %#v", v))
}
func (v valueUnstructured) IsList() bool {
if v.Value == nil {
return false
}
_, ok := v.Value.([]interface{})
return ok
}
func (v valueUnstructured) AsList() List {
return listUnstructured(v.Value.([]interface{}))
}
func (v valueUnstructured) IsFloat() bool {
if v.Value == nil {
return false
} else if _, ok := v.Value.(float64); ok {
return true
} else if _, ok := v.Value.(float32); ok {
return true
}
return false
}
func (v valueUnstructured) AsFloat() float64 {
if f, ok := v.Value.(float32); ok {
return float64(f)
}
return v.Value.(float64)
}
func (v valueUnstructured) IsInt() bool {
if v.Value == nil {
return false
} else if _, ok := v.Value.(int); ok {
return true
} else if _, ok := v.Value.(int8); ok {
return true
} else if _, ok := v.Value.(int16); ok {
return true
} else if _, ok := v.Value.(int32); ok {
return true
} else if _, ok := v.Value.(int64); ok {
return true
} else if _, ok := v.Value.(uint); ok {
return true
} else if _, ok := v.Value.(uint8); ok {
return true
} else if _, ok := v.Value.(uint16); ok {
return true
} else if _, ok := v.Value.(uint32); ok {
return true
}
return false
}
func (v valueUnstructured) AsInt() int64 {
if i, ok := v.Value.(int); ok {
return int64(i)
} else if i, ok := v.Value.(int8); ok {
return int64(i)
} else if i, ok := v.Value.(int16); ok {
return int64(i)
} else if i, ok := v.Value.(int32); ok {
return int64(i)
} else if i, ok := v.Value.(uint); ok {
return int64(i)
} else if i, ok := v.Value.(uint8); ok {
return int64(i)
} else if i, ok := v.Value.(uint16); ok {
return int64(i)
} else if i, ok := v.Value.(uint32); ok {
return int64(i)
}
return v.Value.(int64)
}
func (v valueUnstructured) IsString() bool {
if v.Value == nil {
return false
}
_, ok := v.Value.(string)
return ok
}
func (v valueUnstructured) AsString() string {
return v.Value.(string)
}
func (v valueUnstructured) IsBool() bool {
if v.Value == nil {
return false
}
_, ok := v.Value.(bool)
return ok
}
func (v valueUnstructured) AsBool() bool {
return v.Value.(bool)
}
func (v valueUnstructured) IsNull() bool {
return v.Value == nil
}
func (v *valueUnstructured) Recycle() {
viPool.Put(v)
}
func (v valueUnstructured) Unstructured() interface{} {
return v.Value
}