1 package replace
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/url"
7 "os"
8 "path"
9 "strconv"
10
11 "github.com/go-openapi/analysis/internal/debug"
12 "github.com/go-openapi/jsonpointer"
13 "github.com/go-openapi/spec"
14 )
15
16 const definitionsPath = "#/definitions"
17
18 var debugLog = debug.GetLogger("analysis/flatten/replace", os.Getenv("SWAGGER_DEBUG") != "")
19
20
21 func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
22 debugLog("rewriting schema to ref for %s with %s", key, ref.String())
23 _, value, err := getPointerFromKey(sp, key)
24 if err != nil {
25 return err
26 }
27
28 switch refable := value.(type) {
29 case *spec.Schema:
30 return rewriteParentRef(sp, key, ref)
31
32 case spec.Schema:
33 return rewriteParentRef(sp, key, ref)
34
35 case *spec.SchemaOrArray:
36 if refable.Schema != nil {
37 refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
38 }
39
40 case *spec.SchemaOrBool:
41 if refable.Schema != nil {
42 refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
43 }
44 case map[string]interface{}:
45 return rewriteParentRef(sp, key, ref)
46 default:
47 return fmt.Errorf("no schema with ref found at %s for %T", key, value)
48 }
49
50 return nil
51 }
52
53 func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
54 parent, entry, pvalue, err := getParentFromKey(sp, key)
55 if err != nil {
56 return err
57 }
58
59 debugLog("rewriting holder for %T", pvalue)
60 switch container := pvalue.(type) {
61 case spec.Response:
62 if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
63 return err
64 }
65
66 case *spec.Response:
67 container.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
68
69 case *spec.Responses:
70 statusCode, err := strconv.Atoi(entry)
71 if err != nil {
72 return fmt.Errorf("%s not a number: %w", key[1:], err)
73 }
74 resp := container.StatusCodeResponses[statusCode]
75 resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
76 container.StatusCodeResponses[statusCode] = resp
77
78 case map[string]spec.Response:
79 resp := container[entry]
80 resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
81 container[entry] = resp
82
83 case spec.Parameter:
84 if err := rewriteParentRef(sp, "#"+parent, ref); err != nil {
85 return err
86 }
87
88 case map[string]spec.Parameter:
89 param := container[entry]
90 param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
91 container[entry] = param
92
93 case []spec.Parameter:
94 idx, err := strconv.Atoi(entry)
95 if err != nil {
96 return fmt.Errorf("%s not a number: %w", key[1:], err)
97 }
98 param := container[idx]
99 param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
100 container[idx] = param
101
102 case spec.Definitions:
103 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
104
105 case map[string]spec.Schema:
106 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
107
108 case []spec.Schema:
109 idx, err := strconv.Atoi(entry)
110 if err != nil {
111 return fmt.Errorf("%s not a number: %w", key[1:], err)
112 }
113 container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
114
115 case *spec.SchemaOrArray:
116
117 idx, err := strconv.Atoi(entry)
118 if err != nil {
119 return fmt.Errorf("%s not a number: %w", key[1:], err)
120 }
121 container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
122
123 case spec.SchemaProperties:
124 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
125
126 case *interface{}:
127 *container = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
128
129
130
131 default:
132 return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
133 }
134
135 return nil
136 }
137
138
139 func getPointerFromKey(sp interface{}, key string) (string, interface{}, error) {
140 switch sp.(type) {
141 case *spec.Schema:
142 case *spec.Swagger:
143 default:
144 panic("unexpected type used in getPointerFromKey")
145 }
146 if key == "#/" {
147 return "", sp, nil
148 }
149
150 pth, _ := url.PathUnescape(key[1:])
151 ptr, err := jsonpointer.New(pth)
152 if err != nil {
153 return "", nil, err
154 }
155
156 value, _, err := ptr.Get(sp)
157 if err != nil {
158 debugLog("error when getting key: %s with path: %s", key, pth)
159
160 return "", nil, err
161 }
162
163 return pth, value, nil
164 }
165
166
167 func getParentFromKey(sp interface{}, key string) (string, string, interface{}, error) {
168 switch sp.(type) {
169 case *spec.Schema:
170 case *spec.Swagger:
171 default:
172 panic("unexpected type used in getPointerFromKey")
173 }
174
175 pth, _ := url.PathUnescape(key[1:])
176
177 parent, entry := path.Dir(pth), path.Base(pth)
178 debugLog("getting schema holder at: %s, with entry: %s", parent, entry)
179
180 pptr, err := jsonpointer.New(parent)
181 if err != nil {
182 return "", "", nil, err
183 }
184 pvalue, _, err := pptr.Get(sp)
185 if err != nil {
186 return "", "", nil, fmt.Errorf("can't get parent for %s: %w", parent, err)
187 }
188
189 return parent, entry, pvalue, nil
190 }
191
192
193 func UpdateRef(sp interface{}, key string, ref spec.Ref) error {
194 switch sp.(type) {
195 case *spec.Schema:
196 case *spec.Swagger:
197 default:
198 panic("unexpected type used in getPointerFromKey")
199 }
200 debugLog("updating ref for %s with %s", key, ref.String())
201 pth, value, err := getPointerFromKey(sp, key)
202 if err != nil {
203 return err
204 }
205
206 switch refable := value.(type) {
207 case *spec.Schema:
208 refable.Ref = ref
209 case *spec.SchemaOrArray:
210 if refable.Schema != nil {
211 refable.Schema.Ref = ref
212 }
213 case *spec.SchemaOrBool:
214 if refable.Schema != nil {
215 refable.Schema.Ref = ref
216 }
217 case spec.Schema:
218 debugLog("rewriting holder for %T", refable)
219 _, entry, pvalue, erp := getParentFromKey(sp, key)
220 if erp != nil {
221 return err
222 }
223 switch container := pvalue.(type) {
224 case spec.Definitions:
225 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
226
227 case map[string]spec.Schema:
228 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
229
230 case []spec.Schema:
231 idx, err := strconv.Atoi(entry)
232 if err != nil {
233 return fmt.Errorf("%s not a number: %w", pth, err)
234 }
235 container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
236
237 case *spec.SchemaOrArray:
238
239 idx, err := strconv.Atoi(entry)
240 if err != nil {
241 return fmt.Errorf("%s not a number: %w", pth, err)
242 }
243 container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
244
245 case spec.SchemaProperties:
246 container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
247
248
249
250 default:
251 return fmt.Errorf("unhandled container type at %s: %T", key, value)
252 }
253
254 default:
255 return fmt.Errorf("no schema with ref found at %s for %T", key, value)
256 }
257
258 return nil
259 }
260
261
262 func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
263 debugLog("updating ref for %s with schema", key)
264 pth, value, err := getPointerFromKey(sp, key)
265 if err != nil {
266 return err
267 }
268
269 switch refable := value.(type) {
270 case *spec.Schema:
271 *refable = *sch
272 case spec.Schema:
273 _, entry, pvalue, erp := getParentFromKey(sp, key)
274 if erp != nil {
275 return err
276 }
277 switch container := pvalue.(type) {
278 case spec.Definitions:
279 container[entry] = *sch
280
281 case map[string]spec.Schema:
282 container[entry] = *sch
283
284 case []spec.Schema:
285 idx, err := strconv.Atoi(entry)
286 if err != nil {
287 return fmt.Errorf("%s not a number: %w", pth, err)
288 }
289 container[idx] = *sch
290
291 case *spec.SchemaOrArray:
292
293 idx, err := strconv.Atoi(entry)
294 if err != nil {
295 return fmt.Errorf("%s not a number: %w", pth, err)
296 }
297 container.Schemas[idx] = *sch
298
299 case spec.SchemaProperties:
300 container[entry] = *sch
301
302
303
304 default:
305 return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value)
306 }
307 case *spec.SchemaOrArray:
308 *refable.Schema = *sch
309
310 case *spec.SchemaOrBool:
311 *refable.Schema = *sch
312 default:
313 return fmt.Errorf("no schema with ref found at %s for %T", key, value)
314 }
315
316 return nil
317 }
318
319
320 type DeepestRefResult struct {
321 Ref spec.Ref
322 Schema *spec.Schema
323 Warnings []string
324 }
325
326
327
328
329
330
331 func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) {
332 if !ref.HasFragmentOnly {
333
334
335 return &DeepestRefResult{Ref: ref}, nil
336 }
337
338 currentRef := ref
339 visited := make(map[string]bool, 64)
340 warnings := make([]string, 0, 2)
341
342 DOWNREF:
343 for currentRef.String() != "" {
344 if path.Dir(currentRef.String()) == definitionsPath {
345
346 return &DeepestRefResult{Ref: currentRef}, nil
347 }
348
349 if _, beenThere := visited[currentRef.String()]; beenThere {
350 return nil,
351 fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String())
352 }
353
354 visited[currentRef.String()] = true
355 value, _, err := currentRef.GetPointer().Get(sp)
356 if err != nil {
357 return nil, err
358 }
359
360 switch refable := value.(type) {
361 case *spec.Schema:
362 if refable.Ref.String() == "" {
363 break DOWNREF
364 }
365 currentRef = refable.Ref
366
367 case spec.Schema:
368 if refable.Ref.String() == "" {
369 break DOWNREF
370 }
371 currentRef = refable.Ref
372
373 case *spec.SchemaOrArray:
374 if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
375 break DOWNREF
376 }
377 currentRef = refable.Schema.Ref
378
379 case *spec.SchemaOrBool:
380 if refable.Schema == nil || refable.Schema != nil && refable.Schema.Ref.String() == "" {
381 break DOWNREF
382 }
383 currentRef = refable.Schema.Ref
384
385 case spec.Response:
386
387
388 asJSON, _ := refable.MarshalJSON()
389 var asSchema spec.Schema
390
391 err := asSchema.UnmarshalJSON(asJSON)
392 if err != nil {
393 return nil,
394 fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
395 currentRef.String(), value, err,
396 )
397 }
398 warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
399
400 if asSchema.Ref.String() == "" {
401 break DOWNREF
402 }
403 currentRef = asSchema.Ref
404
405 case spec.Parameter:
406
407
408 asJSON, _ := refable.MarshalJSON()
409 var asSchema spec.Schema
410 if err := asSchema.UnmarshalJSON(asJSON); err != nil {
411 return nil,
412 fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
413 currentRef.String(), value, err,
414 )
415 }
416
417 warnings = append(warnings, fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
418
419 if asSchema.Ref.String() == "" {
420 break DOWNREF
421 }
422 currentRef = asSchema.Ref
423
424 default:
425
426 if refable == nil {
427 break DOWNREF
428 }
429
430 asJSON, _ := json.Marshal(refable)
431 var asSchema spec.Schema
432 if err := asSchema.UnmarshalJSON(asJSON); err != nil {
433 return nil,
434 fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T (%v)",
435 currentRef.String(), value, err,
436 )
437 }
438 warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable))
439
440 if asSchema.Ref.String() == "" {
441 break DOWNREF
442 }
443 currentRef = asSchema.Ref
444 }
445 }
446
447
448 sch, erv := spec.ResolveRefWithBase(sp, ¤tRef, opts)
449 if erv != nil {
450 return nil, erv
451 }
452
453 if sch == nil {
454 return nil, fmt.Errorf("no schema found at %s", currentRef.String())
455 }
456
457 return &DeepestRefResult{Ref: currentRef, Schema: sch, Warnings: warnings}, nil
458 }
459
View as plain text