예제 #1
0
파일: patch.go 프로젝트: qinguoan/vulcan
func sortMergeListsByNameMap(s map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
	newS := map[string]interface{}{}
	for k, v := range s {
		if k != directiveMarker {
			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
				v, err = sortMergeListsByNameMap(typedV, fieldType)
				if err != nil {
					return nil, err
				}
			} else if typedV, ok := v.([]interface{}); ok {
				if fieldPatchStrategy == mergeDirective {
					var err error
					v, err = sortMergeListsByNameArray(typedV, fieldType.Elem(), fieldPatchMergeKey, true)
					if err != nil {
						return nil, err
					}
				}
			}
		}

		newS[k] = v
	}

	return newS, nil
}
예제 #2
0
파일: patch.go 프로젝트: qinguoan/vulcan
// Returns a (recursive) strategic merge patch that yields modified when applied to original.
func diffMaps(original, modified map[string]interface{}, t reflect.Type, ignoreChangesAndAdditions, ignoreDeletions bool) (map[string]interface{}, error) {
	patch := map[string]interface{}{}
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	for key, modifiedValue := range modified {
		originalValue, ok := original[key]
		if !ok {
			// Key was added, so add to patch
			if !ignoreChangesAndAdditions {
				patch[key] = modifiedValue
			}

			continue
		}

		if key == directiveMarker {
			originalString, ok := originalValue.(string)
			if !ok {
				return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker)
			}

			modifiedString, ok := modifiedValue.(string)
			if !ok {
				return nil, fmt.Errorf("invalid value for special key: %s", directiveMarker)
			}

			if modifiedString != originalString {
				patch[directiveMarker] = modifiedValue
			}

			continue
		}

		if reflect.TypeOf(originalValue) != reflect.TypeOf(modifiedValue) {
			// Types have changed, so add to patch
			if !ignoreChangesAndAdditions {
				patch[key] = modifiedValue
			}

			continue
		}

		// Types are the same, so 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, ignoreChangesAndAdditions, ignoreDeletions)
			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 == mergeDirective {
				patchValue, err := diffLists(originalValueTyped, modifiedValueTyped, fieldType.Elem(), fieldPatchMergeKey, ignoreChangesAndAdditions, ignoreDeletions)
				if err != nil {
					return nil, err
				}

				if len(patchValue) > 0 {
					patch[key] = patchValue
				}

				continue
			}
		}

		if !ignoreChangesAndAdditions {
			if !reflect.DeepEqual(originalValue, modifiedValue) {
				// Values are different, so add to patch
				patch[key] = modifiedValue
			}
		}
	}

	if !ignoreDeletions {
		// Add nils for deleted values
		for key := range original {
			_, found := modified[key]
			if !found {
				patch[key] = nil
			}
		}
	}

	return patch, nil
}
예제 #3
0
파일: patch.go 프로젝트: qinguoan/vulcan
// 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
// golang is highly non-trivial.
func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
	if v, ok := patch[directiveMarker]; ok {
		if v == replaceDirective {
			// If the patch contains "$patch: replace", don't merge it, just use the
			// patch directly. Later on, we can add a single level replace that only
			// affects the map that the $patch is in.
			delete(patch, directiveMarker)
			return patch, nil
		}

		if v == deleteDirective {
			// If the patch contains "$patch: delete", don't merge it, just return
			//  an empty map.
			return map[string]interface{}{}, nil
		}

		return nil, fmt.Errorf(errBadPatchTypeFmt, v, patch)
	}

	// nil is an accepted value for original to simplify logic in other places.
	// If original is nil, replace it with an empty map and then apply the patch.
	if original == nil {
		original = map[string]interface{}{}
	}

	// Start merging the patch into the original.
	for k, patchV := range patch {
		// If the value of this key is null, delete the key if it exists in the
		// original. Otherwise, skip it.
		if patchV == nil {
			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
			}

			if originalType.Kind() == reflect.Map && fieldPatchStrategy != replaceDirective {
				typedOriginal := original[k].(map[string]interface{})
				typedPatch := patchV.(map[string]interface{})
				var err error
				original[k], err = mergeMap(typedOriginal, typedPatch, fieldType)
				if err != nil {
					return nil, err
				}

				continue
			}

			if originalType.Kind() == reflect.Slice && fieldPatchStrategy == mergeDirective {
				elemType := fieldType.Elem()
				typedOriginal := original[k].([]interface{})
				typedPatch := patchV.([]interface{})
				var err error
				original[k], err = mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey)
				if err != nil {
					return nil, err
				}

				continue
			}
		}

		// If originalType and patchType are different OR the types are both
		// maps or slices but we're just supposed to replace them, just take
		// the value from patch.
		original[k] = patchV
	}

	return original, nil
}
예제 #4
0
파일: patch.go 프로젝트: qinguoan/vulcan
// 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
// golang is highly non-trivial.
func mergeMap(original, patch map[string]interface{}, t reflect.Type) (map[string]interface{}, error) {
	// If the map contains "$patch: replace", don't merge it, just use the
	// patch map directly. Later on, can add a non-recursive replace that only
	// affects the map that the $patch is in.
	if v, ok := patch[specialKey]; ok {
		if v == "replace" {
			delete(patch, specialKey)
			return patch, nil
		}
		return nil, fmt.Errorf("unknown patch type found: %s", v)
	}

	// nil is an accepted value for original to simplify logic in other places.
	// If original is nil, create a map so if patch requires us to modify the
	// map, it'll work.
	if original == nil {
		original = map[string]interface{}{}
	}

	// Start merging the patch into the original.
	for k, patchV := range patch {
		// If the value of this key is null, delete the key if it exists in the
		// original. Otherwise, skip it.
		if patchV == nil {
			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.
		// 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{})
				var err error
				original[k], err = mergeMap(typedOriginal, typedPatch, fieldType)
				if err != nil {
					return nil, err
				}
				continue
			}
			if originalType.Kind() == reflect.Slice && fieldPatchStrategy == "merge" {
				elemType := fieldType.Elem()
				typedOriginal := original[k].([]interface{})
				typedPatch := patchV.([]interface{})
				var err error
				original[k], err = mergeSlice(typedOriginal, typedPatch, elemType, fieldPatchMergeKey)
				if err != nil {
					return nil, err
				}
				continue
			}
		}

		// If originalType and patchType are different OR the types are both
		// maps or slices but we're just supposed to replace them, just take
		// the value from patch.
		original[k] = patchV
	}

	return original, nil
}