Files
kubesphere/kube/pkg/openapi/merge/merge.go
KubeSphere CI Bot 447a51f08b feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
2024-09-06 11:05:52 +08:00

921 lines
22 KiB
Go

package merge
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/go-openapi/spec"
"k8s.io/kube-openapi/pkg/util"
)
const gvkKey = "x-kubesphere-group-version-kind"
// usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values.
func usedDefinitionForSpec(root *spec.Swagger) map[string]bool {
usedDefinitions := map[string]bool{}
walkOnAllReferences(func(ref *spec.Ref) {
if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
usedDefinitions[refStr[len(definitionPrefix):]] = true
}
}, root)
return usedDefinitions
}
// FilterSpecByPathsWithoutSideEffects removes unnecessary paths and definitions used by those paths.
// i.e. if a Path removed by this function, all definitions used by it and not used
// anywhere else will also be removed.
// It does not modify the input, but the output shares data structures with the input.
func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []string) *spec.Swagger {
if sp.Paths == nil {
return sp
}
// Walk all references to find all used definitions. This function
// want to only deal with unused definitions resulted from filtering paths.
// Thus a definition will be removed only if it has been used before but
// it is unused because of a path prune.
initialUsedDefinitions := usedDefinitionForSpec(sp)
// First remove unwanted paths
prefixes := util.NewTrie(keepPathPrefixes)
ret := *sp
ret.Paths = &spec.Paths{
VendorExtensible: sp.Paths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range sp.Paths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
ret.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := usedDefinitionForSpec(&ret)
// Remove unused definitions
ret.Definitions = spec.Definitions{}
for k, v := range sp.Definitions {
if usedDefinitions[k] || !initialUsedDefinitions[k] {
ret.Definitions[k] = v
}
}
return &ret
}
// renameDefinitions renames definition references, without mutating the input.
// The output might share data structures with the input.
func renameDefinitions(s *spec.Swagger, renames map[string]string) *spec.Swagger {
refRenames := make(map[string]string, len(renames))
foundOne := false
for k, v := range renames {
refRenames[definitionPrefix+k] = definitionPrefix + v
if _, ok := s.Definitions[k]; ok {
foundOne = true
}
}
if !foundOne {
return s
}
ret := &spec.Swagger{}
*ret = *s
ret = ReplaceReferences(func(ref *spec.Ref) *spec.Ref {
refName := ref.String()
if newRef, found := refRenames[refName]; found {
ret := spec.MustCreateRef(newRef)
return &ret
}
return ref
}, ret)
renamedDefinitions := make(spec.Definitions, len(ret.Definitions))
for k, v := range ret.Definitions {
if newRef, found := renames[k]; found {
k = newRef
}
renamedDefinitions[k] = v
}
ret.Definitions = renamedDefinitions
return ret
}
// renameParameters renames parameter references, without mutating the input.
// The output might share data structures with the input.
func renameParameters(s *spec.Swagger, renames map[string]string) *spec.Swagger {
refRenames := make(map[string]string, len(renames))
foundOne := false
for k, v := range renames {
refRenames[parameterPrefix+k] = parameterPrefix + v
if _, ok := s.Parameters[k]; ok {
foundOne = true
}
}
if !foundOne {
return s
}
ret := &spec.Swagger{}
*ret = *s
ret = ReplaceReferences(func(ref *spec.Ref) *spec.Ref {
refName := ref.String()
if newRef, found := refRenames[refName]; found {
ret := spec.MustCreateRef(newRef)
return &ret
}
return ref
}, ret)
renamed := make(map[string]spec.Parameter, len(ret.Parameters))
for k, v := range ret.Parameters {
if newRef, found := renames[k]; found {
k = newRef
}
renamed[k] = v
}
ret.Parameters = renamed
return ret
}
// MergeSpecsIgnorePathConflictRenamingDefinitionsAndParameters is the same as
// MergeSpecs except it will ignore any path conflicts by keeping the paths of
// destination. It will rename definition and parameter conflicts.
func MergeSpecsIgnorePathConflictRenamingDefinitionsAndParameters(dest, source *spec.Swagger) error {
return mergeSpecs(dest, source, true, true, true)
}
// mergeSpecs merges source into dest while resolving conflicts.
// The source is not mutated.
func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, renameParameterConflicts, ignorePathConflicts bool) (err error) {
// Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if source.Paths == nil {
// When a source spec does not have any path, that means none of the definitions
// are used thus we should not do anything
return nil
}
if dest.Paths == nil {
dest.Paths = &spec.Paths{}
}
if ignorePathConflicts {
keepPaths := []string{}
hasConflictingPath := false
for k := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; !found {
keepPaths = append(keepPaths, k)
} else {
hasConflictingPath = true
}
}
if len(keepPaths) == 0 {
// There is nothing to merge. All paths are conflicting.
return nil
}
if hasConflictingPath {
source = FilterSpecByPathsWithoutSideEffects(source, keepPaths)
}
}
// Check for model conflicts and rename to make definitions conflict-free (modulo different GVKs)
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
renames := map[string]string{}
DEFINITIONLOOP:
for k, v := range source.Definitions {
existing, found := dest.Definitions[k]
if !found || deepEqualDefinitionsModuloGVKs(&existing, &v) {
// skip for now, we copy them after the rename loop
continue
}
if !renameModelConflicts {
return fmt.Errorf("model name conflict in merging OpenAPI spec: %s", k)
}
// Reuse previously renamed model if one exists
var newName string
i := 1
for found {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
existing, found = dest.Definitions[newName]
if found && deepEqualDefinitionsModuloGVKs(&existing, &v) {
renames[k] = newName
continue DEFINITIONLOOP
}
}
_, foundInSource := source.Definitions[newName]
for usedNames[newName] || foundInSource {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
_, foundInSource = source.Definitions[newName]
}
renames[k] = newName
usedNames[newName] = true
}
source = renameDefinitions(source, renames)
// Check for parameter conflicts and rename to make parameters conflict-free
usedNames = map[string]bool{}
for k := range dest.Parameters {
usedNames[k] = true
}
renames = map[string]string{}
PARAMETERLOOP:
for k, p := range source.Parameters {
existing, found := dest.Parameters[k]
if !found || reflect.DeepEqual(&existing, &p) {
// skip for now, we copy them after the rename loop
continue
}
if !renameParameterConflicts {
return fmt.Errorf("parameter name conflict in merging OpenAPI spec: %s", k)
}
// Reuse previously renamed parameter if one exists
var newName string
i := 1
for found {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
existing, found = dest.Parameters[newName]
if found && reflect.DeepEqual(&existing, &p) {
renames[k] = newName
continue PARAMETERLOOP
}
}
_, foundInSource := source.Parameters[newName]
for usedNames[newName] || foundInSource {
i++
newName = fmt.Sprintf("%s_v%d", k, i)
_, foundInSource = source.Parameters[newName]
}
renames[k] = newName
usedNames[newName] = true
}
source = renameParameters(source, renames)
// Now without conflict (modulo different GVKs), copy definitions to dest
for k, v := range source.Definitions {
if existing, found := dest.Definitions[k]; !found {
if dest.Definitions == nil {
dest.Definitions = make(spec.Definitions, len(source.Definitions))
}
dest.Definitions[k] = v
} else if merged, changed, err := mergedGVKs(&existing, &v); err != nil {
return err
} else if changed {
existing.Extensions[gvkKey] = merged
}
}
// Now without conflict, copy parameters to dest
for k, v := range source.Parameters {
if _, found := dest.Parameters[k]; !found {
if dest.Parameters == nil {
dest.Parameters = make(map[string]spec.Parameter, len(source.Parameters))
}
dest.Parameters[k] = v
}
}
// Check for path conflicts
for k, v := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; found {
return fmt.Errorf("unable to merge: duplicated path %s", k)
}
// PathItem may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
if dest.Paths.Paths == nil {
dest.Paths.Paths = map[string]spec.PathItem{}
}
dest.Paths.Paths[k] = v
}
return nil
}
// deepEqualDefinitionsModuloGVKs compares s1 and s2, but ignores the x-kubernetes-group-version-kind extension.
func deepEqualDefinitionsModuloGVKs(s1, s2 *spec.Schema) bool {
if s1 == nil {
return s2 == nil
} else if s2 == nil {
return false
}
if !reflect.DeepEqual(s1.Extensions, s2.Extensions) {
for k, v := range s1.Extensions {
if k == gvkKey {
continue
}
if !reflect.DeepEqual(v, s2.Extensions[k]) {
return false
}
}
len1 := len(s1.Extensions)
len2 := len(s2.Extensions)
if _, found := s1.Extensions[gvkKey]; found {
len1--
}
if _, found := s2.Extensions[gvkKey]; found {
len2--
}
if len1 != len2 {
return false
}
if s1.Extensions != nil {
shallowCopy := *s1
s1 = &shallowCopy
s1.Extensions = nil
}
if s2.Extensions != nil {
shallowCopy := *s2
s2 = &shallowCopy
s2.Extensions = nil
}
}
return reflect.DeepEqual(s1, s2)
}
// mergedGVKs merges the x-kubernetes-group-version-kind slices and returns the result, and whether
// s1's x-kubernetes-group-version-kind slice was changed at all.
func mergedGVKs(s1, s2 *spec.Schema) (interface{}, bool, error) {
gvk1, found1 := s1.Extensions[gvkKey]
gvk2, found2 := s2.Extensions[gvkKey]
if !found1 {
return gvk2, found2, nil
}
if !found2 {
return gvk1, false, nil
}
slice1, ok := gvk1.([]interface{})
if !ok {
return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice1)
}
slice2, ok := gvk2.([]interface{})
if !ok {
return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice2)
}
ret := make([]interface{}, len(slice1), len(slice1)+len(slice2))
keys := make([]string, 0, len(slice1)+len(slice2))
copy(ret, slice1)
seen := make(map[string]bool, len(slice1))
for _, x := range slice1 {
gvk, ok := x.(map[string]interface{})
if !ok {
return nil, false, fmt.Errorf(`expected {"group": <group>, "kind": <kind>, "version": <version>}, got: %#v`, x)
}
k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"])
keys = append(keys, k)
seen[k] = true
}
changed := false
for _, x := range slice2 {
gvk, ok := x.(map[string]interface{})
if !ok {
return nil, false, fmt.Errorf(`expected {"group": <group>, "kind": <kind>, "version": <version>}, got: %#v`, x)
}
k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"])
if seen[k] {
continue
}
ret = append(ret, x)
keys = append(keys, k)
changed = true
}
if changed {
sort.Sort(byKeys{ret, keys})
}
return ret, changed, nil
}
type byKeys struct {
values []interface{}
keys []string
}
func (b byKeys) Len() int {
return len(b.values)
}
func (b byKeys) Less(i, j int) bool {
return b.keys[i] < b.keys[j]
}
func (b byKeys) Swap(i, j int) {
b.values[i], b.values[j] = b.values[j], b.values[i]
b.keys[i], b.keys[j] = b.keys[j], b.keys[i]
}
func ReplaceReferences(walkRef func(ref *spec.Ref) *spec.Ref, sp *spec.Swagger) *spec.Swagger {
walker := &Walker{RefCallback: walkRef, SchemaCallback: SchemaCallBackNoop}
return walker.WalkRoot(sp)
}
type Walker struct {
// SchemaCallback will be called on each schema, taking the original schema,
// and before any other callbacks of the Walker.
// If the schema needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
SchemaCallback func(schema *spec.Schema) *spec.Schema
// RefCallback will be called on each ref.
// If the ref needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
RefCallback func(ref *spec.Ref) *spec.Ref
}
type SchemaCallbackFunc func(schema *spec.Schema) *spec.Schema
type RefCallbackFunc func(ref *spec.Ref) *spec.Ref
var SchemaCallBackNoop SchemaCallbackFunc = func(schema *spec.Schema) *spec.Schema {
return schema
}
var RefCallbackNoop RefCallbackFunc = func(ref *spec.Ref) *spec.Ref {
return ref
}
func (w *Walker) WalkRoot(swagger *spec.Swagger) *spec.Swagger {
if swagger == nil {
return nil
}
orig := swagger
cloned := false
clone := func() {
if !cloned {
cloned = true
swagger = &spec.Swagger{}
*swagger = *orig
}
}
parametersCloned := false
for k, v := range swagger.Parameters {
if p := w.walkParameter(&v); p != &v {
if !parametersCloned {
parametersCloned = true
clone()
swagger.Parameters = make(map[string]spec.Parameter, len(orig.Parameters))
for k2, v2 := range orig.Parameters {
swagger.Parameters[k2] = v2
}
}
swagger.Parameters[k] = *p
}
}
responsesCloned := false
for k, v := range swagger.Responses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
swagger.Responses = make(map[string]spec.Response, len(orig.Responses))
for k2, v2 := range orig.Responses {
swagger.Responses[k2] = v2
}
}
swagger.Responses[k] = *r
}
}
definitionsCloned := false
for k, v := range swagger.Definitions {
if s := w.WalkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
swagger.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
swagger.Definitions[k2] = v2
}
}
swagger.Definitions[k] = *s
}
}
if swagger.Paths != nil {
if p := w.walkPaths(swagger.Paths); p != swagger.Paths {
clone()
swagger.Paths = p
}
}
return swagger
}
func (w *Walker) WalkSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
orig := schema
clone := func() {
if orig == schema {
schema = &spec.Schema{}
*schema = *orig
}
}
// Always run callback on the whole schema first
// so that SchemaCallback can take the original schema as input.
schema = w.SchemaCallback(schema)
if r := w.RefCallback(&schema.Ref); r != &schema.Ref {
clone()
schema.Ref = *r
}
definitionsCloned := false
for k, v := range schema.Definitions {
if s := w.WalkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
schema.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
schema.Definitions[k2] = v2
}
}
schema.Definitions[k] = *s
}
}
propertiesCloned := false
for k, v := range schema.Properties {
if s := w.WalkSchema(&v); s != &v {
if !propertiesCloned {
propertiesCloned = true
clone()
schema.Properties = make(map[string]spec.Schema, len(orig.Properties))
for k2, v2 := range orig.Properties {
schema.Properties[k2] = v2
}
}
schema.Properties[k] = *s
}
}
patternPropertiesCloned := false
for k, v := range schema.PatternProperties {
if s := w.WalkSchema(&v); s != &v {
if !patternPropertiesCloned {
patternPropertiesCloned = true
clone()
schema.PatternProperties = make(map[string]spec.Schema, len(orig.PatternProperties))
for k2, v2 := range orig.PatternProperties {
schema.PatternProperties[k2] = v2
}
}
schema.PatternProperties[k] = *s
}
}
allOfCloned := false
for i := range schema.AllOf {
if s := w.WalkSchema(&schema.AllOf[i]); s != &schema.AllOf[i] {
if !allOfCloned {
allOfCloned = true
clone()
schema.AllOf = make([]spec.Schema, len(orig.AllOf))
copy(schema.AllOf, orig.AllOf)
}
schema.AllOf[i] = *s
}
}
anyOfCloned := false
for i := range schema.AnyOf {
if s := w.WalkSchema(&schema.AnyOf[i]); s != &schema.AnyOf[i] {
if !anyOfCloned {
anyOfCloned = true
clone()
schema.AnyOf = make([]spec.Schema, len(orig.AnyOf))
copy(schema.AnyOf, orig.AnyOf)
}
schema.AnyOf[i] = *s
}
}
oneOfCloned := false
for i := range schema.OneOf {
if s := w.WalkSchema(&schema.OneOf[i]); s != &schema.OneOf[i] {
if !oneOfCloned {
oneOfCloned = true
clone()
schema.OneOf = make([]spec.Schema, len(orig.OneOf))
copy(schema.OneOf, orig.OneOf)
}
schema.OneOf[i] = *s
}
}
if schema.Not != nil {
if s := w.WalkSchema(schema.Not); s != schema.Not {
clone()
schema.Not = s
}
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
if s := w.WalkSchema(schema.AdditionalProperties.Schema); s != schema.AdditionalProperties.Schema {
clone()
schema.AdditionalProperties = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalProperties.Allows}
}
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
if s := w.WalkSchema(schema.AdditionalItems.Schema); s != schema.AdditionalItems.Schema {
clone()
schema.AdditionalItems = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalItems.Allows}
}
}
if schema.Items != nil {
if schema.Items.Schema != nil {
if s := w.WalkSchema(schema.Items.Schema); s != schema.Items.Schema {
clone()
schema.Items = &spec.SchemaOrArray{Schema: s}
}
} else {
itemsCloned := false
for i := range schema.Items.Schemas {
if s := w.WalkSchema(&schema.Items.Schemas[i]); s != &schema.Items.Schemas[i] {
if !itemsCloned {
clone()
schema.Items = &spec.SchemaOrArray{
Schemas: make([]spec.Schema, len(orig.Items.Schemas)),
}
itemsCloned = true
copy(schema.Items.Schemas, orig.Items.Schemas)
}
schema.Items.Schemas[i] = *s
}
}
}
}
return schema
}
func (w *Walker) walkParameter(param *spec.Parameter) *spec.Parameter {
if param == nil {
return nil
}
orig := param
cloned := false
clone := func() {
if !cloned {
cloned = true
param = &spec.Parameter{}
*param = *orig
}
}
if r := w.RefCallback(&param.Ref); r != &param.Ref {
clone()
param.Ref = *r
}
if s := w.WalkSchema(param.Schema); s != param.Schema {
clone()
param.Schema = s
}
if param.Items != nil {
if r := w.RefCallback(&param.Items.Ref); r != &param.Items.Ref {
param.Items.Ref = *r
}
}
return param
}
func (w *Walker) walkParameters(params []spec.Parameter) ([]spec.Parameter, bool) {
if params == nil {
return nil, false
}
orig := params
cloned := false
clone := func() {
if !cloned {
cloned = true
params = make([]spec.Parameter, len(params))
copy(params, orig)
}
}
for i := range params {
if s := w.walkParameter(&params[i]); s != &params[i] {
clone()
params[i] = *s
}
}
return params, cloned
}
func (w *Walker) walkResponse(resp *spec.Response) *spec.Response {
if resp == nil {
return nil
}
orig := resp
cloned := false
clone := func() {
if !cloned {
cloned = true
resp = &spec.Response{}
*resp = *orig
}
}
if r := w.RefCallback(&resp.Ref); r != &resp.Ref {
clone()
resp.Ref = *r
}
if s := w.WalkSchema(resp.Schema); s != resp.Schema {
clone()
resp.Schema = s
}
return resp
}
func (w *Walker) walkResponses(resps *spec.Responses) *spec.Responses {
if resps == nil {
return nil
}
orig := resps
cloned := false
clone := func() {
if !cloned {
cloned = true
resps = &spec.Responses{}
*resps = *orig
}
}
if r := w.walkResponse(resps.ResponsesProps.Default); r != resps.ResponsesProps.Default {
clone()
resps.Default = r
}
responsesCloned := false
for k, v := range resps.ResponsesProps.StatusCodeResponses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
resps.ResponsesProps.StatusCodeResponses = make(map[int]spec.Response, len(orig.StatusCodeResponses))
for k2, v2 := range orig.StatusCodeResponses {
resps.ResponsesProps.StatusCodeResponses[k2] = v2
}
}
resps.ResponsesProps.StatusCodeResponses[k] = *r
}
}
return resps
}
func (w *Walker) walkOperation(op *spec.Operation) *spec.Operation {
if op == nil {
return nil
}
orig := op
cloned := false
clone := func() {
if !cloned {
cloned = true
op = &spec.Operation{}
*op = *orig
}
}
parametersCloned := false
for i := range op.Parameters {
if s := w.walkParameter(&op.Parameters[i]); s != &op.Parameters[i] {
if !parametersCloned {
parametersCloned = true
clone()
op.Parameters = make([]spec.Parameter, len(orig.Parameters))
copy(op.Parameters, orig.Parameters)
}
op.Parameters[i] = *s
}
}
if r := w.walkResponses(op.Responses); r != op.Responses {
clone()
op.Responses = r
}
return op
}
func (w *Walker) walkPathItem(pathItem *spec.PathItem) *spec.PathItem {
if pathItem == nil {
return nil
}
orig := pathItem
cloned := false
clone := func() {
if !cloned {
cloned = true
pathItem = &spec.PathItem{}
*pathItem = *orig
}
}
if p, changed := w.walkParameters(pathItem.Parameters); changed {
clone()
pathItem.Parameters = p
}
if op := w.walkOperation(pathItem.Get); op != pathItem.Get {
clone()
pathItem.Get = op
}
if op := w.walkOperation(pathItem.Head); op != pathItem.Head {
clone()
pathItem.Head = op
}
if op := w.walkOperation(pathItem.Delete); op != pathItem.Delete {
clone()
pathItem.Delete = op
}
if op := w.walkOperation(pathItem.Options); op != pathItem.Options {
clone()
pathItem.Options = op
}
if op := w.walkOperation(pathItem.Patch); op != pathItem.Patch {
clone()
pathItem.Patch = op
}
if op := w.walkOperation(pathItem.Post); op != pathItem.Post {
clone()
pathItem.Post = op
}
if op := w.walkOperation(pathItem.Put); op != pathItem.Put {
clone()
pathItem.Put = op
}
return pathItem
}
func (w *Walker) walkPaths(paths *spec.Paths) *spec.Paths {
if paths == nil {
return nil
}
orig := paths
cloned := false
clone := func() {
if !cloned {
cloned = true
paths = &spec.Paths{}
*paths = *orig
}
}
pathsCloned := false
for k, v := range paths.Paths {
if p := w.walkPathItem(&v); p != &v {
if !pathsCloned {
pathsCloned = true
clone()
paths.Paths = make(map[string]spec.PathItem, len(orig.Paths))
for k2, v2 := range orig.Paths {
paths.Paths[k2] = v2
}
}
paths.Paths[k] = *p
}
}
return paths
}