1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package aggregator
18
19import (
20	"strings"
21
22	"github.com/go-openapi/spec"
23)
24
25const (
26	definitionPrefix = "#/definitions/"
27)
28
29// Run a readonlyReferenceWalker method on all references of an OpenAPI spec
30type readonlyReferenceWalker struct {
31	// walkRefCallback will be called on each reference. The input will never be nil.
32	walkRefCallback func(ref *spec.Ref)
33
34	// The spec to walk through.
35	root *spec.Swagger
36}
37
38// walkOnAllReferences recursively walks on all references, while following references into definitions.
39// it calls walkRef on each found reference.
40func walkOnAllReferences(walkRef func(ref *spec.Ref), root *spec.Swagger) {
41	alreadyVisited := map[string]bool{}
42
43	walker := &readonlyReferenceWalker{
44		root: root,
45	}
46	walker.walkRefCallback = func(ref *spec.Ref) {
47		walkRef(ref)
48
49		refStr := ref.String()
50		if refStr == "" || !strings.HasPrefix(refStr, definitionPrefix) {
51			return
52		}
53		defName := refStr[len(definitionPrefix):]
54
55		if _, found := root.Definitions[defName]; found && !alreadyVisited[refStr] {
56			alreadyVisited[refStr] = true
57			def := root.Definitions[defName]
58			walker.walkSchema(&def)
59		}
60	}
61	walker.Start()
62}
63
64func (s *readonlyReferenceWalker) walkSchema(schema *spec.Schema) {
65	if schema == nil {
66		return
67	}
68	s.walkRefCallback(&schema.Ref)
69	var v *spec.Schema
70	if len(schema.Definitions)+len(schema.Properties)+len(schema.PatternProperties) > 0 {
71		v = &spec.Schema{}
72	}
73	for k := range schema.Definitions {
74		*v = schema.Definitions[k]
75		s.walkSchema(v)
76	}
77	for k := range schema.Properties {
78		*v = schema.Properties[k]
79		s.walkSchema(v)
80	}
81	for k := range schema.PatternProperties {
82		*v = schema.PatternProperties[k]
83		s.walkSchema(v)
84	}
85	for i := range schema.AllOf {
86		s.walkSchema(&schema.AllOf[i])
87	}
88	for i := range schema.AnyOf {
89		s.walkSchema(&schema.AnyOf[i])
90	}
91	for i := range schema.OneOf {
92		s.walkSchema(&schema.OneOf[i])
93	}
94	if schema.Not != nil {
95		s.walkSchema(schema.Not)
96	}
97	if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
98		s.walkSchema(schema.AdditionalProperties.Schema)
99	}
100	if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
101		s.walkSchema(schema.AdditionalItems.Schema)
102	}
103	if schema.Items != nil {
104		if schema.Items.Schema != nil {
105			s.walkSchema(schema.Items.Schema)
106		}
107		for i := range schema.Items.Schemas {
108			s.walkSchema(&schema.Items.Schemas[i])
109		}
110	}
111}
112
113func (s *readonlyReferenceWalker) walkParams(params []spec.Parameter) {
114	if params == nil {
115		return
116	}
117	for _, param := range params {
118		s.walkRefCallback(&param.Ref)
119		s.walkSchema(param.Schema)
120		if param.Items != nil {
121			s.walkRefCallback(&param.Items.Ref)
122		}
123	}
124}
125
126func (s *readonlyReferenceWalker) walkResponse(resp *spec.Response) {
127	if resp == nil {
128		return
129	}
130	s.walkRefCallback(&resp.Ref)
131	s.walkSchema(resp.Schema)
132}
133
134func (s *readonlyReferenceWalker) walkOperation(op *spec.Operation) {
135	if op == nil {
136		return
137	}
138	s.walkParams(op.Parameters)
139	if op.Responses == nil {
140		return
141	}
142	s.walkResponse(op.Responses.Default)
143	for _, r := range op.Responses.StatusCodeResponses {
144		s.walkResponse(&r)
145	}
146}
147
148func (s *readonlyReferenceWalker) Start() {
149	if s.root.Paths == nil {
150		return
151	}
152	for _, pathItem := range s.root.Paths.Paths {
153		s.walkParams(pathItem.Parameters)
154		s.walkOperation(pathItem.Delete)
155		s.walkOperation(pathItem.Get)
156		s.walkOperation(pathItem.Head)
157		s.walkOperation(pathItem.Options)
158		s.walkOperation(pathItem.Patch)
159		s.walkOperation(pathItem.Post)
160		s.walkOperation(pathItem.Put)
161	}
162}
163