1
16
17 package builder
18
19 import (
20 "encoding/base64"
21 "encoding/json"
22 "fmt"
23 "hash/fnv"
24 "sort"
25 "strconv"
26 "strings"
27
28 "k8s.io/kube-openapi/pkg/validation/spec"
29 )
30
31
32
33
34
35 func deduplicateParameters(sp *spec.Swagger) (*spec.Swagger, error) {
36 names, parameters, err := collectSharedParameters(sp)
37 if err != nil {
38 return nil, err
39 }
40
41 if sp.Parameters != nil {
42 return nil, fmt.Errorf("shared parameters already exist")
43 }
44
45 clone := *sp
46 clone.Parameters = parameters
47 return replaceSharedParameters(names, &clone)
48 }
49
50
51
52
53
54 func collectSharedParameters(sp *spec.Swagger) (namesByJSON map[string]string, ret map[string]spec.Parameter, err error) {
55 if sp == nil || sp.Paths == nil {
56 return nil, nil, nil
57 }
58
59 countsByJSON := map[string]int{}
60 shared := map[string]spec.Parameter{}
61 var keys []string
62
63 collect := func(p *spec.Parameter) error {
64 if (p.In == "query" || p.In == "path") && p.Name == "name" {
65 return nil
66 }
67 if p.In == "query" && p.Name == "fieldValidation" {
68 return nil
69 }
70 if p.In == "query" && p.Name == "dryRun" {
71 return nil
72 }
73 if p.Schema != nil && p.In == "body" && p.Name == "body" && !strings.HasPrefix(p.Schema.Ref.String(), "#/definitions/io.k8s.apimachinery") {
74 return nil
75 }
76
77 bs, err := json.Marshal(p)
78 if err != nil {
79 return err
80 }
81
82 k := string(bs)
83 countsByJSON[k]++
84 if count := countsByJSON[k]; count == 1 {
85 shared[k] = *p
86 keys = append(keys, k)
87 }
88
89 return nil
90 }
91
92 for _, path := range sp.Paths.Paths {
93
94 for _, op := range operations(&path) {
95 if op == nil {
96 continue
97 }
98 for _, p := range op.Parameters {
99 if p.Ref.String() != "" {
100
101 continue
102 }
103 if err := collect(&p); err != nil {
104 return nil, nil, err
105 }
106 }
107 }
108
109
110 for _, p := range path.Parameters {
111 if p.Ref.String() != "" {
112 continue
113 }
114 if err := collect(&p); err != nil {
115 return nil, nil, err
116 }
117 }
118 }
119
120
121 sort.Strings(keys)
122 ret = map[string]spec.Parameter{}
123 namesByJSON = map[string]string{}
124 for _, k := range keys {
125 name := shared[k].Name
126 if name == "" {
127
128 name = "param"
129 }
130 name += "-" + base64Hash(k)
131 i := 0
132 for {
133 if _, ok := ret[name]; !ok {
134 ret[name] = shared[k]
135 namesByJSON[k] = name
136 break
137 }
138 i++
139 name = shared[k].Name + "-" + strconv.Itoa(i)
140 }
141 }
142
143 return namesByJSON, ret, nil
144 }
145
146 func operations(path *spec.PathItem) []*spec.Operation {
147 return []*spec.Operation{path.Get, path.Put, path.Post, path.Delete, path.Options, path.Head, path.Patch}
148 }
149
150 func base64Hash(s string) string {
151 hash := fnv.New64()
152 hash.Write([]byte(s))
153 return base64.URLEncoding.EncodeToString(hash.Sum(make([]byte, 0, 8))[:6])
154 }
155
156 func replaceSharedParameters(sharedParameterNamesByJSON map[string]string, sp *spec.Swagger) (*spec.Swagger, error) {
157 if sp == nil || sp.Paths == nil {
158 return sp, nil
159 }
160
161 ret := sp
162
163 firstPathChange := true
164 for k, path := range sp.Paths.Paths {
165 pathChanged := false
166
167
168 for _, op := range []**spec.Operation{&path.Get, &path.Put, &path.Post, &path.Delete, &path.Options, &path.Head, &path.Patch} {
169 if *op == nil {
170 continue
171 }
172
173 firstParamChange := true
174 for i := range (*op).Parameters {
175 p := (*op).Parameters[i]
176
177 if p.Ref.String() != "" {
178
179 continue
180 }
181
182 bs, err := json.Marshal(p)
183 if err != nil {
184 return nil, err
185 }
186
187 if name, ok := sharedParameterNamesByJSON[string(bs)]; ok {
188 if firstParamChange {
189 orig := *op
190 *op = &spec.Operation{}
191 **op = *orig
192 (*op).Parameters = make([]spec.Parameter, len(orig.Parameters))
193 copy((*op).Parameters, orig.Parameters)
194 firstParamChange = false
195 }
196
197 (*op).Parameters[i] = spec.Parameter{
198 Refable: spec.Refable{
199 Ref: spec.MustCreateRef("#/parameters/" + name),
200 },
201 }
202 pathChanged = true
203 }
204 }
205 }
206
207
208 firstParamChange := true
209 for i := range path.Parameters {
210 p := path.Parameters[i]
211
212 if p.Ref.String() != "" {
213
214 continue
215 }
216
217 bs, err := json.Marshal(p)
218 if err != nil {
219 return nil, err
220 }
221
222 if name, ok := sharedParameterNamesByJSON[string(bs)]; ok {
223 if firstParamChange {
224 orig := path.Parameters
225 path.Parameters = make([]spec.Parameter, len(orig))
226 copy(path.Parameters, orig)
227 firstParamChange = false
228 }
229
230 path.Parameters[i] = spec.Parameter{
231 Refable: spec.Refable{
232 Ref: spec.MustCreateRef("#/parameters/" + name),
233 },
234 }
235 pathChanged = true
236 }
237 }
238
239 if pathChanged {
240 if firstPathChange {
241 clone := *sp
242 ret = &clone
243
244 pathsClone := *ret.Paths
245 ret.Paths = &pathsClone
246
247 ret.Paths.Paths = make(map[string]spec.PathItem, len(sp.Paths.Paths))
248 for k, v := range sp.Paths.Paths {
249 ret.Paths.Paths[k] = v
250 }
251
252 firstPathChange = false
253 }
254 ret.Paths.Paths[k] = path
255 }
256 }
257
258 return ret, nil
259 }
260
View as plain text