mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add method to create strategic merge patch.
This commit is contained in:
		@@ -31,28 +31,357 @@ import (
 | 
			
		||||
// lists should be merged or replaced.
 | 
			
		||||
//
 | 
			
		||||
// For more information, see the PATCH section of docs/api-conventions.md.
 | 
			
		||||
func StrategicMergePatchData(original, patch []byte, dataStruct interface{}) ([]byte, error) {
 | 
			
		||||
	var o map[string]interface{}
 | 
			
		||||
	err := json.Unmarshal(original, &o)
 | 
			
		||||
//
 | 
			
		||||
// Some of the content of this package was borrowed with minor adaptations from
 | 
			
		||||
// evanphx/json-patch and openshift/origin.
 | 
			
		||||
 | 
			
		||||
const specialKey = "$patch"
 | 
			
		||||
const specialValue = "delete"
 | 
			
		||||
 | 
			
		||||
var errBadJSONDoc = fmt.Errorf("Invalid JSON document")
 | 
			
		||||
var errNoListOfLists = fmt.Errorf("Lists of lists are not supported")
 | 
			
		||||
 | 
			
		||||
// CreateStrategicMergePatch creates a patch that can be passed to StrategicMergePatch.
 | 
			
		||||
// The original and modified documents must be passed to the method as json encoded content.
 | 
			
		||||
// It will return a mergeable json document with differences from original to modified, or an error
 | 
			
		||||
// if either of the two documents is invalid.
 | 
			
		||||
func CreateStrategicMergePatch(original, modified []byte, dataStruct interface{}) ([]byte, error) {
 | 
			
		||||
	originalMap := map[string]interface{}{}
 | 
			
		||||
	err := json.Unmarshal(original, &originalMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errBadJSONDoc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	modifiedMap := map[string]interface{}{}
 | 
			
		||||
	err = json.Unmarshal(modified, &modifiedMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errBadJSONDoc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := getTagStructType(dataStruct)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var p map[string]interface{}
 | 
			
		||||
	err = json.Unmarshal(patch, &p)
 | 
			
		||||
	patchMap, err := diffMaps(originalMap, modifiedMap, t, false, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := reflect.TypeOf(dataStruct)
 | 
			
		||||
	return json.Marshal(patchMap)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original.
 | 
			
		||||
func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreAdditions, ignoreChangesAndDeletions bool) (map[string]interface{}, error) {
 | 
			
		||||
	patch := map[string]interface{}{}
 | 
			
		||||
	if t.Kind() == reflect.Ptr {
 | 
			
		||||
		t = t.Elem()
 | 
			
		||||
	}
 | 
			
		||||
	if t.Kind() != reflect.Struct {
 | 
			
		||||
		return nil, fmt.Errorf("strategic merge patch needs a struct, %s received instead", t.Kind().String())
 | 
			
		||||
 | 
			
		||||
	for key, modifiedValue := range modified {
 | 
			
		||||
		originalValue, ok := original[key]
 | 
			
		||||
		// value was added
 | 
			
		||||
		if !ok {
 | 
			
		||||
			if !ignoreAdditions {
 | 
			
		||||
				patch[key] = modifiedValue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	result, err := mergeMap(o, p, t)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if key == specialKey {
 | 
			
		||||
			originalString, ok := originalValue.(string)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf("invalid value for special key: %s", specialKey)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			modifiedString, ok := modifiedValue.(string)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf("invalid value for special key: %s", specialKey)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if modifiedString != originalString {
 | 
			
		||||
				patch[key] = modifiedValue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !ignoreChangesAndDeletions {
 | 
			
		||||
			// If types have changed, replace completely
 | 
			
		||||
			if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
 | 
			
		||||
				patch[key] = modifiedValue
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Types are the same, compare values
 | 
			
		||||
		switch originalValueTyped := originalValue.(type) {
 | 
			
		||||
		case map[string]interface{}:
 | 
			
		||||
			modifiedValueTyped := modifiedValue.(map[string]interface{})
 | 
			
		||||
			fieldType, _, _, err := forkedjson.LookupPatchMetadata(t, key)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			patchValue, err := diffMaps(originalValueTyped, modifiedValueTyped, fieldType, ignoreAdditions, ignoreChangesAndDeletions)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(patchValue) > 0 {
 | 
			
		||||
				patch[key] = patchValue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			continue
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			modifiedValueTyped := modifiedValue.([]interface{})
 | 
			
		||||
			fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, key)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if fieldPatchStrategy == "merge" {
 | 
			
		||||
				patchValue, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreAdditions, ignoreChangesAndDeletions)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if len(patchValue) > 0 {
 | 
			
		||||
					patch[key] = patchValue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !ignoreChangesAndDeletions {
 | 
			
		||||
			if !reflect.DeepEqual(originalValue, modifiedValue) {
 | 
			
		||||
				patch[key] = modifiedValue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ignoreChangesAndDeletions {
 | 
			
		||||
		// Now add all deleted values as nil
 | 
			
		||||
		for key := range original {
 | 
			
		||||
			_, found := modified[key]
 | 
			
		||||
			if !found {
 | 
			
		||||
				patch[key] = nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return patch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
 | 
			
		||||
// for a pair of lists with merge semantics.
 | 
			
		||||
func diffLists(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreAdditions, ignoreChangesAndDeletions bool) ([]interface{}, error) {
 | 
			
		||||
	if len(original) == 0 {
 | 
			
		||||
		if len(modified) == 0 || ignoreAdditions {
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return modified, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	elementType, err := sliceElementType(original, modified)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var patch []interface{}
 | 
			
		||||
 | 
			
		||||
	// If the elements are not maps...
 | 
			
		||||
	if elementType.Kind() == reflect.Map {
 | 
			
		||||
		patch, err = diffListsOfMaps(original, modified, t, mergeKey, ignoreAdditions, ignoreChangesAndDeletions)
 | 
			
		||||
	} else {
 | 
			
		||||
		patch, err = diffListsOfScalars(original, modified, ignoreAdditions)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return patch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
 | 
			
		||||
// for a pair of lists of scalars with merge semantics.
 | 
			
		||||
func diffListsOfScalars(original, modified []interface{}, ignoreAdditions bool) ([]interface{}, error) {
 | 
			
		||||
	if len(modified) == 0 {
 | 
			
		||||
		// There is no need to check the length of original because there is no way to create
 | 
			
		||||
		// a patch that deletes a scalar from a list of scalars with merge semantics.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	patch := []interface{}{}
 | 
			
		||||
 | 
			
		||||
	originalScalars := uniqifyAndSortScalars(original)
 | 
			
		||||
	modifiedScalars := uniqifyAndSortScalars(modified)
 | 
			
		||||
	originalIndex, modifiedIndex := 0, 0
 | 
			
		||||
 | 
			
		||||
loopB:
 | 
			
		||||
	for ; modifiedIndex < len(modifiedScalars); modifiedIndex++ {
 | 
			
		||||
		for ; originalIndex < len(originalScalars); originalIndex++ {
 | 
			
		||||
			originalString := fmt.Sprintf("%v", original[originalIndex])
 | 
			
		||||
			modifiedString := fmt.Sprintf("%v", modified[modifiedIndex])
 | 
			
		||||
			if originalString >= modifiedString {
 | 
			
		||||
				if originalString != modifiedString {
 | 
			
		||||
					if !ignoreAdditions {
 | 
			
		||||
						patch = append(patch, modified[modifiedIndex])
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue loopB
 | 
			
		||||
			}
 | 
			
		||||
			// There is no else clause because there is no way to create a patch that deletes
 | 
			
		||||
			// a scalar from a list of scalars with merge semantics.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ignoreAdditions {
 | 
			
		||||
		// Add any remaining items found only in modified
 | 
			
		||||
		for ; modifiedIndex < len(modifiedScalars); modifiedIndex++ {
 | 
			
		||||
			patch = append(patch, modified[modifiedIndex])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return patch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var errNoMergeKeyFmt = "map: %v does not contain declared merge key: %s"
 | 
			
		||||
var errBadArgTypeFmt = "expected a %s, but received a %t"
 | 
			
		||||
 | 
			
		||||
// Returns a (recursive) strategic merge patch that yields modified when applied to original,
 | 
			
		||||
// for a pair of lists of maps with merge semantics.
 | 
			
		||||
func diffListsOfMaps(original, modified []interface{}, t reflect.Type, mergeKey string, ignoreAdditions, ignoreChangesAndDeletions bool) ([]interface{}, error) {
 | 
			
		||||
	patch := make([]interface{}, 0)
 | 
			
		||||
 | 
			
		||||
	originalSorted, err := sortMergeListsByNameArray(original, t, mergeKey, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	modifiedSorted, err := sortMergeListsByNameArray(modified, t, mergeKey, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	originalIndex, modifiedIndex := 0, 0
 | 
			
		||||
 | 
			
		||||
loopB:
 | 
			
		||||
	for ; modifiedIndex < len(modifiedSorted); modifiedIndex++ {
 | 
			
		||||
		modifiedMap, ok := modifiedSorted[modifiedIndex].(map[string]interface{})
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, fmt.Errorf(errBadArgTypeFmt, "map[string]interface{}", modifiedSorted[modifiedIndex])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		modifiedValue, ok := modifiedMap[mergeKey]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, fmt.Errorf(errNoMergeKeyFmt, modifiedMap, mergeKey)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for ; originalIndex < len(originalSorted); originalIndex++ {
 | 
			
		||||
			originalMap, ok := originalSorted[originalIndex].(map[string]interface{})
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf(errBadArgTypeFmt, "map[string]interface{}", originalSorted[originalIndex])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			originalValue, ok := originalMap[mergeKey]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf(errNoMergeKeyFmt, originalMap, mergeKey)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Assume that the merge key values are comparable strings
 | 
			
		||||
			originalString := fmt.Sprintf("%v", originalValue)
 | 
			
		||||
			modifiedString := fmt.Sprintf("%v", modifiedValue)
 | 
			
		||||
			if originalString >= modifiedString {
 | 
			
		||||
				if originalString == modifiedString {
 | 
			
		||||
					patchValue, err := diffMaps(originalMap, modifiedMap, t, ignoreAdditions, ignoreChangesAndDeletions)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					originalIndex++
 | 
			
		||||
					if len(patchValue) > 0 {
 | 
			
		||||
						patchValue[mergeKey] = modifiedValue
 | 
			
		||||
						patch = append(patch, patchValue)
 | 
			
		||||
					}
 | 
			
		||||
				} else if !ignoreAdditions {
 | 
			
		||||
					patch = append(patch, modifiedMap)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue loopB
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !ignoreChangesAndDeletions {
 | 
			
		||||
				patch = append(patch, map[string]interface{}{mergeKey: originalValue, specialKey: specialValue})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ignoreChangesAndDeletions {
 | 
			
		||||
		// Delete any remaining items found only in original
 | 
			
		||||
		for ; originalIndex < len(originalSorted); originalIndex++ {
 | 
			
		||||
			originalMap, ok := originalSorted[originalIndex].(map[string]interface{})
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf(errBadArgTypeFmt, "map[string]interface{}", originalSorted[originalIndex])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			originalValue, ok := originalMap[mergeKey]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, fmt.Errorf(errNoMergeKeyFmt, originalMap, mergeKey)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			patch = append(patch, map[string]interface{}{mergeKey: originalValue, specialKey: specialValue})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ignoreAdditions {
 | 
			
		||||
		// Add any remaining items found only in modified
 | 
			
		||||
		for ; modifiedIndex < len(modifiedSorted); modifiedIndex++ {
 | 
			
		||||
			patch = append(patch, modified[modifiedIndex])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return patch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StrategicMergePatchData applies a patch using strategic merge patch semantics.
 | 
			
		||||
// Deprecated: StrategicMergePatchData is deprecated. Use the synonym StrategicMergePatch,
 | 
			
		||||
// instead, which follows the naming convention of evanphx/json-patch.
 | 
			
		||||
func StrategicMergePatchData(original, patch []byte, dataStruct interface{}) ([]byte, error) {
 | 
			
		||||
	return StrategicMergePatch(original, patch, dataStruct)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StrategicMergePatch applies a strategic merge patch. The patch and the original document
 | 
			
		||||
// must be json encoded content. A patch can be created from an original and a modified document
 | 
			
		||||
// by calling CreateStrategicMergePatch.
 | 
			
		||||
func StrategicMergePatch(original, patch []byte, dataStruct interface{}) ([]byte, error) {
 | 
			
		||||
	originalMap := map[string]interface{}{}
 | 
			
		||||
	err := json.Unmarshal(original, &originalMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errBadJSONDoc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	patchMap := map[string]interface{}{}
 | 
			
		||||
	err = json.Unmarshal(patch, &patchMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errBadJSONDoc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := getTagStructType(dataStruct)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := mergeMap(originalMap, patchMap, t)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -60,7 +389,20 @@ func StrategicMergePatchData(original, patch []byte, dataStruct interface{}) ([]
 | 
			
		||||
	return json.Marshal(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const specialKey = "$patch"
 | 
			
		||||
func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
 | 
			
		||||
	t := reflect.TypeOf(dataStruct)
 | 
			
		||||
	if t.Kind() == reflect.Ptr {
 | 
			
		||||
		t = t.Elem()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.Kind() != reflect.Struct {
 | 
			
		||||
		return nil, fmt.Errorf(errBadArgTypeFmt, "struct", t.Kind().String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var errBadPatchTypeFmt = "unknown patch type: %s in map: %v"
 | 
			
		||||
 | 
			
		||||
// Merge fields from a patch map into the original map. Note: This may modify
 | 
			
		||||
// both the original map and the patch because getting a deep copy of a map in
 | 
			
		||||
@@ -74,7 +416,8 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
 | 
			
		||||
			delete(patch, specialKey)
 | 
			
		||||
			return patch, nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil, fmt.Errorf("unknown patch type found: %s", v)
 | 
			
		||||
 | 
			
		||||
		return nil, fmt.Errorf(errBadPatchTypeFmt, v, patch)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// nil is an accepted value for original to simplify logic in other places.
 | 
			
		||||
@@ -92,29 +435,32 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
 | 
			
		||||
			if _, ok := original[k]; ok {
 | 
			
		||||
				delete(original, k)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, ok := original[k]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// If it's not in the original document, just take the patch value.
 | 
			
		||||
			original[k] = patchV
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If the data type is a pointer, resolve the element.
 | 
			
		||||
		if t.Kind() == reflect.Ptr {
 | 
			
		||||
			t = t.Elem()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If they're both maps or lists, recurse into the value.
 | 
			
		||||
		originalType := reflect.TypeOf(original[k])
 | 
			
		||||
		patchType := reflect.TypeOf(patchV)
 | 
			
		||||
		if originalType == patchType {
 | 
			
		||||
			// First find the fieldPatchStrategy and fieldPatchMergeKey.
 | 
			
		||||
			fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		originalType := reflect.TypeOf(original[k])
 | 
			
		||||
		patchType := reflect.TypeOf(patchV)
 | 
			
		||||
		if originalType == patchType {
 | 
			
		||||
			if originalType.Kind() == reflect.Map && fieldPatchStrategy != "replace" {
 | 
			
		||||
				typedOriginal := original[k].(map[string]interface{})
 | 
			
		||||
				typedPatch := patchV.(map[string]interface{})
 | 
			
		||||
@@ -123,8 +469,10 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if originalType.Kind() == reflect.Slice && fieldPatchStrategy == "merge" {
 | 
			
		||||
				elemType := fieldType.Elem()
 | 
			
		||||
				typedOriginal := original[k].([]interface{})
 | 
			
		||||
@@ -134,6 +482,7 @@ func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[strin
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -154,15 +503,13 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
	if len(original) == 0 && len(patch) == 0 {
 | 
			
		||||
		return original, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// All the values must be of the same type, but not a list.
 | 
			
		||||
	t, err := sliceElementType(original, patch)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("types of list elements need to be the same, type: %s: %v",
 | 
			
		||||
			elemType.Kind().String(), err)
 | 
			
		||||
	}
 | 
			
		||||
	if t.Kind() == reflect.Slice {
 | 
			
		||||
		return nil, fmt.Errorf("not supporting merging lists of lists yet")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the elements are not maps, merge the slices of scalars.
 | 
			
		||||
	if t.Kind() != reflect.Map {
 | 
			
		||||
		// Maybe in the future add a "concat" mode that doesn't
 | 
			
		||||
@@ -182,10 +529,14 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
		typedV := v.(map[string]interface{})
 | 
			
		||||
		patchType, ok := typedV[specialKey]
 | 
			
		||||
		if ok {
 | 
			
		||||
			if patchType == "delete" {
 | 
			
		||||
			if patchType == specialValue {
 | 
			
		||||
				mergeValue, ok := typedV[mergeKey]
 | 
			
		||||
				if ok {
 | 
			
		||||
					_, originalKey, found := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
 | 
			
		||||
					_, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if found {
 | 
			
		||||
						// Delete the element at originalKey.
 | 
			
		||||
						original = append(original[:originalKey], original[originalKey+1:]...)
 | 
			
		||||
@@ -199,7 +550,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
			} else if patchType == "merge" {
 | 
			
		||||
				return nil, fmt.Errorf("merging lists cannot yet be specified in the patch")
 | 
			
		||||
			} else {
 | 
			
		||||
				return nil, fmt.Errorf("unknown patch type found: %s", patchType)
 | 
			
		||||
				return nil, fmt.Errorf(errBadPatchTypeFmt, patchType, typedV)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			patchWithoutSpecialElements = append(patchWithoutSpecialElements, v)
 | 
			
		||||
@@ -218,11 +569,16 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
		typedV := v.(map[string]interface{})
 | 
			
		||||
		mergeValue, ok := typedV[mergeKey]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, fmt.Errorf("all list elements need the merge key %s", mergeKey)
 | 
			
		||||
			return nil, fmt.Errorf(errNoMergeKeyFmt, typedV, mergeKey)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If we find a value with this merge key value in original, merge the
 | 
			
		||||
		// maps. Otherwise append onto original.
 | 
			
		||||
		originalMap, originalKey, found := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
 | 
			
		||||
		originalMap, originalKey, found, err := findMapInSliceBasedOnKeyValue(original, mergeKey, mergeValue)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			var mergedMaps interface{}
 | 
			
		||||
			var err error
 | 
			
		||||
@@ -231,6 +587,7 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			original[originalKey] = mergedMaps
 | 
			
		||||
		} else {
 | 
			
		||||
			original = append(original, v)
 | 
			
		||||
@@ -240,16 +597,21 @@ func mergeSlice(original, patch []interface{}, elemType reflect.Type, mergeKey s
 | 
			
		||||
	return original, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This panics if any element of the slice is not a map.
 | 
			
		||||
func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{}) (map[string]interface{}, int, bool) {
 | 
			
		||||
// This method no longer panics if any element of the slice is not a map.
 | 
			
		||||
func findMapInSliceBasedOnKeyValue(m []interface{}, key string, value interface{}) (map[string]interface{}, int, bool, error) {
 | 
			
		||||
	for k, v := range m {
 | 
			
		||||
		typedV := v.(map[string]interface{})
 | 
			
		||||
		typedV, ok := v.(map[string]interface{})
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, 0, false, fmt.Errorf("value for key %v is not a map.", k)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		valueToMatch, ok := typedV[key]
 | 
			
		||||
		if ok && valueToMatch == value {
 | 
			
		||||
			return typedV, k, true
 | 
			
		||||
			return typedV, k, true, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, 0, false
 | 
			
		||||
 | 
			
		||||
	return nil, 0, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This function takes a JSON map and sorts all the lists that should be merged
 | 
			
		||||
@@ -274,10 +636,12 @@ func sortMergeListsByName(mapJSON []byte, dataStruct interface{}) ([]byte, error
 | 
			
		||||
func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
 | 
			
		||||
	newS := map[string]interface{}{}
 | 
			
		||||
	for k, v := range s {
 | 
			
		||||
		if k != specialKey {
 | 
			
		||||
			fieldType, fieldPatchStrategy, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadata(t, k)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// If v is a map or a merge slice, recurse.
 | 
			
		||||
			if typedV, ok := v.(map[string]interface{}); ok {
 | 
			
		||||
				var err error
 | 
			
		||||
@@ -288,29 +652,30 @@ func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[stri
 | 
			
		||||
			} else if typedV, ok := v.([]interface{}); ok {
 | 
			
		||||
				if fieldPatchStrategy == "merge" {
 | 
			
		||||
					var err error
 | 
			
		||||
				v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey)
 | 
			
		||||
					v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newS[k] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newS, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey string) ([]interface{}, error) {
 | 
			
		||||
func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey string, recurse bool) ([]interface{}, error) {
 | 
			
		||||
	if len(s) == 0 {
 | 
			
		||||
		return s, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We don't support lists of lists yet.
 | 
			
		||||
	t, err := sliceElementType(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if t.Kind() == reflect.Slice {
 | 
			
		||||
		return nil, fmt.Errorf("not supporting lists of lists yet")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the elements are not maps...
 | 
			
		||||
	if t.Kind() != reflect.Map {
 | 
			
		||||
@@ -319,15 +684,20 @@ func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Elements are maps - if one of the keys of the map is a map or a
 | 
			
		||||
	// list, we need to recurse into it.
 | 
			
		||||
	// list, we may need to recurse into it.
 | 
			
		||||
	newS := []interface{}{}
 | 
			
		||||
	for _, elem := range s {
 | 
			
		||||
		if recurse {
 | 
			
		||||
			typedElem := elem.(map[string]interface{})
 | 
			
		||||
			newElem, err := sortMergeListsByNameMap(typedElem, elemType)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			newS = append(newS, newElem)
 | 
			
		||||
		} else {
 | 
			
		||||
			newS = append(newS, elem)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort the maps.
 | 
			
		||||
@@ -336,19 +706,32 @@ func sortMergeListsByNameArray(s []interface{}, elemType reflect.Type, mergeKey
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sortMapsBasedOnField(m []interface{}, fieldName string) []interface{} {
 | 
			
		||||
	mapM := []map[string]interface{}{}
 | 
			
		||||
	for _, v := range m {
 | 
			
		||||
		mapM = append(mapM, v.(map[string]interface{}))
 | 
			
		||||
	}
 | 
			
		||||
	mapM := mapSliceFromSlice(m)
 | 
			
		||||
	ss := SortableSliceOfMaps{mapM, fieldName}
 | 
			
		||||
	sort.Sort(ss)
 | 
			
		||||
	newM := []interface{}{}
 | 
			
		||||
	for _, v := range ss.s {
 | 
			
		||||
		newM = append(newM, v)
 | 
			
		||||
	newS := sliceFromMapSlice(ss.s)
 | 
			
		||||
	return newS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mapSliceFromSlice(m []interface{}) []map[string]interface{} {
 | 
			
		||||
	newM := []map[string]interface{}{}
 | 
			
		||||
	for _, v := range m {
 | 
			
		||||
		vt := v.(map[string]interface{})
 | 
			
		||||
		newM = append(newM, vt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newM
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sliceFromMapSlice(s []map[string]interface{}) []interface{} {
 | 
			
		||||
	newS := []interface{}{}
 | 
			
		||||
	for _, v := range s {
 | 
			
		||||
		newS = append(newS, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SortableSliceOfMaps struct {
 | 
			
		||||
	s []map[string]interface{}
 | 
			
		||||
	k string // key to sort on
 | 
			
		||||
@@ -391,6 +774,7 @@ func uniqifyScalars(s []interface{}) []interface{} {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -415,7 +799,7 @@ func (ss SortableSliceOfScalars) Swap(i, j int) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the type of the elements of N slice(s). If the type is different,
 | 
			
		||||
// returns an error.
 | 
			
		||||
// another slice or undefined, returns an error.
 | 
			
		||||
func sliceElementType(slices ...[]interface{}) (reflect.Type, error) {
 | 
			
		||||
	var prevType reflect.Type
 | 
			
		||||
	for _, s := range slices {
 | 
			
		||||
@@ -424,16 +808,22 @@ func sliceElementType(slices ...[]interface{}) (reflect.Type, error) {
 | 
			
		||||
			currentType := reflect.TypeOf(v)
 | 
			
		||||
			if prevType == nil {
 | 
			
		||||
				prevType = currentType
 | 
			
		||||
				// We don't support lists of lists yet.
 | 
			
		||||
				if prevType.Kind() == reflect.Slice {
 | 
			
		||||
					return nil, errNoListOfLists
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if prevType != currentType {
 | 
			
		||||
					return nil, fmt.Errorf("at least two types found: %s and %s", prevType, currentType)
 | 
			
		||||
					return nil, fmt.Errorf("list element types are not identical: %v", fmt.Sprint(slices))
 | 
			
		||||
				}
 | 
			
		||||
				prevType = currentType
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if prevType == nil {
 | 
			
		||||
		return nil, fmt.Errorf("no elements in any given slices")
 | 
			
		||||
		return nil, fmt.Errorf("no elements in any of the given slices")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return prevType, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,19 +26,26 @@ import (
 | 
			
		||||
	"github.com/ghodss/yaml"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TestCases struct {
 | 
			
		||||
	StrategicMergePatchCases []StrategicMergePatchCase
 | 
			
		||||
	SortMergeListTestCases   []SortMergeListCase
 | 
			
		||||
type StrategicMergePatchTestCases struct {
 | 
			
		||||
	TestCases []StrategicMergePatchTestCase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StrategicMergePatchCase struct {
 | 
			
		||||
	Description string
 | 
			
		||||
	Patch       map[string]interface{}
 | 
			
		||||
type SortMergeListTestCases struct {
 | 
			
		||||
	TestCases []SortMergeListTestCase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StrategicMergePatchTestCaseData struct {
 | 
			
		||||
	Original map[string]interface{}
 | 
			
		||||
	Result      map[string]interface{}
 | 
			
		||||
	Patch    map[string]interface{}
 | 
			
		||||
	Modified map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SortMergeListCase struct {
 | 
			
		||||
type StrategicMergePatchTestCase struct {
 | 
			
		||||
	Description string
 | 
			
		||||
	StrategicMergePatchTestCaseData
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SortMergeListTestCase struct {
 | 
			
		||||
	Description string
 | 
			
		||||
	Original    map[string]interface{}
 | 
			
		||||
	Sorted      map[string]interface{}
 | 
			
		||||
@@ -47,6 +54,7 @@ type SortMergeListCase struct {
 | 
			
		||||
type MergeItem struct {
 | 
			
		||||
	Name              string
 | 
			
		||||
	Value             string
 | 
			
		||||
	Other             string
 | 
			
		||||
	MergingList       []MergeItem `patchStrategy:"merge" patchMergeKey:"name"`
 | 
			
		||||
	NonMergingList    []MergeItem
 | 
			
		||||
	MergingIntList    []int `patchStrategy:"merge"`
 | 
			
		||||
@@ -55,220 +63,10 @@ type MergeItem struct {
 | 
			
		||||
	SimpleMap         map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var testCaseData = []byte(`
 | 
			
		||||
strategicMergePatchCases:
 | 
			
		||||
  - description: add new field
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      value: 1
 | 
			
		||||
    result:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
  - description: remove field and add new field
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      name: null
 | 
			
		||||
      value: 1
 | 
			
		||||
    result:
 | 
			
		||||
      value: 1
 | 
			
		||||
  - description: merge arrays of scalars
 | 
			
		||||
    original:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
    result:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
  - description: replace arrays of scalars
 | 
			
		||||
    original:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
    patch:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
    result:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
  - description: update param of list that should be merged but had element added serverside
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: a
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: a
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
  - description: delete field when field is nested in a map
 | 
			
		||||
    original:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key1: 1
 | 
			
		||||
        key2: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key2: null
 | 
			
		||||
    result:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key1: 1
 | 
			
		||||
  - description: update nested list when nested list should not be merged
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: update nested list when nested list should be merged
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: update map when map should be replaced
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      value: 1
 | 
			
		||||
      $patch: replace
 | 
			
		||||
    result:
 | 
			
		||||
      value: 1
 | 
			
		||||
  - description: merge empty merge lists
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: delete others in a map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      $patch: replace
 | 
			
		||||
    result: {}
 | 
			
		||||
  - description: delete item from a merge list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - $patch: delete
 | 
			
		||||
          name: 1
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: add and delete item from a merge list
 | 
			
		||||
    original:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 3
 | 
			
		||||
        - $patch: delete
 | 
			
		||||
          name: 1
 | 
			
		||||
    result:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 2
 | 
			
		||||
        - name: 3
 | 
			
		||||
  - description: delete all items from a merge list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - $patch: replace
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: add new field inside pointers
 | 
			
		||||
    original:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 2
 | 
			
		||||
    result:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: update nested pointers
 | 
			
		||||
    original:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    result:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
sortMergeListTestCases:
 | 
			
		||||
// These are test cases for SortMergeList, used to assert that it (recursively)
 | 
			
		||||
// sorts both merging and non merging lists correctly.
 | 
			
		||||
var sortMergeListTestCaseData = []byte(`
 | 
			
		||||
testCases:
 | 
			
		||||
  - description: sort one list of maps
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
@@ -327,7 +125,7 @@ sortMergeListTestCases:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
            - name: 3
 | 
			
		||||
  - description: merging list should NOT sort when nested in a non merging list
 | 
			
		||||
  - description: merging list should NOT sort when nested in non merging list
 | 
			
		||||
    original:
 | 
			
		||||
      nonMergingList:
 | 
			
		||||
        - name: 2
 | 
			
		||||
@@ -350,7 +148,7 @@ sortMergeListTestCases:
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 2
 | 
			
		||||
            - name: 1
 | 
			
		||||
  - description: sort a very nested list of maps
 | 
			
		||||
  - description: sort very nested list of maps
 | 
			
		||||
    fieldTypes:
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
@@ -410,7 +208,7 @@ sortMergeListTestCases:
 | 
			
		||||
            - 1
 | 
			
		||||
            - 2
 | 
			
		||||
            - 3
 | 
			
		||||
  - description: sort one pointer of maps
 | 
			
		||||
  - description: sort merging list by pointer
 | 
			
		||||
    original:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
@@ -423,81 +221,546 @@ sortMergeListTestCases:
 | 
			
		||||
        - name: 3
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
func TestStrategicMergePatch(t *testing.T) {
 | 
			
		||||
	tc := TestCases{}
 | 
			
		||||
	err := yaml.Unmarshal(testCaseData, &tc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("can't unmarshal test cases: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var e MergeItem
 | 
			
		||||
	for _, c := range tc.StrategicMergePatchCases {
 | 
			
		||||
		result, err := StrategicMergePatchData(toJSON(c.Original), toJSON(c.Patch), e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("error patching: %v:\noriginal:\n%s\npatch:\n%s",
 | 
			
		||||
				err, toYAML(c.Original), toYAML(c.Patch))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Sort the lists that have merged maps, since order is not significant.
 | 
			
		||||
		result, err = sortMergeListsByName(result, e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("error sorting result object: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		cResult, err := sortMergeListsByName(toJSON(c.Result), e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("error sorting result object: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(result, cResult) {
 | 
			
		||||
			t.Errorf("patching failed: %s\noriginal:\n%s\npatch:\n%s\nexpected result:\n%s\ngot result:\n%s",
 | 
			
		||||
				c.Description, toYAML(c.Original), toYAML(c.Patch), jsonToYAML(cResult), jsonToYAML(result))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSortMergeLists(t *testing.T) {
 | 
			
		||||
	tc := TestCases{}
 | 
			
		||||
	err := yaml.Unmarshal(testCaseData, &tc)
 | 
			
		||||
	tc := SortMergeListTestCases{}
 | 
			
		||||
	err := yaml.Unmarshal(sortMergeListTestCaseData, &tc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("can't unmarshal test cases: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var e MergeItem
 | 
			
		||||
	for _, c := range tc.SortMergeListTestCases {
 | 
			
		||||
		sorted, err := sortMergeListsByName(toJSON(c.Original), e)
 | 
			
		||||
	for _, c := range tc.TestCases {
 | 
			
		||||
		sorted, err := sortMergeListsByName(toJSONOrFail(c.Original, t), e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("sort arrays returned error: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(sorted, toJSON(c.Sorted)) {
 | 
			
		||||
			t.Errorf("sorting failed: %s\ntried to sort:\n%s\nexpected:\n%s\ngot:\n%s",
 | 
			
		||||
				c.Description, toYAML(c.Original), toYAML(c.Sorted), jsonToYAML(sorted))
 | 
			
		||||
		if !reflect.DeepEqual(sorted, toJSONOrFail(c.Sorted, t)) {
 | 
			
		||||
			t.Errorf("sorting failed: %v\ntried to sort:\n%vexpected:\n%vgot:\n%v",
 | 
			
		||||
				c.Description, toYAMLOrError(c.Original), toYAMLOrError(c.Sorted), jsonToYAMLOrError(sorted))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toYAML(v interface{}) string {
 | 
			
		||||
	y, err := yaml.Marshal(v)
 | 
			
		||||
// These are test cases for StrategicMergePatch that cannot be generated using
 | 
			
		||||
// CreateStrategicMergePatch because it doesn't use the replace directive, generate
 | 
			
		||||
// duplicate integers for a merging list patch, or generate empty merging lists.
 | 
			
		||||
var customStrategicMergePatchTestCaseData = []byte(`
 | 
			
		||||
testCases:
 | 
			
		||||
  - description: unique scalars when merging lists
 | 
			
		||||
    original:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
  - description: delete all items from merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - $patch: replace
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: merge empty merging lists
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: delete all keys from map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      $patch: replace
 | 
			
		||||
    modified: {}
 | 
			
		||||
  - description: add key and delete all keys from map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      other: a
 | 
			
		||||
      $patch: replace
 | 
			
		||||
    modified:
 | 
			
		||||
      other: a
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
func TestCustomStrategicMergePatch(t *testing.T) {
 | 
			
		||||
	tc := StrategicMergePatchTestCases{}
 | 
			
		||||
	err := yaml.Unmarshal(customStrategicMergePatchTestCaseData, &tc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("yaml marshal failed: %v", err))
 | 
			
		||||
		t.Errorf("can't unmarshal test cases: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range tc.TestCases {
 | 
			
		||||
		cOriginal, cPatch, cModified := testCaseToJSONOrFail(t, c)
 | 
			
		||||
		testPatchApplication(t, cOriginal, cPatch, cModified, c.Description)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testCaseToJSONOrFail(t *testing.T, c StrategicMergePatchTestCase) ([]byte, []byte, []byte) {
 | 
			
		||||
	var e MergeItem
 | 
			
		||||
	cOriginal := toJSONOrFail(c.Original, t)
 | 
			
		||||
	cPatch, err := sortMergeListsByName(toJSONOrFail(c.Patch, t), e)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error:%v sorting patch object:\n%v", err, c.Patch)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cModified, err := sortMergeListsByName(toJSONOrFail(c.Modified, t), e)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error: %v sorting modified object:\n%v", err, c.Modified)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cOriginal, cPatch, cModified
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testPatchApplication(t *testing.T, cOriginal, cPatch, cModified []byte, description string) {
 | 
			
		||||
	var e MergeItem
 | 
			
		||||
	modified, err := StrategicMergePatch(cOriginal, cPatch, e)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error applying patch: %v:\noriginal:\n%vpatch:\n%v",
 | 
			
		||||
			err, jsonToYAMLOrError(cOriginal), jsonToYAMLOrError(cPatch))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sort the lists that have merged maps, since order is not significant.
 | 
			
		||||
	modified, err = sortMergeListsByName(modified, e)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("error: %v sorting modified object:\n%v", err, modified)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !reflect.DeepEqual(modified, cModified) {
 | 
			
		||||
		t.Errorf("patch application failed: %v\noriginal:\n%vpatch:\n%vexpected modified:\n%vgot modified:\n%v",
 | 
			
		||||
			description, jsonToYAMLOrError(cOriginal), jsonToYAMLOrError(cPatch),
 | 
			
		||||
			jsonToYAMLOrError(cModified), jsonToYAMLOrError(modified))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// These are test cases for CreateStrategicMergePatch, used to assert that it
 | 
			
		||||
// generates the correct patch for a given outcome. They are also test cases for
 | 
			
		||||
// StrategicMergePatch, used to assert that applying a patch yields the correct
 | 
			
		||||
// outcome.
 | 
			
		||||
var createStrategicMergePatchTestCaseData = []byte(`
 | 
			
		||||
testCases:
 | 
			
		||||
  - description: add field to map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
  - description: add field and delete field from map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      name: null
 | 
			
		||||
      value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      value: 1
 | 
			
		||||
  - description: delete field from nested map
 | 
			
		||||
    original:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key1: 1
 | 
			
		||||
        key2: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key2: null
 | 
			
		||||
    modified:
 | 
			
		||||
      simpleMap:
 | 
			
		||||
        key1: 1
 | 
			
		||||
  - description: delete all fields from map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      name: null
 | 
			
		||||
      value: null
 | 
			
		||||
    modified: {}
 | 
			
		||||
  - description: add field and delete all fields from map
 | 
			
		||||
    original:
 | 
			
		||||
      name: 1
 | 
			
		||||
      value: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      other: a
 | 
			
		||||
      name: null
 | 
			
		||||
      value: null
 | 
			
		||||
    modified:
 | 
			
		||||
      other: a
 | 
			
		||||
  - description: replace list of scalars
 | 
			
		||||
    original:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
    patch:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
    modified:
 | 
			
		||||
      nonMergingIntList:
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
  - description: merge lists of scalars
 | 
			
		||||
    original:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 3
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingIntList:
 | 
			
		||||
        - 1
 | 
			
		||||
        - 2
 | 
			
		||||
        - 3
 | 
			
		||||
  - description: merge lists of maps
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 3
 | 
			
		||||
          value: 3
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
        - name: 3
 | 
			
		||||
          value: 3
 | 
			
		||||
  - description: add field to map in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
  - description: add duplicate field to map in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
  - description: replace map field value in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: a
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: a
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
  - description: delete map from merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: delete missing map from merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: add map and delete map from merging list
 | 
			
		||||
    original:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
        - name: 3
 | 
			
		||||
    modified:
 | 
			
		||||
      merginglist:
 | 
			
		||||
        - name: 2
 | 
			
		||||
        - name: 3
 | 
			
		||||
  - description: delete all maps from merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
        - name: 2
 | 
			
		||||
          $patch: delete
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: delete all maps from partially empty merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
        - name: 2
 | 
			
		||||
          $patch: delete
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: delete all maps from empty merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          $patch: delete
 | 
			
		||||
        - name: 2
 | 
			
		||||
          $patch: delete
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: delete field from map in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          value: null
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
          value: 2
 | 
			
		||||
  - description: replace non merging list nested in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          nonMergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: add field to map in merging list nested in merging list
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergingList:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: merge empty merging lists
 | 
			
		||||
    original:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    patch:
 | 
			
		||||
      {}
 | 
			
		||||
    modified:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    current:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
    result:
 | 
			
		||||
      mergingList: []
 | 
			
		||||
  - description: add map to merging list by pointer
 | 
			
		||||
    original:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
    patch:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 2
 | 
			
		||||
    modified:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
        - name: 2
 | 
			
		||||
  - description: add field to map in merging list by pointer
 | 
			
		||||
    original:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
    patch:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
    modified:
 | 
			
		||||
      mergeItemPtr:
 | 
			
		||||
        - name: 1
 | 
			
		||||
          mergeItemPtr:
 | 
			
		||||
            - name: 1
 | 
			
		||||
              value: 1
 | 
			
		||||
            - name: 2
 | 
			
		||||
              value: 2
 | 
			
		||||
        - name: 2
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
func TestStrategicMergePatch(t *testing.T) {
 | 
			
		||||
	tc := StrategicMergePatchTestCases{}
 | 
			
		||||
	err := yaml.Unmarshal(createStrategicMergePatchTestCaseData, &tc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("can't unmarshal test cases: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var e MergeItem
 | 
			
		||||
	for _, c := range tc.TestCases {
 | 
			
		||||
		cOriginal, cPatch, cModified := testCaseToJSONOrFail(t, c)
 | 
			
		||||
 | 
			
		||||
		// Test patch generation
 | 
			
		||||
		patch, err := CreateStrategicMergePatch(cOriginal, cModified, e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("error generating patch: %s:\n%v", err, toYAMLOrError(c.StrategicMergePatchTestCaseData))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Sort the lists that have merged maps, since order is not significant.
 | 
			
		||||
		patch, err = sortMergeListsByName(patch, e)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("error: %s sorting patch object:\n%v", err, patch)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reflect.DeepEqual(patch, cPatch) {
 | 
			
		||||
			t.Errorf("patch generation failed:\n%vgot patch:\n%v", toYAMLOrError(c.StrategicMergePatchTestCaseData), jsonToYAMLOrError(patch))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Test patch application
 | 
			
		||||
		testPatchApplication(t, cOriginal, cPatch, cModified, c.Description)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toYAMLOrError(v interface{}) string {
 | 
			
		||||
	y, err := toYAML(v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err.Error()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return y
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toJSONOrFail(v interface{}, t *testing.T) []byte {
 | 
			
		||||
	theJSON, err := toJSON(v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return theJSON
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func jsonToYAMLOrError(j []byte) string {
 | 
			
		||||
	y, err := jsonToYAML(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err.Error()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(y)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toJSON(v interface{}) []byte {
 | 
			
		||||
	j, err := json.Marshal(v)
 | 
			
		||||
func toYAML(v interface{}) (string, error) {
 | 
			
		||||
	y, err := yaml.Marshal(v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("json marshal failed: %s", spew.Sdump(v)))
 | 
			
		||||
	}
 | 
			
		||||
	return j
 | 
			
		||||
		return "", fmt.Errorf("yaml marshal failed: %v\n%v", err, spew.Sdump(v))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func jsonToYAML(j []byte) []byte {
 | 
			
		||||
	return string(y), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toJSON(v interface{}) ([]byte, error) {
 | 
			
		||||
	j, err := json.Marshal(v)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("json marshal failed: %v\n%v", err, spew.Sdump(v))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return j, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func jsonToYAML(j []byte) ([]byte, error) {
 | 
			
		||||
	y, err := yaml.JSONToYAML(j)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("json to yaml failed: %v", err))
 | 
			
		||||
		return nil, fmt.Errorf("json to yaml failed: %v\n%v", err, j)
 | 
			
		||||
	}
 | 
			
		||||
	return y
 | 
			
		||||
 | 
			
		||||
	return y, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user