Merge pull request #85227 from apelisse/update-smd

Update structured-merge-diff to latest version
This commit is contained in:
Kubernetes Prow Robot 2019-11-13 20:02:24 -08:00 committed by GitHub
commit 85bc79d81f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 485 additions and 201 deletions

2
go.mod
View File

@ -559,7 +559,7 @@ replace (
mvdan.cc/lint => mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b
mvdan.cc/unparam => mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34
sigs.k8s.io/kustomize => sigs.k8s.io/kustomize v2.0.3+incompatible
sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca
sigs.k8s.io/structured-merge-diff => sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06
sigs.k8s.io/yaml => sigs.k8s.io/yaml v1.1.0
sourcegraph.com/sqs/pbtypes => sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4
vbom.ml/util => vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc

4
go.sum
View File

@ -601,8 +601,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jC
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -446,7 +446,7 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -50,7 +50,7 @@ require (
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06
sigs.k8s.io/yaml v1.1.0
)

View File

@ -357,7 +357,7 @@ k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d h1:1P0iBJsBzxRmR+dIFnM+Iu4aLxnoa
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -77,7 +77,7 @@ func NewPathElement(s string) (fieldpath.PathElement, error) {
if err != nil {
return fieldpath.PathElement{}, err
}
fields := []value.Field{}
fields := value.FieldList{}
for k, v := range kv {
b, err := json.Marshal(v)
if err != nil {
@ -94,7 +94,7 @@ func NewPathElement(s string) (fieldpath.PathElement, error) {
})
}
return fieldpath.PathElement{
Key: &value.Map{Items: fields},
Key: &fields,
}, nil
default:
// Ignore unknown key types
@ -109,7 +109,7 @@ func PathElementString(pe fieldpath.PathElement) (string, error) {
return Field + Separator + *pe.FieldName, nil
case pe.Key != nil:
kv := map[string]json.RawMessage{}
for _, k := range pe.Key.Items {
for _, k := range *pe.Key {
b, err := k.Value.ToJSON()
if err != nil {
return "", err

View File

@ -397,7 +397,7 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -352,6 +352,6 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d h1:1P0iBJsBzxRmR+dIFnM+Iu4aLxnoa7lBqozW/0uHbT8=
k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -394,7 +394,7 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

2
vendor/modules.txt vendored
View File

@ -1929,7 +1929,7 @@ sigs.k8s.io/kustomize/pkg/transformers
sigs.k8s.io/kustomize/pkg/transformers/config
sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig
sigs.k8s.io/kustomize/pkg/types
# sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca => sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca
# sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 => sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06
sigs.k8s.io/structured-merge-diff/fieldpath
sigs.k8s.io/structured-merge-diff/merge
sigs.k8s.io/structured-merge-diff/schema

View File

@ -8,6 +8,7 @@ go_library(
"fromvalue.go",
"managers.go",
"path.go",
"pathelementmap.go",
"serialize.go",
"serialize-pe.go",
"set.go",

View File

@ -35,7 +35,7 @@ type PathElement struct {
// Key selects the list element which has fields matching those given.
// The containing object must be an associative list with map typed
// elements.
Key *value.Map
Key *value.FieldList
// Value selects the list element with the given value. The containing
// object must be an associative list with a primitive typed element
@ -62,7 +62,7 @@ func (e PathElement) Less(rhs PathElement) bool {
if rhs.Key == nil {
return true
}
return e.Key.Less(rhs.Key)
return e.Key.Less(*rhs.Key)
} else if rhs.Key != nil {
return false
}
@ -101,13 +101,11 @@ func (e PathElement) String() string {
case e.FieldName != nil:
return "." + *e.FieldName
case e.Key != nil:
strs := make([]string, len(e.Key.Items))
for i, k := range e.Key.Items {
strs := make([]string, len(*e.Key))
for i, k := range *e.Key {
strs[i] = fmt.Sprintf("%v=%v", k.Name, k.Value)
}
// The order must be canonical, since we use the string value
// in a set structure.
sort.Strings(strs)
// Keys are supposed to be sorted.
return "[" + strings.Join(strs, ",") + "]"
case e.Value != nil:
return fmt.Sprintf("[=%v]", e.Value)
@ -123,18 +121,19 @@ func (e PathElement) String() string {
// names (type must be string) with values (type must be value.Value). If these
// conditions are not met, KeyByFields will panic--it's intended for static
// construction and shouldn't have user-produced values passed to it.
func KeyByFields(nameValues ...interface{}) []value.Field {
func KeyByFields(nameValues ...interface{}) *value.FieldList {
if len(nameValues)%2 != 0 {
panic("must have a value for every name")
}
out := []value.Field{}
out := value.FieldList{}
for i := 0; i < len(nameValues)-1; i += 2 {
out = append(out, value.Field{
Name: nameValues[i].(string),
Value: nameValues[i+1].(value.Value),
})
}
return out
out.Sort()
return &out
}
// PathElementSet is a set of path elements.
@ -143,6 +142,12 @@ type PathElementSet struct {
members sortedPathElements
}
func MakePathElementSet(size int) PathElementSet {
return PathElementSet{
members: make(sortedPathElements, 0, size),
}
}
type sortedPathElements []PathElement
// Implement the sort interface; this would permit bulk creation, which would

View File

@ -104,7 +104,7 @@ func GuessBestListPathElement(index int, item value.Value) PathElement {
return PathElement{Index: &index}
}
var keys []value.Field
var keys value.FieldList
for _, name := range AssociativeListCandidateFieldNames {
f, ok := item.MapValue.Get(name)
if !ok {
@ -117,7 +117,8 @@ func GuessBestListPathElement(index int, item value.Value) PathElement {
keys = append(keys, *f)
}
if len(keys) > 0 {
return PathElement{Key: &value.Map{Items: keys}}
keys.Sort()
return PathElement{Key: &keys}
}
return PathElement{Index: &index}
}

View File

@ -88,11 +88,11 @@ func MakePath(parts ...interface{}) (Path, error) {
fp = append(fp, PathElement{Index: &t})
case string:
fp = append(fp, PathElement{FieldName: &t})
case []value.Field:
if len(t) == 0 {
case *value.FieldList:
if len(*t) == 0 {
return nil, fmt.Errorf("associative list key type path elements must have at least one key (got zero)")
}
fp = append(fp, PathElement{Key: &value.Map{Items: t}})
fp = append(fp, PathElement{Key: t})
case value.Value:
// TODO: understand schema and verify that this is a set type
// TODO: make a copy of t

View File

@ -0,0 +1,85 @@
/*
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 fieldpath
import (
"sort"
"sigs.k8s.io/structured-merge-diff/value"
)
// PathElementValueMap is a map from PathElement to value.Value.
//
// TODO(apelisse): We have multiple very similar implementation of this
// for PathElementSet and SetNodeMap, so we could probably share the
// code.
type PathElementValueMap struct {
members sortedPathElementValues
}
func MakePathElementValueMap(size int) PathElementValueMap {
return PathElementValueMap{
members: make(sortedPathElementValues, 0, size),
}
}
type pathElementValue struct {
PathElement PathElement
Value value.Value
}
type sortedPathElementValues []pathElementValue
// Implement the sort interface; this would permit bulk creation, which would
// be faster than doing it one at a time via Insert.
func (spev sortedPathElementValues) Len() int { return len(spev) }
func (spev sortedPathElementValues) Less(i, j int) bool {
return spev[i].PathElement.Less(spev[j].PathElement)
}
func (spev sortedPathElementValues) Swap(i, j int) { spev[i], spev[j] = spev[j], spev[i] }
// Insert adds the pathelement and associated value in the map.
func (s *PathElementValueMap) Insert(pe PathElement, v value.Value) {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].PathElement.Less(pe)
})
if loc == len(s.members) {
s.members = append(s.members, pathElementValue{pe, v})
return
}
if s.members[loc].PathElement.Equals(pe) {
return
}
s.members = append(s.members, pathElementValue{})
copy(s.members[loc+1:], s.members[loc:])
s.members[loc] = pathElementValue{pe, v}
}
// Get retrieves the value associated with the given PathElement from the map.
// (nil, false) is returned if there is no such PathElement.
func (s *PathElementValueMap) Get(pe PathElement) (value.Value, bool) {
loc := sort.Search(len(s.members), func(i int) bool {
return !s.members[i].PathElement.Less(pe)
})
if loc == len(s.members) {
return value.Value{}, false
}
if s.members[loc].PathElement.Equals(pe) {
return s.members[loc].Value, true
}
return value.Value{}, false
}

View File

@ -83,14 +83,18 @@ func DeserializePathElement(s string) (PathElement, error) {
case peKeySepBytes[0]:
iter := readPool.BorrowIterator(b)
defer readPool.ReturnIterator(iter)
v, err := value.ReadJSONIter(iter)
if err != nil {
return PathElement{}, err
}
if v.MapValue == nil {
return PathElement{}, fmt.Errorf("expected key value pairs but got %#v", v)
}
return PathElement{Key: v.MapValue}, nil
fields := value.FieldList{}
iter.ReadObjectCB(func(iter *jsoniter.Iterator, key string) bool {
v, err := value.ReadJSONIter(iter)
if err != nil {
iter.Error = err
return false
}
fields = append(fields, value.Field{Name: key, Value: v})
return true
})
fields.Sort()
return PathElement{Key: &fields}, iter.Error
case peIndexSepBytes[0]:
i, err := strconv.Atoi(s[2:])
if err != nil {
@ -129,8 +133,15 @@ func serializePathElementToWriter(w io.Writer, pe PathElement) error {
if _, err := stream.Write(peKeySepBytes); err != nil {
return err
}
v := value.Value{MapValue: pe.Key}
v.WriteJSONStream(stream)
stream.WriteObjectStart()
for i, field := range *pe.Key {
if i > 0 {
stream.WriteMore()
}
stream.WriteObjectField(field.Name)
field.Value.WriteJSONStream(stream)
}
stream.WriteObjectEnd()
case pe.Value != nil:
if _, err := stream.Write(peValueSepBytes); err != nil {
return err

View File

@ -41,12 +41,12 @@ func (s *Updater) EnableUnionFeature() {
s.enableUnions = true
}
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, *typed.Comparison, error) {
conflicts := fieldpath.ManagedFields{}
removed := fieldpath.ManagedFields{}
compare, err := oldObject.Compare(newObject)
if err != nil {
return nil, fmt.Errorf("failed to compare objects: %v", err)
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions := map[fieldpath.APIVersion]*typed.Comparison{
@ -66,7 +66,7 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
delete(managers, manager)
continue
}
return nil, fmt.Errorf("failed to convert old object: %v", err)
return nil, nil, fmt.Errorf("failed to convert old object: %v", err)
}
versionedNewObject, err := s.Converter.Convert(newObject, managerSet.APIVersion())
if err != nil {
@ -74,11 +74,11 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
delete(managers, manager)
continue
}
return nil, fmt.Errorf("failed to convert new object: %v", err)
return nil, nil, fmt.Errorf("failed to convert new object: %v", err)
}
compare, err = versionedOldObject.Compare(versionedNewObject)
if err != nil {
return nil, fmt.Errorf("failed to compare objects: %v", err)
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions[managerSet.APIVersion()] = compare
}
@ -94,7 +94,7 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
}
if !force && len(conflicts) != 0 {
return nil, ConflictsFromManagers(conflicts)
return nil, nil, ConflictsFromManagers(conflicts)
}
for manager, conflictSet := range conflicts {
@ -111,7 +111,7 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
}
}
return managers, nil
return managers, compare, nil
}
// Update is the method you should call once you've merged your final
@ -128,14 +128,10 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
}
}
managers = shallowCopyManagers(managers)
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
managers, compare, err := s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
compare, err := liveObject.Compare(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
}
if _, ok := managers[manager]; !ok {
managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
}
@ -182,7 +178,7 @@ func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fiel
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
}
managers, err = s.update(liveObject, newObject, version, managers, manager, force)
managers, _, err = s.update(liveObject, newObject, version, managers, manager, force)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}

View File

@ -16,9 +16,17 @@ limitations under the License.
package schema
import "sync"
// Schema is a list of named types.
//
// Schema types are indexed in a map before the first search so this type
// should be considered immutable.
type Schema struct {
Types []TypeDef `yaml:"types,omitempty"`
once sync.Once
m map[string]TypeDef
}
// A TypeSpecifier references a particular type in a schema.
@ -92,6 +100,9 @@ const (
//
// Maps may also represent a type which is composed of a number of different fields.
// Each field has a name and a type.
//
// Fields are indexed in a map before the first search so this type
// should be considered immutable.
type Map struct {
// Each struct field appears exactly once in this list. The order in
// this list defines the canonical field ordering.
@ -117,6 +128,22 @@ type Map struct {
// The default behavior for maps is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
once sync.Once
m map[string]StructField
}
// FindField is a convenience function that returns the referenced StructField,
// if it exists, or (nil, false) if it doesn't.
func (m *Map) FindField(name string) (StructField, bool) {
m.once.Do(func() {
m.m = make(map[string]StructField, len(m.Fields))
for _, field := range m.Fields {
m.m[field.Name] = field
}
})
sf, ok := m.m[name]
return sf, ok
}
// UnionFields are mapping between the fields that are part of the union and
@ -204,13 +231,15 @@ type List struct {
// FindNamedType is a convenience function that returns the referenced TypeDef,
// if it exists, or (nil, false) if it doesn't.
func (s Schema) FindNamedType(name string) (TypeDef, bool) {
for _, t := range s.Types {
if t.Name == name {
return t, true
func (s *Schema) FindNamedType(name string) (TypeDef, bool) {
s.once.Do(func() {
s.m = make(map[string]TypeDef, len(s.Types))
for _, t := range s.Types {
s.m[t.Name] = t
}
}
return TypeDef{}, false
})
t, ok := s.m[name]
return t, ok
}
// Resolve is a convenience function which returns the atom referenced, whether

View File

@ -97,9 +97,9 @@ func (ef errorFormatter) prefixError(prefix string, err error) ValidationErrors
}
type atomHandler interface {
doScalar(schema.Scalar) ValidationErrors
doList(schema.List) ValidationErrors
doMap(schema.Map) ValidationErrors
doScalar(*schema.Scalar) ValidationErrors
doList(*schema.List) ValidationErrors
doMap(*schema.Map) ValidationErrors
errorf(msg string, args ...interface{}) ValidationErrors
}
@ -130,11 +130,11 @@ func deduceAtom(a schema.Atom, v *value.Value) schema.Atom {
func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErrors {
switch {
case a.Map != nil:
return ah.doMap(*a.Map)
return ah.doMap(a.Map)
case a.Scalar != nil:
return ah.doScalar(*a.Scalar)
return ah.doScalar(a.Scalar)
case a.List != nil:
return ah.doList(*a.List)
return ah.doList(a.List)
}
name := "inlined"
@ -145,14 +145,14 @@ func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErro
return ah.errorf("schema error: invalid atom: %v", name)
}
func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix string) (errs ValidationErrors) {
func (ef errorFormatter) validateScalar(t *schema.Scalar, v *value.Value, prefix string) (errs ValidationErrors) {
if v == nil {
return nil
}
if v.Null {
return nil
}
switch t {
switch *t {
case schema.Numeric:
if v.FloatValue == nil && v.IntValue == nil {
// TODO: should the schema separate int and float?
@ -195,7 +195,7 @@ func mapValue(val value.Value) (*value.Map, error) {
}
}
func keyedAssociativeListItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
func keyedAssociativeListItemToPathElement(list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
if child.Null {
// For now, the keys are required which means that null entries
@ -205,7 +205,7 @@ func keyedAssociativeListItemToPathElement(list schema.List, index int, child va
if child.MapValue == nil {
return pe, errors.New("associative list with keys may not have non-map elements")
}
keyMap := &value.Map{}
keyMap := value.FieldList{}
for _, fieldName := range list.Keys {
var fieldValue value.Value
field, ok := child.MapValue.Get(fieldName)
@ -215,13 +215,14 @@ func keyedAssociativeListItemToPathElement(list schema.List, index int, child va
// Treat keys as required.
return pe, fmt.Errorf("associative list with keys has an element that omits key field %q", fieldName)
}
keyMap.Set(fieldName, fieldValue)
keyMap = append(keyMap, value.Field{Name: fieldName, Value: fieldValue})
}
pe.Key = keyMap
keyMap.Sort()
pe.Key = &keyMap
return pe, nil
}
func setItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
func setItemToPathElement(list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
switch {
case child.MapValue != nil:
@ -241,7 +242,7 @@ func setItemToPathElement(list schema.List, index int, child value.Value) (field
}
}
func listItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
func listItemToPathElement(list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
if list.ElementRelationship == schema.Associative {
if len(list.Keys) > 0 {
return keyedAssociativeListItemToPathElement(list, index, child)

View File

@ -104,7 +104,7 @@ func (w *mergingWalker) doLeaf() {
w.rule(w)
}
func (w *mergingWalker) doScalar(t schema.Scalar) (errs ValidationErrors) {
func (w *mergingWalker) doScalar(t *schema.Scalar) (errs ValidationErrors) {
errs = append(errs, w.validateScalar(t, w.lhs, "lhs: ")...)
errs = append(errs, w.validateScalar(t, w.rhs, "rhs: ")...)
if len(errs) > 0 {
@ -158,7 +158,7 @@ func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest **value.Map
return nil
}
func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (errs ValidationErrors) {
func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs *value.List) (errs ValidationErrors) {
out := &value.List{}
// TODO: ordering is totally wrong.
@ -168,8 +168,9 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
rhsOrder := []fieldpath.PathElement{}
// First, collect all RHS children.
observedRHS := map[string]value.Value{}
var observedRHS fieldpath.PathElementValueMap
if rhs != nil {
observedRHS = fieldpath.MakePathElementValueMap(len(rhs.Items))
for i, child := range rhs.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
@ -179,18 +180,18 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
// this element.
continue
}
keyStr := pe.String()
if _, found := observedRHS[keyStr]; found {
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", keyStr)...)
if _, ok := observedRHS.Get(pe); ok {
errs = append(errs, w.errorf("rhs: duplicate entries for key %v", pe.String())...)
}
observedRHS[keyStr] = child
observedRHS.Insert(pe, child)
rhsOrder = append(rhsOrder, pe)
}
}
// Then merge with LHS children.
observedLHS := map[string]struct{}{}
var observedLHS fieldpath.PathElementSet
if lhs != nil {
observedLHS = fieldpath.MakePathElementSet(len(lhs.Items))
for i, child := range lhs.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
@ -200,15 +201,14 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
// this element.
continue
}
keyStr := pe.String()
if _, found := observedLHS[keyStr]; found {
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", keyStr)...)
if observedLHS.Has(pe) {
errs = append(errs, w.errorf("lhs: duplicate entries for key %v", pe.String())...)
continue
}
observedLHS[keyStr] = struct{}{}
observedLHS.Insert(pe)
w2 := w.prepareDescent(pe, t.ElementType)
w2.lhs = &child
if rchild, ok := observedRHS[keyStr]; ok {
if rchild, ok := observedRHS.Get(pe); ok {
w2.rhs = &rchild
}
if newErrs := w2.merge(); len(newErrs) > 0 {
@ -217,22 +217,22 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
// Keep track of children that have been handled
delete(observedRHS, keyStr)
}
}
for _, rhsToCheck := range rhsOrder {
if unmergedChild, ok := observedRHS[rhsToCheck.String()]; ok {
w2 := w.prepareDescent(rhsToCheck, t.ElementType)
w2.rhs = &unmergedChild
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
for _, pe := range rhsOrder {
if observedLHS.Has(pe) {
continue
}
value, _ := observedRHS.Get(pe)
w2 := w.prepareDescent(pe, t.ElementType)
w2.rhs = &value
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
}
if len(out.Items) > 0 {
@ -255,7 +255,7 @@ func (w *mergingWalker) derefList(prefix string, v *value.Value, dest **value.Li
return nil
}
func (w *mergingWalker) doList(t schema.List) (errs ValidationErrors) {
func (w *mergingWalker) doList(t *schema.List) (errs ValidationErrors) {
var lhs, rhs *value.List
w.derefList("lhs: ", w.lhs, &lhs)
w.derefList("rhs: ", w.rhs, &rhs)
@ -280,23 +280,15 @@ func (w *mergingWalker) doList(t schema.List) (errs ValidationErrors) {
return errs
}
func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs ValidationErrors) {
func (w *mergingWalker) visitMapItems(t *schema.Map, lhs, rhs *value.Map) (errs ValidationErrors) {
out := &value.Map{}
fieldTypes := map[string]schema.TypeRef{}
for i := range t.Fields {
// I don't want to use the loop variable since a reference
// might outlive the loop iteration (in an error message).
f := t.Fields[i]
fieldTypes[f.Name] = f.Type
}
if lhs != nil {
for i := range lhs.Items {
litem := &lhs.Items[i]
fieldType := t.ElementType
if ft, ok := fieldTypes[litem.Name]; ok {
fieldType = ft
if sf, ok := t.FindField(litem.Name); ok {
fieldType = sf.Type
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &litem.Name}, fieldType)
w2.lhs = &litem.Value
@ -324,8 +316,8 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
}
fieldType := t.ElementType
if ft, ok := fieldTypes[ritem.Name]; ok {
fieldType = ft
if sf, ok := t.FindField(ritem.Name); ok {
fieldType = sf.Type
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &ritem.Name}, fieldType)
w2.rhs = &ritem.Value
@ -344,7 +336,7 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
return errs
}
func (w *mergingWalker) doMap(t schema.Map) (errs ValidationErrors) {
func (w *mergingWalker) doMap(t *schema.Map) (errs ValidationErrors) {
var lhs, rhs *value.Map
w.derefMap("lhs: ", w.lhs, &lhs)
w.derefMap("rhs: ", w.rhs, &rhs)

View File

@ -38,9 +38,9 @@ func removeItemsWithSchema(value *value.Value, toRemove *fieldpath.Set, schema *
// will be a descent. It modifies w.inLeaf.
func (w *removingWalker) doLeaf() ValidationErrors { return nil }
func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil }
func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors { return nil }
func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
l := w.value.ListValue
// If list is null, empty, or atomic just return
@ -70,7 +70,7 @@ func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
return nil
}
func (w *removingWalker) doMap(t schema.Map) ValidationErrors {
func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
m := w.value.MapValue
// If map is null, empty, or atomic just return

View File

@ -18,6 +18,7 @@ package typed
import (
"fmt"
"strings"
"sync"
"sigs.k8s.io/structured-merge-diff/fieldpath"
@ -116,7 +117,7 @@ func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
Modified: fieldpath.NewSet(),
Added: fieldpath.NewSet(),
}
c.Merged, err = merge(&tv, rhs, func(w *mergingWalker) {
_, err = merge(&tv, rhs, func(w *mergingWalker) {
if w.lhs == nil {
c.Added.Insert(w.path)
} else if w.rhs == nil {
@ -126,8 +127,6 @@ func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
// Need to implement equality check on the value type.
c.Modified.Insert(w.path)
}
ruleKeepRHS(w)
}, func(w *mergingWalker) {
if w.lhs == nil {
c.Added.Insert(w.path)
@ -268,10 +267,6 @@ func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error)
// No field will appear in more than one of the three fieldsets. If all of the
// fieldsets are empty, then the objects must have been equal.
type Comparison struct {
// Merged is the result of merging the two objects, as explained in the
// comments on TypedValue.Merge().
Merged *TypedValue
// Removed contains any fields removed by rhs (the right-hand-side
// object in the comparison).
Removed *fieldpath.Set
@ -289,15 +284,15 @@ func (c *Comparison) IsSame() bool {
// String returns a human readable version of the comparison.
func (c *Comparison) String() string {
str := fmt.Sprintf("- Merged Object:\n%v\n", c.Merged.AsValue())
bld := strings.Builder{}
if !c.Modified.Empty() {
str += fmt.Sprintf("- Modified Fields:\n%v\n", c.Modified)
bld.WriteString(fmt.Sprintf("- Modified Fields:\n%v\n", c.Modified))
}
if !c.Added.Empty() {
str += fmt.Sprintf("- Added Fields:\n%v\n", c.Added)
bld.WriteString(fmt.Sprintf("- Added Fields:\n%v\n", c.Added))
}
if !c.Removed.Empty() {
str += fmt.Sprintf("- Removed Fields:\n%v\n", c.Removed)
bld.WriteString(fmt.Sprintf("- Removed Fields:\n%v\n", c.Removed))
}
return str
return bld.String()
}

View File

@ -130,7 +130,7 @@ func (v *validatingObjectWalker) doNode() {
}
}
func (v *validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
func (v *validatingObjectWalker) doScalar(t *schema.Scalar) ValidationErrors {
if errs := v.validateScalar(t, &v.value, ""); len(errs) > 0 {
return errs
}
@ -141,8 +141,8 @@ func (v *validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
return nil
}
func (v *validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
observedKeys := map[string]struct{}{}
func (v *validatingObjectWalker) visitListItems(t *schema.List, list *value.List) (errs ValidationErrors) {
observedKeys := fieldpath.MakePathElementSet(len(list.Items))
for i, child := range list.Items {
pe, err := listItemToPathElement(t, i, child)
if err != nil {
@ -152,11 +152,10 @@ func (v *validatingObjectWalker) visitListItems(t schema.List, list *value.List)
// this element.
continue
}
keyStr := pe.String()
if _, found := observedKeys[keyStr]; found {
errs = append(errs, v.errorf("duplicate entries for key %v", keyStr)...)
if observedKeys.Has(pe) {
errs = append(errs, v.errorf("duplicate entries for key %v", pe.String())...)
}
observedKeys[keyStr] = struct{}{}
observedKeys.Insert(pe)
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = child
errs = append(errs, v2.validate()...)
@ -167,7 +166,7 @@ func (v *validatingObjectWalker) visitListItems(t schema.List, list *value.List)
return errs
}
func (v *validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
func (v *validatingObjectWalker) doList(t *schema.List) (errs ValidationErrors) {
list, err := listValue(v.value)
if err != nil {
return v.error(err)
@ -186,21 +185,13 @@ func (v *validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
return errs
}
func (v *validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs ValidationErrors) {
fieldTypes := map[string]schema.TypeRef{}
for i := range t.Fields {
// I don't want to use the loop variable since a reference
// might outlive the loop iteration (in an error message).
f := t.Fields[i]
fieldTypes[f.Name] = f.Type
}
func (v *validatingObjectWalker) visitMapItems(t *schema.Map, m *value.Map) (errs ValidationErrors) {
for i := range m.Items {
item := &m.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
if tr, ok := fieldTypes[item.Name]; ok {
v2 := v.prepareDescent(pe, tr)
if sf, ok := t.FindField(item.Name); ok {
v2 := v.prepareDescent(pe, sf.Type)
v2.value = item.Value
errs = append(errs, v2.validate()...)
v.finishDescent(v2)
@ -215,7 +206,7 @@ func (v *validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs
return errs
}
func (v *validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
func (v *validatingObjectWalker) doMap(t *schema.Map) (errs ValidationErrors) {
m, err := mapValue(v.value)
if err != nil {
return v.error(err)

View File

@ -36,86 +36,146 @@ type Value struct {
// Equals returns true iff the two values are equal.
func (v Value) Equals(rhs Value) bool {
return !v.Less(rhs) && !rhs.Less(v)
if v.FloatValue != nil || rhs.FloatValue != nil {
var lf float64
if v.FloatValue != nil {
lf = float64(*v.FloatValue)
} else if v.IntValue != nil {
lf = float64(*v.IntValue)
} else {
return false
}
var rf float64
if rhs.FloatValue != nil {
rf = float64(*rhs.FloatValue)
} else if rhs.IntValue != nil {
rf = float64(*rhs.IntValue)
} else {
return false
}
return lf == rf
}
if v.IntValue != nil {
if rhs.IntValue != nil {
return *v.IntValue == *rhs.IntValue
}
return false
}
if v.StringValue != nil {
if rhs.StringValue != nil {
return *v.StringValue == *rhs.StringValue
}
return false
}
if v.BooleanValue != nil {
if rhs.BooleanValue != nil {
return *v.BooleanValue == *rhs.BooleanValue
}
return false
}
if v.ListValue != nil {
if rhs.ListValue != nil {
return v.ListValue.Equals(rhs.ListValue)
}
return false
}
if v.MapValue != nil {
if rhs.MapValue != nil {
return v.MapValue.Equals(rhs.MapValue)
}
return false
}
if v.Null {
if rhs.Null {
return true
}
return false
}
// No field is set, on either objects.
return true
}
// Less provides a total ordering for Value (so that they can be sorted, even
// if they are of different types).
func (v Value) Less(rhs Value) bool {
return v.Compare(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 (v Value) Compare(rhs Value) int {
if v.FloatValue != nil {
if rhs.FloatValue == nil {
// Extra: compare floats and ints numerically.
if rhs.IntValue != nil {
return float64(*v.FloatValue) < float64(*rhs.IntValue)
return v.FloatValue.Compare(Float(*rhs.IntValue))
}
return true
return -1
}
return *v.FloatValue < *rhs.FloatValue
return v.FloatValue.Compare(*rhs.FloatValue)
} else if rhs.FloatValue != nil {
// Extra: compare floats and ints numerically.
if v.IntValue != nil {
return float64(*v.IntValue) < float64(*rhs.FloatValue)
return Float(*v.IntValue).Compare(*rhs.FloatValue)
}
return false
return 1
}
if v.IntValue != nil {
if rhs.IntValue == nil {
return true
return -1
}
return *v.IntValue < *rhs.IntValue
return v.IntValue.Compare(*rhs.IntValue)
} else if rhs.IntValue != nil {
return false
return 1
}
if v.StringValue != nil {
if rhs.StringValue == nil {
return true
return -1
}
return *v.StringValue < *rhs.StringValue
return strings.Compare(string(*v.StringValue), string(*rhs.StringValue))
} else if rhs.StringValue != nil {
return false
return 1
}
if v.BooleanValue != nil {
if rhs.BooleanValue == nil {
return true
return -1
}
if *v.BooleanValue == *rhs.BooleanValue {
return false
}
return *v.BooleanValue == false
return v.BooleanValue.Compare(*rhs.BooleanValue)
} else if rhs.BooleanValue != nil {
return false
return 1
}
if v.ListValue != nil {
if rhs.ListValue == nil {
return true
return -1
}
return v.ListValue.Less(rhs.ListValue)
return v.ListValue.Compare(rhs.ListValue)
} else if rhs.ListValue != nil {
return false
return 1
}
if v.MapValue != nil {
if rhs.MapValue == nil {
return true
return -1
}
return v.MapValue.Less(rhs.MapValue)
return v.MapValue.Compare(rhs.MapValue)
} else if rhs.MapValue != nil {
return false
return 1
}
if v.Null {
if !rhs.Null {
return true
return -1
}
return false
return 0
} else if rhs.Null {
return false
return 1
}
// Invalid Value-- nothing is set.
return false
return 0
}
type Int int64
@ -123,40 +183,141 @@ type Float float64
type String string
type Boolean bool
// Compare compares integers. The result will be 0 if i==rhs, -1 if i <
// rhs, and +1 if i > rhs.
func (i Int) Compare(rhs Int) int {
if i > rhs {
return 1
} else if i < rhs {
return -1
}
return 0
}
// Compare compares floats. The result will be 0 if f==rhs, -1 if f <
// rhs, and +1 if f > rhs.
func (f Float) Compare(rhs Float) int {
if f > rhs {
return 1
} else if f < 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 (b Boolean) Compare(rhs Boolean) int {
if b == rhs {
return 0
} else if b == false {
return -1
}
return 1
}
// 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 expected 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
}
// Less 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 := f[i].Value.Compare(rhs[i].Value); c != 0 {
return c
}
// The items are equal; continue.
i++
}
}
// List is a list of items.
type List struct {
Items []Value
}
// Equals compares two lists lexically.
func (l *List) Equals(rhs *List) bool {
if len(l.Items) != len(rhs.Items) {
return false
}
for i, lv := range l.Items {
if !lv.Equals(rhs.Items[i]) {
return false
}
}
return true
}
// Less compares two lists lexically.
func (l *List) Less(rhs *List) bool {
return l.Compare(rhs) == -1
}
// Compare compares two lists lexically. The result will be 0 if l==rhs, -1
// if l < rhs, and +1 if l > rhs.
func (l *List) Compare(rhs *List) int {
i := 0
for {
if i >= len(l.Items) && i >= len(rhs.Items) {
// Lists are the same length and all items are equal.
return false
return 0
}
if i >= len(l.Items) {
// LHS is shorter.
return true
return -1
}
if i >= len(rhs.Items) {
// RHS is shorter.
return false
return 1
}
if l.Items[i].Less(rhs.Items[i]) {
// LHS is less; return
return true
}
if rhs.Items[i].Less(l.Items[i]) {
// RHS is less; return
return false
if c := l.Items[i].Compare(rhs.Items[i]); c != 0 {
return c
}
// The items are equal; continue.
i++
@ -191,8 +352,30 @@ func (m *Map) computeOrder() []int {
return m.order
}
// Equals compares two maps lexically.
func (m *Map) Equals(rhs *Map) bool {
if len(m.Items) != len(rhs.Items) {
return false
}
for _, lfield := range m.Items {
rfield, ok := rhs.Get(lfield.Name)
if !ok {
return false
}
if !lfield.Value.Equals(rfield.Value) {
return false
}
}
return true
}
// Less compares two maps lexically.
func (m *Map) Less(rhs *Map) bool {
return m.Compare(rhs) == -1
}
// Compare compares two maps lexically.
func (m *Map) Compare(rhs *Map) int {
var noAllocL, noAllocR [2]int
var morder, rorder []int
@ -238,28 +421,22 @@ func (m *Map) Less(rhs *Map) bool {
for {
if i >= len(morder) && i >= len(rorder) {
// Maps are the same length and all items are equal.
return false
return 0
}
if i >= len(morder) {
// LHS is shorter.
return true
return -1
}
if i >= len(rorder) {
// RHS is shorter.
return false
return 1
}
fa, fb := &m.Items[morder[i]], &rhs.Items[rorder[i]]
if fa.Name != fb.Name {
// the map having the field name that sorts lexically less is "less"
return fa.Name < fb.Name
if c := strings.Compare(fa.Name, fb.Name); c != 0 {
return c
}
if fa.Value.Less(fb.Value) {
// LHS is less; return
return true
}
if fb.Value.Less(fa.Value) {
// RHS is less; return
return false
if c := fa.Value.Compare(fb.Value); c != 0 {
return c
}
// The items are equal; continue.
i++