/* Copyright 2017 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package schemamutation import ( "k8s.io/kube-openapi/pkg/validation/spec" ) // Walker runs callback functions on all references of an OpenAPI spec, // replacing the values when visiting corresponding types. 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 } // ReplaceReferences rewrites the references without mutating the input. // The output might share data with the input. func ReplaceReferences(walkRef func(ref *spec.Ref) *spec.Ref, sp *spec.Swagger) *spec.Swagger { walker := &Walker{RefCallback: walkRef, SchemaCallback: SchemaCallBackNoop} return walker.WalkRoot(sp) } 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(¶m.Ref); r != ¶m.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(¶m.Items.Ref); r != ¶m.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(¶ms[i]); s != ¶ms[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 } 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 }