Run update-vendor.sh

This commit is contained in:
Dr. Stefan Schimanski
2019-07-13 10:07:03 +02:00
parent 7408ebfdca
commit 91a3704938
63 changed files with 1907 additions and 793 deletions

View File

@@ -23,7 +23,7 @@ import (
// Converter is an interface to the conversion logic. The converter
// needs to be able to convert objects from one version to another.
type Converter interface {
Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error)
Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error)
IsMissingVersionError(error) bool
}
@@ -33,15 +33,15 @@ type Updater struct {
Converter Converter
}
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, error) {
conflicts := fieldpath.ManagedFields{}
removed := fieldpath.ManagedFields{}
type Versioned struct {
oldObject typed.TypedValue
newObject typed.TypedValue
oldObject *typed.TypedValue
newObject *typed.TypedValue
}
versions := map[fieldpath.APIVersion]Versioned{
version: Versioned{
version: {
oldObject: oldObject,
newObject: newObject,
},
@@ -119,16 +119,19 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat
// that you intend to persist (after applying the patch if this is for a
// PATCH call), and liveObject must be the original object (empty if
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
var err error
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
newObject, err := liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
managers = shallowCopyManagers(managers)
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return fieldpath.ManagedFields{}, err
return nil, fieldpath.ManagedFields{}, err
}
compare, err := liveObject.Compare(newObject)
if err != nil {
return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err)
}
if _, ok := managers[manager]; !ok {
managers[manager] = &fieldpath.VersionedSet{
@@ -140,18 +143,26 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa
if managers[manager].Set.Empty() {
delete(managers, manager)
}
return managers, nil
return newObject, managers, nil
}
// Apply should be called when Apply is run, given the current object as
// well as the configuration that is applied. This will merge the object
// and return it.
func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) {
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
managers = shallowCopyManagers(managers)
configObject, err := configObject.NormalizeUnionsApply(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
newObject, err := liveObject.Merge(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
}
newObject, err = configObject.NormalizeUnionsApply(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
lastSet := managers[manager]
set, err := configObject.ToFieldSet()
if err != nil {
@@ -185,7 +196,7 @@ func shallowCopyManagers(managers fieldpath.ManagedFields) fieldpath.ManagedFiel
// * applyingManager applied it last time
// * applyingManager didn't apply it this time
// * no other applier claims to manage it
func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet *fieldpath.VersionedSet) (*typed.TypedValue, error) {
if lastSet == nil || lastSet.Set.Empty() {
return merged, nil
}
@@ -210,7 +221,7 @@ func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedField
// addBackOwnedItems adds back any list and map items that were removed by prune,
// but other appliers (or the current applier's new config) claim to own.
func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (typed.TypedValue, error) {
func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
var err error
managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
for _, managerSet := range managedFields {
@@ -249,11 +260,10 @@ func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFiel
return pruned, nil
}
// addBackDanglingItems makes sure that the only items removed by prune are items that were
// previously owned by the currently applying manager. This will add back unowned items and items
// which are owned by Updaters that shouldn't be removed.
func (s *Updater) addBackDanglingItems(merged, pruned typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet *fieldpath.VersionedSet) (*typed.TypedValue, error) {
convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion)
if err != nil {
if s.Converter.IsMissingVersionError(err) {

View File

@@ -5,13 +5,11 @@ go_library(
srcs = [
"doc.go",
"elements.go",
"fromvalue.go",
"schemaschema.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema",
importpath = "sigs.k8s.io/structured-merge-diff/schema",
visibility = ["//visibility:public"],
deps = ["//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library"],
)
filegroup(

View File

@@ -43,13 +43,12 @@ type TypeRef struct {
}
// Atom represents the smallest possible pieces of the type system.
// Each set field in the Atom represents a possible type for the object.
// If none of the fields are set, any object will fail validation against the atom.
type Atom struct {
// Exactly one of the below must be set.
*Scalar `yaml:"scalar,omitempty"`
*Struct `yaml:"struct,omitempty"`
*List `yaml:"list,omitempty"`
*Map `yaml:"map,omitempty"`
*Untyped `yaml:"untyped,omitempty"`
*Scalar `yaml:"scalar,omitempty"`
*List `yaml:"list,omitempty"`
*Map `yaml:"map,omitempty"`
}
// Scalar (AKA "primitive") represents a type which has a single value which is
@@ -65,47 +64,105 @@ const (
)
// ElementRelationship is an enum of the different possible relationships
// between the elements of container types (maps, lists, structs, untyped).
// between the elements of container types (maps, lists).
type ElementRelationship string
const (
// Associative only applies to lists (see the documentation there).
Associative = ElementRelationship("associative")
// Atomic makes container types (lists, maps, structs, untyped) behave
// as scalars / leaf fields (which is the default for untyped data).
// Atomic makes container types (lists, maps) behave
// as scalars / leaf fields
Atomic = ElementRelationship("atomic")
// Separable means the items of the container type have no particular
// relationship (default behavior for maps and structs).
// relationship (default behavior for maps).
Separable = ElementRelationship("separable")
)
// Struct represents a type which is composed of a number of different fields.
// Each field has a name and a type.
// Map is a key-value pair. Its default semantics are the same as an
// associative list, but:
// * It is serialized differently:
// map: {"k": {"value": "v"}}
// list: [{"key": "k", "value": "v"}]
// * Keys must be string typed.
// * Keys can't have multiple components.
//
// TODO: in the future, we will add one-of groups (sometimes called unions).
type Struct struct {
// Optionally, maps may be atomic (for example, imagine representing an RGB
// color value--it doesn't make sense to have different actors own the R and G
// values).
//
// Maps may also represent a type which is composed of a number of different fields.
// Each field has a name and a type.
type Map struct {
// Each struct field appears exactly once in this list. The order in
// this list defines the canonical field ordering.
Fields []StructField `yaml:"fields,omitempty"`
// TODO: Implement unions, either this way or by inlining.
// Unions are groupings of fields with special rules. They may refer to
// A Union is a grouping of fields with special rules. It may refer to
// one or more fields in the above list. A given field from the above
// list may be referenced in exactly 0 or 1 places in the below list.
// Unions []Union `yaml:"unions,omitempty"`
// One can have multiple unions in the same struct, but the fields can't
// overlap between unions.
Unions []Union `yaml:"unions,omitempty"`
// ElementRelationship states the relationship between the struct's items.
// ElementType is the type of the structs's unknown fields.
ElementType TypeRef `yaml:"elementType,omitempty"`
// ElementRelationship states the relationship between the map's items.
// * `separable` (or unset) implies that each element is 100% independent.
// * `atomic` implies that all elements depend on each other, and this
// is effectively a scalar / leaf field; it doesn't make sense for
// separate actors to set the elements. Example: an RGB color struct;
// it would never make sense to "own" only one component of the
// color.
// The default behavior for structs is `separable`; it's permitted to
// The default behavior for maps is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
}
// UnionFields are mapping between the fields that are part of the union and
// their discriminated value. The discriminated value has to be set, and
// should not conflict with other discriminated value in the list.
type UnionField struct {
// FieldName is the name of the field that is part of the union. This
// is the serialized form of the field.
FieldName string `yaml:"fieldName"`
// Discriminatorvalue is the value of the discriminator to
// select that field. If the union doesn't have a discriminator,
// this field is ignored.
DiscriminatorValue string `yaml:"discriminatorValue"`
}
// Union, or oneof, means that only one of multiple fields of a structure can be
// set at a time. Setting the discriminator helps clearing oher fields:
// - If discriminator changed to non-nil, and a new field has been added
// that doesn't match, an error is returned,
// - If discriminator hasn't changed and two fields or more are set, an
// error is returned,
// - If discriminator changed to non-nil, all other fields but the
// discriminated one will be cleared,
// - Otherwise, If only one field is left, update discriminator to that value.
type Union struct {
// Discriminator, if present, is the name of the field that
// discriminates fields in the union. The mapping between the value of
// the discriminator and the field is done by using the Fields list
// below.
Discriminator *string `yaml:"discriminator,omitempty"`
// DeduceInvalidDiscriminator indicates if the discriminator
// should be updated automatically based on the fields set. This
// typically defaults to false since we don't want to deduce by
// default (the behavior exists to maintain compatibility on
// existing types and shouldn't be used for new types).
DeduceInvalidDiscriminator bool `yaml:"deduceInvalidDiscriminator,omitempty"`
// This is the list of fields that belong to this union. All the
// fields present in here have to be part of the parent
// structure. Discriminator (if oneOf has one), is NOT included in
// this list. The value for field is how we map the name of the field
// to actual value for discriminator.
Fields []UnionField `yaml:"fields,omitempty"`
}
// StructField pairs a field name with a field type.
type StructField struct {
// Name is the field name.
@@ -129,15 +186,14 @@ type List struct {
// * `atomic`: the list is treated as a single entity, like a scalar.
// * `associative`:
// - If the list element is a scalar, the list is treated as a set.
// - If the list element is a struct, the list is treated as a map.
// - The list element must not be a map or a list itself.
// - If the list element is a map, the list is treated as a map.
// There is no default for this value for lists; all schemas must
// explicitly state the element relationship for all lists.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
// Iff ElementRelationship is `associative`, and the element type is
// struct, then Keys must have non-zero length, and it lists the fields
// of the element's struct type which are to be used as the keys of the
// map, then Keys must have non-zero length, and it lists the fields
// of the element's map type which are to be used as the keys of the
// list.
//
// TODO: change this to "non-atomic struct" above and make the code reflect this.
@@ -146,51 +202,6 @@ type List struct {
Keys []string `yaml:"keys,omitempty"`
}
// Map is a key-value pair. Its default semantics are the same as an
// associative list, but:
// * It is serialized differently:
// map: {"k": {"value": "v"}}
// list: [{"key": "k", "value": "v"}]
// * Keys must be string typed.
// * Keys can't have multiple components.
//
// Although serialized the same, maps are different from structs in that each
// map item must have the same type.
//
// Optionally, maps may be atomic (for example, imagine representing an RGB
// color value--it doesn't make sense to have different actors own the R and G
// values).
type Map struct {
// ElementType is the type of the list's elements.
ElementType TypeRef `yaml:"elementType,omitempty"`
// ElementRelationship states the relationship between the map's items.
// * `separable` implies that each element is 100% independent.
// * `atomic` implies that all elements depend on each other, and this
// is effectively a scalar / leaf field; it doesn't make sense for
// separate actors to set the elements.
// TODO: find a simple example.
// The default behavior for maps is `separable`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
}
// Untyped represents types that allow arbitrary content. (Think: plugin
// objects.)
type Untyped struct {
// ElementRelationship states the relationship between the items, if
// container-typed data happens to be present here.
// * `atomic` implies that all elements depend on each other, and this
// is effectively a scalar / leaf field; it doesn't make sense for
// separate actors to set the elements.
// TODO: support "guess" (guesses at associative list keys)
// TODO: support "lookup" (calls a lookup function to figure out the
// schema based on the data)
// The default behavior for untyped data is `atomic`; it's permitted to
// leave this unset to get the default behavior.
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
}
// 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) {

View File

@@ -1,57 +0,0 @@
/*
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 schema
import (
"sigs.k8s.io/structured-merge-diff/value"
)
// TypeRefFromValue creates an inlined type from a value v
func TypeRefFromValue(v value.Value) TypeRef {
atom := atomFor(v)
return TypeRef{
Inlined: atom,
}
}
func atomFor(v value.Value) Atom {
switch {
// Untyped cases (handled at the bottom of this function)
case v.Null:
case v.ListValue != nil:
case v.FloatValue != nil:
case v.IntValue != nil:
case v.StringValue != nil:
case v.BooleanValue != nil:
// Recursive case
case v.MapValue != nil:
s := Struct{}
for i := range v.MapValue.Items {
child := v.MapValue.Items[i]
field := StructField{
Name: child.Name,
Type: TypeRef{
Inlined: atomFor(child.Value),
},
}
s.Fields = append(s.Fields, field)
}
return Atom{Struct: &s}
}
return Atom{Untyped: &Untyped{}}
}

View File

@@ -20,7 +20,7 @@ package schema
// It will validate itself. It can be unmarshalled into a Schema type.
var SchemaSchemaYAML = `types:
- name: schema
struct:
map:
fields:
- name: types
type:
@@ -31,7 +31,7 @@ var SchemaSchemaYAML = `types:
keys:
- name
- name: typeDef
struct:
map:
fields:
- name: name
type:
@@ -39,20 +39,17 @@ var SchemaSchemaYAML = `types:
- name: scalar
type:
scalar: string
- name: struct
type:
namedType: struct
- name: list
type:
namedType: list
- name: map
type:
namedType: map
- name: list
type:
namedType: list
- name: untyped
type:
namedType: untyped
- name: typeRef
struct:
map:
fields:
- name: namedType
type:
@@ -60,22 +57,19 @@ var SchemaSchemaYAML = `types:
- name: scalar
type:
scalar: string
- name: struct
type:
namedType: struct
- name: list
type:
namedType: list
- name: map
type:
namedType: map
- name: list
type:
namedType: list
- name: untyped
type:
namedType: untyped
- name: scalar
scalar: string
- name: struct
struct:
- name: map
map:
fields:
- name: fields
type:
@@ -84,11 +78,46 @@ var SchemaSchemaYAML = `types:
namedType: structField
elementRelationship: associative
keys: [ "name" ]
- name: unions
type:
list:
elementType:
namedType: union
elementRelationship: atomic
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: unionField
map:
fields:
- name: fieldName
type:
scalar: string
- name: discriminatorValue
type:
scalar: string
- name: union
map:
fields:
- name: discriminator
type:
scalar: string
- name: deduceInvalidDiscriminator
type:
scalar: bool
- name: fields
type:
list:
elementRelationship: associative
elementType:
namedType: unionField
keys:
- fieldName
- name: structField
struct:
map:
fields:
- name: name
type:
@@ -97,7 +126,7 @@ var SchemaSchemaYAML = `types:
type:
namedType: typeRef
- name: list
struct:
map:
fields:
- name: elementType
type:
@@ -110,17 +139,8 @@ var SchemaSchemaYAML = `types:
list:
elementType:
scalar: string
- name: map
struct:
fields:
- name: elementType
type:
namedType: typeRef
- name: elementRelationship
type:
scalar: string
- name: untyped
struct:
map:
fields:
- name: elementRelationship
type:

View File

@@ -3,13 +3,13 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"deduced.go",
"doc.go",
"helpers.go",
"merge.go",
"parser.go",
"remove.go",
"typed.go",
"union.go",
"validate.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/typed",

View File

@@ -1,178 +0,0 @@
/*
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 typed
import (
"reflect"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/value"
)
// deducedTypedValue holds a value and guesses what it is and what to
// do with it.
type deducedTypedValue struct {
value value.Value
}
// AsTypedDeduced is going to generate it's own type definition based on
// the content of the object. This is useful for CRDs that don't have a
// validation field.
func AsTypedDeduced(v value.Value) TypedValue {
return deducedTypedValue{value: v}
}
func (dv deducedTypedValue) AsValue() *value.Value {
return &dv.value
}
func (deducedTypedValue) Validate() error {
return nil
}
func (dv deducedTypedValue) ToFieldSet() (*fieldpath.Set, error) {
set := fieldpath.NewSet()
fieldsetDeduced(dv.value, fieldpath.Path{}, set)
return set, nil
}
func fieldsetDeduced(v value.Value, path fieldpath.Path, set *fieldpath.Set) {
if v.MapValue == nil {
set.Insert(path)
return
}
// We have a map.
// copy the existing path, append each item, and recursively call.
for i := range v.MapValue.Items {
child := v.MapValue.Items[i]
np := path.Copy()
np = append(np, fieldpath.PathElement{FieldName: &child.Name})
fieldsetDeduced(child.Value, np, set)
}
}
func (dv deducedTypedValue) Merge(pso TypedValue) (TypedValue, error) {
tpso, ok := pso.(deducedTypedValue)
if !ok {
return nil, errorFormatter{}.
errorf("can't merge deducedTypedValue with %T", tpso)
}
return AsTypedDeduced(mergeDeduced(dv.value, tpso.value)), nil
}
func mergeDeduced(lhs, rhs value.Value) value.Value {
// If both sides are maps, merge them, otherwise return right
// side.
if rhs.MapValue == nil || lhs.MapValue == nil {
return rhs
}
v := value.Value{MapValue: &value.Map{}}
for i := range lhs.MapValue.Items {
child := lhs.MapValue.Items[i]
v.MapValue.Set(child.Name, child.Value)
}
for i := range rhs.MapValue.Items {
child := rhs.MapValue.Items[i]
if sub, ok := v.MapValue.Get(child.Name); ok {
new := mergeDeduced(sub.Value, child.Value)
v.MapValue.Set(child.Name, new)
} else {
v.MapValue.Set(child.Name, child.Value)
}
}
return v
}
func (dv deducedTypedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
trhs, ok := rhs.(deducedTypedValue)
if !ok {
return nil, errorFormatter{}.
errorf("can't merge deducedTypedValue with %T", rhs)
}
c = &Comparison{
Removed: fieldpath.NewSet(),
Modified: fieldpath.NewSet(),
Added: fieldpath.NewSet(),
}
added(dv.value, trhs.value, fieldpath.Path{}, c.Added)
added(trhs.value, dv.value, fieldpath.Path{}, c.Removed)
modified(dv.value, trhs.value, fieldpath.Path{}, c.Modified)
merge, err := dv.Merge(rhs)
if err != nil {
return nil, err
}
c.Merged = merge
return c, nil
}
func added(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) {
if lhs.MapValue == nil && rhs.MapValue == nil {
// Both non-maps, nothing added, do nothing.
} else if lhs.MapValue == nil && rhs.MapValue != nil {
// From leaf to map, add leaf fields of map.
fieldsetDeduced(rhs, path, set)
} else if lhs.MapValue != nil && rhs.MapValue == nil {
// Went from map to field, add field.
set.Insert(path)
} else {
// Both are maps.
for i := range rhs.MapValue.Items {
child := rhs.MapValue.Items[i]
np := path.Copy()
np = append(np, fieldpath.PathElement{FieldName: &child.Name})
if v, ok := lhs.MapValue.Get(child.Name); ok {
added(v.Value, child.Value, np, set)
} else {
fieldsetDeduced(child.Value, np, set)
}
}
}
}
func modified(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) {
if lhs.MapValue == nil && rhs.MapValue == nil {
if !reflect.DeepEqual(lhs, rhs) {
set.Insert(path)
}
} else if lhs.MapValue != nil && rhs.MapValue != nil {
// Both are maps.
for i := range rhs.MapValue.Items {
child := rhs.MapValue.Items[i]
v, ok := lhs.MapValue.Get(child.Name)
if !ok {
continue
}
np := path.Copy()
np = append(np, fieldpath.PathElement{FieldName: &child.Name})
modified(v.Value, child.Value, np, set)
}
}
}
// RemoveItems does nothing because all lists in a deducedTypedValue are considered atomic,
// and there are no maps because it is indistinguishable from a struct.
func (dv deducedTypedValue) RemoveItems(_ *fieldpath.Set) TypedValue {
return dv
}

View File

@@ -90,31 +90,43 @@ func (ef errorFormatter) prefixError(prefix string, err error) ValidationErrors
type atomHandler interface {
doScalar(schema.Scalar) ValidationErrors
doStruct(schema.Struct) ValidationErrors
doList(schema.List) ValidationErrors
doMap(schema.Map) ValidationErrors
doUntyped(schema.Untyped) ValidationErrors
errorf(msg string, args ...interface{}) ValidationErrors
}
func resolveSchema(s *schema.Schema, tr schema.TypeRef, ah atomHandler) ValidationErrors {
func resolveSchema(s *schema.Schema, tr schema.TypeRef, v *value.Value, ah atomHandler) ValidationErrors {
a, ok := s.Resolve(tr)
if !ok {
return ah.errorf("schema error: no type found matching: %v", *tr.NamedType)
}
a = deduceAtom(a, v)
return handleAtom(a, tr, ah)
}
func deduceAtom(a schema.Atom, v *value.Value) schema.Atom {
switch {
case v == nil:
case v.FloatValue != nil, v.IntValue != nil, v.StringValue != nil, v.BooleanValue != nil:
return schema.Atom{Scalar: a.Scalar}
case v.ListValue != nil:
return schema.Atom{List: a.List}
case v.MapValue != nil:
return schema.Atom{Map: a.Map}
}
return a
}
func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErrors {
switch {
case a.Scalar != nil:
return ah.doScalar(*a.Scalar)
case a.Struct != nil:
return ah.doStruct(*a.Struct)
case a.List != nil:
return ah.doList(*a.List)
case a.Map != nil:
return ah.doMap(*a.Map)
case a.Untyped != nil:
return ah.doUntyped(*a.Untyped)
case a.Scalar != nil:
return ah.doScalar(*a.Scalar)
case a.List != nil:
return ah.doList(*a.List)
}
name := "inlined"
@@ -164,29 +176,17 @@ func listValue(val value.Value) (*value.List, error) {
}
// Returns the map, or an error. Reminder: nil is a valid map and might be returned.
func mapOrStructValue(val value.Value, typeName string) (*value.Map, error) {
func mapValue(val value.Value) (*value.Map, error) {
switch {
case val.Null:
return nil, nil
case val.MapValue != nil:
return val.MapValue, nil
default:
return nil, fmt.Errorf("expected %v, got %v", typeName, val)
return nil, fmt.Errorf("expected map, got %v", val)
}
}
func (ef errorFormatter) rejectExtraStructFields(m *value.Map, allowedNames map[string]struct{}, prefix string) (errs ValidationErrors) {
if m == nil {
return nil
}
for _, f := range m.Items {
if _, allowed := allowedNames[f.Name]; !allowed {
errs = append(errs, ef.errorf("%vfield %v is not mentioned in the schema", prefix, f.Name)...)
}
}
return errs
}
func keyedAssociativeListItemToPathElement(list schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
if child.Null {

View File

@@ -17,6 +17,8 @@ limitations under the License.
package typed
import (
"reflect"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
@@ -61,12 +63,26 @@ var (
)
// merge sets w.out.
func (w *mergingWalker) merge() ValidationErrors {
func (w *mergingWalker) merge() (errs ValidationErrors) {
if w.lhs == nil && w.rhs == nil {
// check this condidition here instead of everywhere below.
return w.errorf("at least one of lhs and rhs must be provided")
}
errs := resolveSchema(w.schema, w.typeRef, w)
a, ok := w.schema.Resolve(w.typeRef)
if !ok {
return w.errorf("schema error: no type found matching: %v", *w.typeRef.NamedType)
}
alhs := deduceAtom(a, w.lhs)
arhs := deduceAtom(a, w.rhs)
if reflect.DeepEqual(alhs, arhs) {
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
} else {
w2 := *w
errs = append(errs, handleAtom(alhs, w.typeRef, &w2)...)
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
}
if !w.inLeaf && w.postItemHook != nil {
w.postItemHook(w)
}
@@ -110,61 +126,13 @@ func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeR
return &w2
}
func (w *mergingWalker) visitStructFields(t schema.Struct, lhs, rhs *value.Map) (errs ValidationErrors) {
out := &value.Map{}
valOrNil := func(m *value.Map, name string) *value.Value {
if m == nil {
return nil
}
val, ok := m.Get(name)
if ok {
return &val.Value
}
return nil
}
allowedNames := map[string]struct{}{}
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]
allowedNames[f.Name] = struct{}{}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &f.Name}, f.Type)
w2.lhs = valOrNil(lhs, f.Name)
w2.rhs = valOrNil(rhs, f.Name)
if w2.lhs == nil && w2.rhs == nil {
// All fields are optional
continue
}
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Set(f.Name, *w2.out)
}
}
// All fields may be optional, but unknown fields are not allowed.
errs = append(errs, w.rejectExtraStructFields(lhs, allowedNames, "lhs: ")...)
errs = append(errs, w.rejectExtraStructFields(rhs, allowedNames, "rhs: ")...)
if len(errs) > 0 {
return errs
}
if len(out.Items) > 0 {
w.out = &value.Value{MapValue: out}
}
return errs
}
func (w *mergingWalker) derefMapOrStruct(prefix, typeName string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
// taking dest as input so that it can be called as a one-liner with
// append.
if v == nil {
return nil
}
m, err := mapOrStructValue(*v, typeName)
m, err := mapValue(*v)
if err != nil {
return w.prefixError(prefix, err)
}
@@ -172,37 +140,6 @@ func (w *mergingWalker) derefMapOrStruct(prefix, typeName string, v *value.Value
return nil
}
func (w *mergingWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
var lhs, rhs *value.Map
errs = append(errs, w.derefMapOrStruct("lhs: ", "struct", w.lhs, &lhs)...)
errs = append(errs, w.derefMapOrStruct("rhs: ", "struct", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
// If both lhs and rhs are empty/null, treat it as a
// leaf: this helps preserve the empty/null
// distinction.
emptyPromoteToLeaf := (lhs == nil || len(lhs.Items) == 0) &&
(rhs == nil || len(rhs.Items) == 0)
if t.ElementRelationship == schema.Atomic || emptyPromoteToLeaf {
w.doLeaf()
return nil
}
if lhs == nil && rhs == nil {
// nil is a valid map!
return nil
}
errs = w.visitStructFields(t, lhs, rhs)
// TODO: Check unions.
return errs
}
func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (errs ValidationErrors) {
out := &value.List{}
@@ -300,11 +237,8 @@ func (w *mergingWalker) derefList(prefix string, v *value.Value, dest **value.Li
func (w *mergingWalker) doList(t schema.List) (errs ValidationErrors) {
var lhs, rhs *value.List
errs = append(errs, w.derefList("lhs: ", w.lhs, &lhs)...)
errs = append(errs, w.derefList("rhs: ", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
w.derefList("lhs: ", w.lhs, &lhs)
w.derefList("rhs: ", w.rhs, &rhs)
// If both lhs and rhs are empty/null, treat it as a
// leaf: this helps preserve the empty/null
@@ -329,10 +263,22 @@ func (w *mergingWalker) doList(t schema.List) (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 _, litem := range lhs.Items {
name := litem.Name
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
fieldType := t.ElementType
if ft, ok := fieldTypes[name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
w2.lhs = &litem.Value
if rhs != nil {
if ritem, ok := rhs.Get(litem.Name); ok {
@@ -356,7 +302,11 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
}
name := ritem.Name
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, t.ElementType)
fieldType := t.ElementType
if ft, ok := fieldTypes[name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
w2.rhs = &ritem.Value
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
@@ -374,11 +324,8 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
func (w *mergingWalker) doMap(t schema.Map) (errs ValidationErrors) {
var lhs, rhs *value.Map
errs = append(errs, w.derefMapOrStruct("lhs: ", "map", w.lhs, &lhs)...)
errs = append(errs, w.derefMapOrStruct("rhs: ", "map", w.rhs, &rhs)...)
if len(errs) > 0 {
return errs
}
w.derefMap("lhs: ", w.lhs, &lhs)
w.derefMap("rhs: ", w.rhs, &rhs)
// If both lhs and rhs are empty/null, treat it as a
// leaf: this helps preserve the empty/null
@@ -395,16 +342,7 @@ func (w *mergingWalker) doMap(t schema.Map) (errs ValidationErrors) {
return nil
}
errs = w.visitMapItems(t, lhs, rhs)
errs = append(errs, w.visitMapItems(t, lhs, rhs)...)
return errs
}
func (w *mergingWalker) doUntyped(t schema.Untyped) (errs ValidationErrors) {
if t.ElementRelationship == "" || t.ElementRelationship == schema.Atomic {
// Untyped sections allow anything, and are considered leaf
// fields.
w.doLeaf()
}
return nil
}

View File

@@ -33,9 +33,9 @@ type Parser struct {
}
// create builds an unvalidated parser.
func create(schema YAMLObject) (*Parser, error) {
func create(s YAMLObject) (*Parser, error) {
p := Parser{}
err := yaml.Unmarshal([]byte(schema), &p.Schema)
err := yaml.Unmarshal([]byte(s), &p.Schema)
return &p, err
}
@@ -55,7 +55,11 @@ func NewParser(schema YAMLObject) (*Parser, error) {
if err != nil {
return nil, fmt.Errorf("unable to validate schema: %v", err)
}
return create(schema)
p, err := create(schema)
if err != nil {
return nil, err
}
return p, nil
}
// TypeNames returns a list of types this parser understands.
@@ -69,79 +73,65 @@ func (p *Parser) TypeNames() (names []string) {
// Type returns a helper which can produce objects of the given type. Any
// errors are deferred until a further function is called.
func (p *Parser) Type(name string) ParseableType {
return &parseableType{
parser: p,
typename: name,
return ParseableType{
Schema: &p.Schema,
TypeRef: schema.TypeRef{NamedType: &name},
}
}
// ParseableType allows for easy production of typed objects.
type ParseableType interface {
IsValid() bool
FromYAML(YAMLObject) (TypedValue, error)
FromUnstructured(interface{}) (TypedValue, error)
type ParseableType struct {
TypeRef schema.TypeRef
Schema *schema.Schema
}
type parseableType struct {
parser *Parser
typename string
}
var _ ParseableType = &parseableType{}
// IsValid return true if p's schema and typename are valid.
func (p *parseableType) IsValid() bool {
_, ok := p.parser.Schema.Resolve(schema.TypeRef{NamedType: &p.typename})
func (p ParseableType) IsValid() bool {
_, ok := p.Schema.Resolve(p.TypeRef)
return ok
}
// FromYAML parses a yaml string into an object with the current schema
// and the type "typename" or an error if validation fails.
func (p *parseableType) FromYAML(object YAMLObject) (TypedValue, error) {
func (p ParseableType) FromYAML(object YAMLObject) (*TypedValue, error) {
v, err := value.FromYAML([]byte(object))
if err != nil {
return nil, err
}
return AsTyped(v, &p.parser.Schema, p.typename)
return AsTyped(v, p.Schema, p.TypeRef)
}
// FromUnstructured converts a go interface to a TypedValue. It will return an
// error if the resulting object fails schema validation.
func (p *parseableType) FromUnstructured(in interface{}) (TypedValue, error) {
func (p ParseableType) FromUnstructured(in interface{}) (*TypedValue, error) {
v, err := value.FromUnstructured(in)
if err != nil {
return nil, err
}
return AsTyped(v, &p.parser.Schema, p.typename)
return AsTyped(v, p.Schema, p.TypeRef)
}
// DeducedParseableType is a ParseableType that deduces the type from
// the content of the object.
type DeducedParseableType struct{}
var _ ParseableType = DeducedParseableType{}
// IsValid always returns true for a DeducedParseableType.
func (p DeducedParseableType) IsValid() bool {
return true
}
// FromYAML parses a yaml string into an object and deduces the type for
// that object.
func (p DeducedParseableType) FromYAML(object YAMLObject) (TypedValue, error) {
v, err := value.FromYAML([]byte(object))
if err != nil {
return nil, err
}
return AsTypedDeduced(v), nil
}
// FromUnstructured converts a go interface to a TypedValue. It will return an
// error if the input object uses un-handled types.
func (p DeducedParseableType) FromUnstructured(in interface{}) (TypedValue, error) {
v, err := value.FromUnstructured(in)
if err != nil {
return nil, err
}
return AsTypedDeduced(v), nil
}
var DeducedParseableType ParseableType = createOrDie(YAMLObject(`types:
- name: __untyped_atomic_
scalar: untyped
list:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
map:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
- name: __untyped_deduced_
scalar: untyped
list:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
map:
elementType:
namedType: __untyped_deduced_
elementRelationship: separable
`)).Type("__untyped_deduced_")

View File

@@ -31,7 +31,7 @@ func removeItemsWithSchema(value *value.Value, toRemove *fieldpath.Set, schema *
schema: schema,
toRemove: toRemove,
}
resolveSchema(schema, typeRef, w)
resolveSchema(schema, typeRef, value, w)
}
// doLeaf should be called on leaves before descending into children, if there
@@ -40,29 +40,6 @@ func (w *removingWalker) doLeaf() ValidationErrors { return nil }
func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil }
func (w *removingWalker) doStruct(t schema.Struct) ValidationErrors {
s := w.value.MapValue
// If struct is null, empty, or atomic just return
if s == nil || len(s.Items) == 0 || t.ElementRelationship == schema.Atomic {
return nil
}
fieldTypes := map[string]schema.TypeRef{}
for _, structField := range t.Fields {
fieldTypes[structField.Name] = structField.Type
}
for i, _ := range s.Items {
item := s.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
removeItemsWithSchema(&s.Items[i].Value, subset, w.schema, fieldTypes[item.Name])
}
}
return nil
}
func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
l := w.value.ListValue
@@ -72,7 +49,7 @@ func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) {
}
newItems := []value.Value{}
for i, _ := range l.Items {
for i := range l.Items {
item := l.Items[i]
// Ignore error because we have already validated this list
pe, _ := listItemToPathElement(t, i, item)
@@ -101,16 +78,26 @@ func (w *removingWalker) doMap(t schema.Map) ValidationErrors {
return nil
}
fieldTypes := map[string]schema.TypeRef{}
for _, structField := range t.Fields {
fieldTypes[structField.Name] = structField.Type
}
newMap := &value.Map{}
for i, _ := range m.Items {
for i := range m.Items {
item := m.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
path, _ := fieldpath.MakePath(pe)
if w.toRemove.Has(path) {
continue
fieldType := t.ElementType
if ft, ok := fieldTypes[item.Name]; ok {
fieldType = ft
} else {
if w.toRemove.Has(path) {
continue
}
}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, t.ElementType)
removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, fieldType)
}
newMap.Set(item.Name, m.Items[i].Value)
}
@@ -122,6 +109,4 @@ func (w *removingWalker) doMap(t schema.Map) ValidationErrors {
return nil
}
func (*removingWalker) doUntyped(_ schema.Untyped) ValidationErrors { return nil }
func (*removingWalker) errorf(_ string, _ ...interface{}) ValidationErrors { return nil }

View File

@@ -25,45 +25,13 @@ import (
"sigs.k8s.io/structured-merge-diff/value"
)
// TypedValue is a value with an associated type.
type TypedValue interface {
// AsValue removes the type from the TypedValue and only keeps the value.
AsValue() *value.Value
// Validate returns an error with a list of every spec violation.
Validate() error
// ToFieldSet creates a set containing every leaf field and item mentioned, or
// validation errors, if any were encountered.
ToFieldSet() (*fieldpath.Set, error)
// Merge returns the result of merging tv and pso ("partially specified
// object") together. Of note:
// * No fields can be removed by this operation.
// * If both tv and pso specify a given leaf field, the result will keep pso's
// value.
// * Container typed elements will have their items ordered:
// * like tv, if pso doesn't change anything in the container
// * like pso, if pso does change something in the container.
// tv and pso must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
Merge(pso TypedValue) (TypedValue, error)
// Compare compares the two objects. See the comments on the `Comparison`
// struct for details on the return value.
//
// tv and rhs must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
Compare(rhs TypedValue) (c *Comparison, err error)
// RemoveItems removes each provided list or map item from the value.
RemoveItems(items *fieldpath.Set) TypedValue
}
// AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
// type 'typeName' in the schema. An error is returned if the v doesn't conform
// to the schema.
func AsTyped(v value.Value, s *schema.Schema, typeName string) (TypedValue, error) {
tv := typedValue{
func AsTyped(v value.Value, s *schema.Schema, typeRef schema.TypeRef) (*TypedValue, error) {
tv := &TypedValue{
value: v,
typeRef: schema.TypeRef{NamedType: &typeName},
typeRef: typeRef,
schema: s,
}
if err := tv.Validate(); err != nil {
@@ -76,36 +44,38 @@ func AsTyped(v value.Value, s *schema.Schema, typeName string) (TypedValue, erro
// conforms to the schema, for cases where that has already been checked or
// where you're going to call a method that validates as a side-effect (like
// ToFieldSet).
func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue {
tv := typedValue{
func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeRef schema.TypeRef) *TypedValue {
tv := &TypedValue{
value: v,
typeRef: schema.TypeRef{NamedType: &typeName},
typeRef: typeRef,
schema: s,
}
return tv
}
// typedValue is a value of some specific type.
type typedValue struct {
// TypedValue is a value of some specific type.
type TypedValue struct {
value value.Value
typeRef schema.TypeRef
schema *schema.Schema
}
var _ TypedValue = typedValue{}
func (tv typedValue) AsValue() *value.Value {
// AsValue removes the type from the TypedValue and only keeps the value.
func (tv TypedValue) AsValue() *value.Value {
return &tv.value
}
func (tv typedValue) Validate() error {
// Validate returns an error with a list of every spec violation.
func (tv TypedValue) Validate() error {
if errs := tv.walker().validate(); len(errs) != 0 {
return errs
}
return nil
}
func (tv typedValue) ToFieldSet() (*fieldpath.Set, error) {
// ToFieldSet creates a set containing every leaf field and item mentioned, or
// validation errors, if any were encountered.
func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
s := fieldpath.NewSet()
w := tv.walker()
w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
@@ -116,27 +86,34 @@ func (tv typedValue) ToFieldSet() (*fieldpath.Set, error) {
return s, nil
}
func (tv typedValue) Merge(pso TypedValue) (TypedValue, error) {
tpso, ok := pso.(typedValue)
if !ok {
return nil, errorFormatter{}.
errorf("can't merge typedValue with %T", pso)
}
return merge(tv, tpso, ruleKeepRHS, nil)
// Merge returns the result of merging tv and pso ("partially specified
// object") together. Of note:
// * No fields can be removed by this operation.
// * If both tv and pso specify a given leaf field, the result will keep pso's
// value.
// * Container typed elements will have their items ordered:
// * like tv, if pso doesn't change anything in the container
// * like pso, if pso does change something in the container.
// tv and pso must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
func (tv TypedValue) Merge(pso *TypedValue) (*TypedValue, error) {
return merge(&tv, pso, ruleKeepRHS, nil)
}
func (tv typedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
trhs, ok := rhs.(typedValue)
if !ok {
return nil, errorFormatter{}.
errorf("can't compare typedValue with %T", rhs)
}
// Compare compares the two objects. See the comments on the `Comparison`
// struct for details on the return value.
//
// tv and rhs must both be of the same type (their Schema and TypeRef must
// match), or an error will be returned. Validation errors will be returned if
// the objects don't conform to the schema.
func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
c = &Comparison{
Removed: fieldpath.NewSet(),
Modified: fieldpath.NewSet(),
Added: fieldpath.NewSet(),
}
c.Merged, err = merge(tv, trhs, func(w *mergingWalker) {
c.Merged, err = merge(&tv, rhs, func(w *mergingWalker) {
if w.lhs == nil {
c.Added.Insert(w.path)
} else if w.rhs == nil {
@@ -163,14 +140,71 @@ func (tv typedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
}
// RemoveItems removes each provided list or map item from the value.
func (tv typedValue) RemoveItems(items *fieldpath.Set) TypedValue {
copied := tv
copied.value, _ = value.FromUnstructured(tv.value.ToUnstructured(true))
removeItemsWithSchema(&copied.value, items, copied.schema, copied.typeRef)
return copied
func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
tv.value, _ = value.FromUnstructured(tv.value.ToUnstructured(true))
removeItemsWithSchema(&tv.value, items, tv.schema, tv.typeRef)
return &tv
}
func merge(lhs, rhs typedValue, rule, postRule mergeRule) (TypedValue, error) {
// NormalizeUnions takes the new object and normalizes the union:
// - If discriminator changed to non-nil, and a new field has been added
// that doesn't match, an error is returned,
// - If discriminator hasn't changed and two fields or more are set, an
// error is returned,
// - If discriminator changed to non-nil, all other fields but the
// discriminated one will be cleared,
// - Otherwise, If only one field is left, update discriminator to that value.
func (tv TypedValue) NormalizeUnions(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
if w.rhs != nil {
v := *w.rhs
w.out = &v
}
if err := normalizeUnions(w); err != nil {
errs = append(errs, w.error(err)...)
}
}
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
if mergeErrs != nil {
errs = append(errs, mergeErrs.(ValidationErrors)...)
}
if len(errs) > 0 {
return nil, errs
}
return out, nil
}
// NormalizeUnionsApply specifically normalize unions on apply. It
// validates that the applied union is correct (there should be no
// ambiguity there), and clear the fields according to the sent intent.
func (tv TypedValue) NormalizeUnionsApply(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
if w.rhs != nil {
v := *w.rhs
w.out = &v
}
if err := normalizeUnionsApply(w); err != nil {
errs = append(errs, w.error(err)...)
}
}
out, mergeErrs := merge(&tv, new, func(w *mergingWalker) {}, normalizeFn)
if mergeErrs != nil {
errs = append(errs, mergeErrs.(ValidationErrors)...)
}
if len(errs) > 0 {
return nil, errs
}
return out, nil
}
func (tv TypedValue) Empty() *TypedValue {
tv.value = value.Value{Null: true}
return &tv
}
func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error) {
if lhs.schema != rhs.schema {
return nil, errorFormatter{}.
errorf("expected objects with types from the same schema")
@@ -193,7 +227,7 @@ func merge(lhs, rhs typedValue, rule, postRule mergeRule) (TypedValue, error) {
return nil, errs
}
out := typedValue{
out := &TypedValue{
schema: lhs.schema,
typeRef: lhs.typeRef,
}
@@ -212,7 +246,7 @@ func merge(lhs, rhs typedValue, rule, postRule mergeRule) (TypedValue, error) {
type Comparison struct {
// Merged is the result of merging the two objects, as explained in the
// comments on TypedValue.Merge().
Merged TypedValue
Merged *TypedValue
// Removed contains any fields removed by rhs (the right-hand-side
// object in the comparison).

273
vendor/sigs.k8s.io/structured-merge-diff/typed/union.go generated vendored Normal file
View File

@@ -0,0 +1,273 @@
/*
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 typed
import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
func normalizeUnions(w *mergingWalker) error {
atom, found := w.schema.Resolve(w.typeRef)
if !found {
panic(fmt.Sprintf("Unable to resolve schema in normalize union: %v/%v", w.schema, w.typeRef))
}
// Unions can only be in structures, and the struct must not have been removed
if atom.Map == nil || w.out == nil {
return nil
}
old := &value.Map{}
if w.lhs != nil {
old = w.lhs.MapValue
}
for _, union := range atom.Map.Unions {
if err := newUnion(&union).Normalize(old, w.rhs.MapValue, w.out.MapValue); err != nil {
return err
}
}
return nil
}
func normalizeUnionsApply(w *mergingWalker) error {
atom, found := w.schema.Resolve(w.typeRef)
if !found {
panic(fmt.Sprintf("Unable to resolve schema in normalize union: %v/%v", w.schema, w.typeRef))
}
// Unions can only be in structures, and the struct must not have been removed
if atom.Map == nil || w.out == nil {
return nil
}
old := &value.Map{}
if w.lhs != nil {
old = w.lhs.MapValue
}
for _, union := range atom.Map.Unions {
if err := newUnion(&union).NormalizeApply(old, w.rhs.MapValue, w.out.MapValue); err != nil {
return err
}
}
return nil
}
type discriminated string
type field string
type discriminatedNames struct {
f2d map[field]discriminated
d2f map[discriminated]field
}
func newDiscriminatedName(f2d map[field]discriminated) discriminatedNames {
d2f := map[discriminated]field{}
for key, value := range f2d {
d2f[value] = key
}
return discriminatedNames{
f2d: f2d,
d2f: d2f,
}
}
func (dn discriminatedNames) toField(d discriminated) field {
if f, ok := dn.d2f[d]; ok {
return f
}
return field(d)
}
func (dn discriminatedNames) toDiscriminated(f field) discriminated {
if d, ok := dn.f2d[f]; ok {
return d
}
return discriminated(f)
}
type discriminator struct {
name string
}
func (d *discriminator) Set(m *value.Map, v discriminated) {
if d == nil {
return
}
m.Set(d.name, value.StringValue(string(v)))
}
func (d *discriminator) Get(m *value.Map) discriminated {
if d == nil || m == nil {
return ""
}
f, ok := m.Get(d.name)
if !ok {
return ""
}
if f.Value.StringValue == nil {
return ""
}
return discriminated(*f.Value.StringValue)
}
type fieldsSet map[field]struct{}
// newFieldsSet returns a map of the fields that are part of the union and are set
// in the given map.
func newFieldsSet(m *value.Map, fields []field) fieldsSet {
if m == nil {
return nil
}
set := fieldsSet{}
for _, f := range fields {
if subField, ok := m.Get(string(f)); ok && !subField.Value.Null {
set.Add(f)
}
}
return set
}
func (fs fieldsSet) Add(f field) {
if fs == nil {
fs = map[field]struct{}{}
}
fs[f] = struct{}{}
}
func (fs fieldsSet) One() *field {
for f := range fs {
return &f
}
return nil
}
func (fs fieldsSet) Has(f field) bool {
_, ok := fs[f]
return ok
}
func (fs fieldsSet) List() []field {
fields := []field{}
for f := range fs {
fields = append(fields, f)
}
return fields
}
func (fs fieldsSet) Difference(o fieldsSet) fieldsSet {
n := fieldsSet{}
for f := range fs {
if !o.Has(f) {
n.Add(f)
}
}
return n
}
func (fs fieldsSet) String() string {
s := []string{}
for k := range fs {
s = append(s, string(k))
}
return strings.Join(s, ", ")
}
type union struct {
deduceInvalidDiscriminator bool
d *discriminator
dn discriminatedNames
f []field
}
func newUnion(su *schema.Union) *union {
u := &union{}
if su.Discriminator != nil {
u.d = &discriminator{name: *su.Discriminator}
}
f2d := map[field]discriminated{}
for _, f := range su.Fields {
u.f = append(u.f, field(f.FieldName))
f2d[field(f.FieldName)] = discriminated(f.DiscriminatorValue)
}
u.dn = newDiscriminatedName(f2d)
u.deduceInvalidDiscriminator = su.DeduceInvalidDiscriminator
return u
}
// clear removes all the fields in map that are part of the union, but
// the one we decided to keep.
func (u *union) clear(m *value.Map, f field) {
for _, fieldName := range u.f {
if field(fieldName) != f {
m.Delete(string(fieldName))
}
}
}
func (u *union) Normalize(old, new, out *value.Map) error {
os := newFieldsSet(old, u.f)
ns := newFieldsSet(new, u.f)
diff := ns.Difference(os)
if u.d.Get(old) != u.d.Get(new) && u.d.Get(new) != "" {
if len(diff) == 1 && u.d.Get(new) != u.dn.toDiscriminated(*diff.One()) {
return fmt.Errorf("discriminator (%v) and field changed (%v) don't match", u.d.Get(new), diff.One())
}
if len(diff) > 1 {
return fmt.Errorf("multiple new fields added: %v", diff)
}
u.clear(out, u.dn.toField(u.d.Get(new)))
return nil
}
if len(ns) > 1 {
return fmt.Errorf("multiple fields set without discriminator change: %v", ns)
}
// Update discriminiator if it needs to be deduced.
if u.deduceInvalidDiscriminator && len(ns) == 1 {
u.d.Set(out, u.dn.toDiscriminated(*ns.One()))
}
return nil
}
func (u *union) NormalizeApply(applied, merged, out *value.Map) error {
as := newFieldsSet(applied, u.f)
if len(as) > 1 {
return fmt.Errorf("more than one field of union applied: %v", as)
}
if len(as) == 0 {
// None is set, just leave.
return nil
}
// We have exactly one, discriminiator must match if set
if u.d.Get(applied) != "" && u.d.Get(applied) != u.dn.toDiscriminated(*as.One()) {
return fmt.Errorf("applied discriminator (%v) doesn't match applied field (%v)", u.d.Get(applied), *as.One())
}
// Update discriminiator if needed
if u.deduceInvalidDiscriminator {
u.d.Set(out, u.dn.toDiscriminated(*as.One()))
}
// Clear others fields.
u.clear(out, *as.One())
return nil
}

View File

@@ -22,7 +22,7 @@ import (
"sigs.k8s.io/structured-merge-diff/value"
)
func (tv typedValue) walker() *validatingObjectWalker {
func (tv TypedValue) walker() *validatingObjectWalker {
return &validatingObjectWalker{
value: tv.value,
schema: tv.schema,
@@ -52,7 +52,7 @@ type validatingObjectWalker struct {
}
func (v validatingObjectWalker) validate() ValidationErrors {
return resolveSchema(v.schema, v.typeRef, v)
return resolveSchema(v.schema, v.typeRef, &v.value, v)
}
// doLeaf should be called on leaves before descending into children, if there
@@ -98,51 +98,6 @@ func (v validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
return nil
}
func (v validatingObjectWalker) visitStructFields(t schema.Struct, m *value.Map) (errs ValidationErrors) {
allowedNames := map[string]struct{}{}
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]
allowedNames[f.Name] = struct{}{}
child, ok := m.Get(f.Name)
if !ok {
// All fields are optional
continue
}
v2 := v
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &f.Name})
v2.value = child.Value
v2.typeRef = f.Type
errs = append(errs, v2.validate()...)
}
// All fields may be optional, but unknown fields are not allowed.
return append(errs, v.rejectExtraStructFields(m, allowedNames, "")...)
}
func (v validatingObjectWalker) doStruct(t schema.Struct) (errs ValidationErrors) {
m, err := mapOrStructValue(v.value, "struct")
if err != nil {
return v.error(err)
}
if t.ElementRelationship == schema.Atomic {
v.doLeaf()
}
if m == nil {
// nil is a valid map!
return nil
}
errs = v.visitStructFields(t, m)
// TODO: Check unions.
return errs
}
func (v validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
observedKeys := map[string]struct{}{}
for i, child := range list.Items {
@@ -190,21 +145,34 @@ func (v validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
}
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
}
for _, item := range m.Items {
v2 := v
name := item.Name
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &name})
v2.value = item.Value
v2.typeRef = t.ElementType
errs = append(errs, v2.validate()...)
v2.doNode()
var ok bool
if v2.typeRef, ok = fieldTypes[name]; ok {
errs = append(errs, v2.validate()...)
} else {
v2.typeRef = t.ElementType
errs = append(errs, v2.validate()...)
v2.doNode()
}
}
return errs
}
func (v validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
m, err := mapOrStructValue(v.value, "map")
m, err := mapValue(v.value)
if err != nil {
return v.error(err)
}
@@ -221,12 +189,3 @@ func (v validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
return errs
}
func (v validatingObjectWalker) doUntyped(t schema.Untyped) (errs ValidationErrors) {
if t.ElementRelationship == "" || t.ElementRelationship == schema.Atomic {
// Untyped sections allow anything, and are considered leaf
// fields.
v.doLeaf()
}
return nil
}

View File

@@ -84,6 +84,18 @@ func (m *Map) Set(key string, value Value) {
m.index = nil // Since the append might have reallocated
}
// Delete removes the key from the set.
func (m *Map) Delete(key string) {
items := []Field{}
for i := range m.Items {
if m.Items[i].Name != key {
items = append(items, m.Items[i])
}
}
m.Items = items
m.index = nil // Since the list has changed
}
// StringValue returns s as a scalar string Value.
func StringValue(s string) Value {
s2 := String(s)