1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package analysis
16
17 import (
18 "fmt"
19 "log"
20 "path"
21 "sort"
22 "strings"
23
24 "github.com/go-openapi/analysis/internal/flatten/normalize"
25 "github.com/go-openapi/analysis/internal/flatten/operations"
26 "github.com/go-openapi/analysis/internal/flatten/replace"
27 "github.com/go-openapi/analysis/internal/flatten/schutils"
28 "github.com/go-openapi/analysis/internal/flatten/sortref"
29 "github.com/go-openapi/jsonpointer"
30 "github.com/go-openapi/spec"
31 )
32
33 const definitionsPath = "#/definitions"
34
35
36 type newRef struct {
37 key string
38 newName string
39 path string
40 isOAIGen bool
41 resolved bool
42 schema *spec.Schema
43 parents []string
44 }
45
46
47 type context struct {
48 newRefs map[string]*newRef
49 warnings []string
50 resolved map[string]string
51 }
52
53 func newContext() *context {
54 return &context{
55 newRefs: make(map[string]*newRef, 150),
56 warnings: make([]string, 0),
57 resolved: make(map[string]string, 50),
58 }
59 }
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 func Flatten(opts FlattenOpts) error {
110 debugLog("FlattenOpts: %#v", opts)
111
112 opts.flattenContext = newContext()
113
114
115
116
117 if err := expand(&opts); err != nil {
118 return err
119 }
120
121
122
123
124
125 if err := normalizeRef(&opts); err != nil {
126 return err
127 }
128
129
130
131
132 if opts.RemoveUnused {
133 removeUnusedShared(&opts)
134 }
135
136
137 if err := importReferences(&opts); err != nil {
138 return err
139 }
140
141
142 if !opts.Minimal && !opts.Expand {
143 if err := nameInlinedSchemas(&opts); err != nil {
144 return err
145 }
146 }
147
148
149
150 if err := stripPointersAndOAIGen(&opts); err != nil {
151 return err
152 }
153
154
155 if opts.RemoveUnused {
156 removeUnused(&opts)
157 }
158
159
160 opts.croak()
161
162
163
164
165
166
167
168
169 return nil
170 }
171
172 func expand(opts *FlattenOpts) error {
173 if err := spec.ExpandSpec(opts.Swagger(), opts.ExpandOpts(!opts.Expand)); err != nil {
174 return err
175 }
176
177 opts.Spec.reload()
178
179 return nil
180 }
181
182
183
184 func normalizeRef(opts *FlattenOpts) error {
185 debugLog("normalizeRef")
186
187 altered := false
188 for k, w := range opts.Spec.references.allRefs {
189 if !strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) {
190 continue
191 }
192
193 altered = true
194 debugLog("stripping absolute path for: %s", w.String())
195
196
197 if err := replace.UpdateRef(opts.Swagger(), k,
198 spec.MustCreateRef(path.Join(definitionsPath, path.Base(w.String())))); err != nil {
199 return err
200 }
201 }
202
203 if altered {
204 opts.Spec.reload()
205 }
206
207 return nil
208 }
209
210 func removeUnusedShared(opts *FlattenOpts) {
211 opts.Swagger().Parameters = nil
212 opts.Swagger().Responses = nil
213
214 opts.Spec.reload()
215 }
216
217 func importReferences(opts *FlattenOpts) error {
218 var (
219 imported bool
220 err error
221 )
222
223 for !imported && err == nil {
224
225
226 imported, err = importExternalReferences(opts)
227
228 opts.Spec.reload()
229 }
230
231 return err
232 }
233
234
235 func nameInlinedSchemas(opts *FlattenOpts) error {
236 debugLog("nameInlinedSchemas")
237
238 namer := &InlineSchemaNamer{
239 Spec: opts.Swagger(),
240 Operations: operations.AllOpRefsByRef(opts.Spec, nil),
241 flattenContext: opts.flattenContext,
242 opts: opts,
243 }
244
245 depthFirst := sortref.DepthFirst(opts.Spec.allSchemas)
246 for _, key := range depthFirst {
247 sch := opts.Spec.allSchemas[key]
248 if sch.Schema == nil || sch.Schema.Ref.String() != "" || sch.TopLevel {
249 continue
250 }
251
252 asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
253 if err != nil {
254 return fmt.Errorf("schema analysis [%s]: %w", key, err)
255 }
256
257 if asch.isAnalyzedAsComplex() {
258 if err := namer.Name(key, sch.Schema, asch); err != nil {
259 return err
260 }
261 }
262 }
263
264 opts.Spec.reload()
265
266 return nil
267 }
268
269 func removeUnused(opts *FlattenOpts) {
270 for removeUnusedSinglePass(opts) {
271
272 }
273 }
274
275 func removeUnusedSinglePass(opts *FlattenOpts) (hasRemoved bool) {
276 expected := make(map[string]struct{})
277 for k := range opts.Swagger().Definitions {
278 expected[path.Join(definitionsPath, jsonpointer.Escape(k))] = struct{}{}
279 }
280
281 for _, k := range opts.Spec.AllDefinitionReferences() {
282 delete(expected, k)
283 }
284
285 for k := range expected {
286 hasRemoved = true
287 debugLog("removing unused definition %s", path.Base(k))
288 if opts.Verbose {
289 log.Printf("info: removing unused definition: %s", path.Base(k))
290 }
291 delete(opts.Swagger().Definitions, path.Base(k))
292 }
293
294 opts.Spec.reload()
295
296 return hasRemoved
297 }
298
299 func importKnownRef(entry sortref.RefRevIdx, refStr, newName string, opts *FlattenOpts) error {
300
301
302 debugLog("resolving known ref [%s] to %s", refStr, newName)
303
304 for _, key := range entry.Keys {
305 if err := replace.UpdateRef(opts.Swagger(), key, spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
306 return err
307 }
308 }
309
310 return nil
311 }
312
313 func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) error {
314 var (
315 isOAIGen bool
316 newName string
317 )
318
319 debugLog("resolving schema from remote $ref [%s]", refStr)
320
321 sch, err := spec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))
322 if err != nil {
323 return fmt.Errorf("could not resolve schema: %w", err)
324 }
325
326
327 partialAnalyzer := &Spec{
328 references: referenceAnalysis{},
329 patterns: patternAnalysis{},
330 enums: enumAnalysis{},
331 }
332 partialAnalyzer.reset()
333 partialAnalyzer.analyzeSchema("", sch, "/")
334
335
336 for key, ref := range partialAnalyzer.references.allRefs {
337 if err := replace.UpdateRef(sch, key, spec.MustCreateRef(normalize.RebaseRef(entry.Ref.String(), ref.String()))); err != nil {
338 return fmt.Errorf("failed to rewrite ref for key %q at %s: %w", key, entry.Ref.String(), err)
339 }
340 }
341
342
343 newName, isOAIGen = uniqifyName(opts.Swagger().Definitions, nameFromRef(entry.Ref, opts))
344 debugLog("new name for [%s]: %s - with name conflict:%t", strings.Join(entry.Keys, ", "), newName, isOAIGen)
345
346 opts.flattenContext.resolved[refStr] = newName
347
348
349 for _, key := range entry.Keys {
350 if err := replace.UpdateRef(opts.Swagger(), key,
351 spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
352 return err
353 }
354
355
356 resolved := false
357 if _, ok := opts.flattenContext.newRefs[key]; ok {
358 resolved = opts.flattenContext.newRefs[key].resolved
359 }
360
361 debugLog("keeping track of ref: %s (%s), resolved: %t", key, newName, resolved)
362 opts.flattenContext.newRefs[key] = &newRef{
363 key: key,
364 newName: newName,
365 path: path.Join(definitionsPath, newName),
366 isOAIGen: isOAIGen,
367 resolved: resolved,
368 schema: sch,
369 }
370 }
371
372
373 schutils.Save(opts.Swagger(), newName, sch)
374
375 return nil
376 }
377
378
379
380
381
382
383 func importExternalReferences(opts *FlattenOpts) (bool, error) {
384 debugLog("importExternalReferences")
385
386 groupedRefs := sortref.ReverseIndex(opts.Spec.references.schemas, opts.BasePath)
387 sortedRefStr := make([]string, 0, len(groupedRefs))
388 if opts.flattenContext == nil {
389 opts.flattenContext = newContext()
390 }
391
392
393 for refStr := range groupedRefs {
394 sortedRefStr = append(sortedRefStr, refStr)
395 }
396 sort.Strings(sortedRefStr)
397
398 complete := true
399
400 for _, refStr := range sortedRefStr {
401 entry := groupedRefs[refStr]
402 if entry.Ref.HasFragmentOnly {
403 continue
404 }
405
406 complete = false
407
408 newName := opts.flattenContext.resolved[refStr]
409 if newName != "" {
410 if err := importKnownRef(entry, refStr, newName, opts); err != nil {
411 return false, err
412 }
413
414 continue
415 }
416
417
418 if err := importNewRef(entry, refStr, opts); err != nil {
419 return false, err
420 }
421 }
422
423
424 for k := range opts.flattenContext.newRefs {
425 r := opts.flattenContext.newRefs[k]
426
427
428 if r.schema.Ref.String() != "" {
429 ref := spec.MustCreateRef(r.path)
430 sch, err := spec.ResolveRefWithBase(opts.Swagger(), &ref, opts.ExpandOpts(false))
431 if err != nil {
432 return false, fmt.Errorf("could not resolve schema: %w", err)
433 }
434
435 r.schema = sch
436 }
437
438 if r.path == k {
439 continue
440 }
441
442
443 renamed := *r
444 renamed.key = r.path
445 opts.flattenContext.newRefs[renamed.path] = &renamed
446
447
448 r.newName = path.Base(k)
449 r.schema = spec.RefSchema(r.path)
450 r.path = k
451 r.isOAIGen = strings.Contains(k, "OAIGen")
452 }
453
454 return complete, nil
455 }
456
457
458
459 func stripPointersAndOAIGen(opts *FlattenOpts) error {
460
461 if err := namePointers(opts); err != nil {
462 return err
463 }
464
465
466 hasIntroducedPointerOrInline, ers := stripOAIGen(opts)
467 if ers != nil {
468 return ers
469 }
470
471
472 for hasIntroducedPointerOrInline {
473 if !opts.Minimal {
474 opts.Spec.reload()
475 if err := nameInlinedSchemas(opts); err != nil {
476 return err
477 }
478 }
479
480 if err := namePointers(opts); err != nil {
481 return err
482 }
483
484
485 var err error
486 if hasIntroducedPointerOrInline, err = stripOAIGen(opts); err != nil {
487 return err
488 }
489 }
490
491 return nil
492 }
493
494
495
496
497
498
499
500
501
502
503 func stripOAIGen(opts *FlattenOpts) (bool, error) {
504 debugLog("stripOAIGen")
505 replacedWithComplex := false
506
507
508 for _, r := range opts.flattenContext.newRefs {
509 updateRefParents(opts.Spec.references.allRefs, r)
510 }
511
512 for k := range opts.flattenContext.newRefs {
513 r := opts.flattenContext.newRefs[k]
514 debugLog("newRefs[%s]: isOAIGen: %t, resolved: %t, name: %s, path:%s, #parents: %d, parents: %v, ref: %s",
515 k, r.isOAIGen, r.resolved, r.newName, r.path, len(r.parents), r.parents, r.schema.Ref.String())
516
517 if !r.isOAIGen || len(r.parents) == 0 {
518 continue
519 }
520
521 hasReplacedWithComplex, err := stripOAIGenForRef(opts, k, r)
522 if err != nil {
523 return replacedWithComplex, err
524 }
525
526 replacedWithComplex = replacedWithComplex || hasReplacedWithComplex
527 }
528
529 debugLog("replacedWithComplex: %t", replacedWithComplex)
530 opts.Spec.reload()
531
532 return replacedWithComplex, nil
533 }
534
535
536 func updateRefParents(allRefs map[string]spec.Ref, r *newRef) {
537 if !r.isOAIGen || r.resolved {
538 return
539 }
540 for k, v := range allRefs {
541 if r.path != v.String() {
542 continue
543 }
544
545 found := false
546 for _, p := range r.parents {
547 if p == k {
548 found = true
549
550 break
551 }
552 }
553 if !found {
554 r.parents = append(r.parents, k)
555 }
556 }
557 }
558
559 func stripOAIGenForRef(opts *FlattenOpts, k string, r *newRef) (bool, error) {
560 replacedWithComplex := false
561
562 pr := sortref.TopmostFirst(r.parents)
563
564
565 debugLog("rewrite first parent %s with schema", pr[0])
566 if err := replace.UpdateRefWithSchema(opts.Swagger(), pr[0], r.schema); err != nil {
567 return false, err
568 }
569
570 if pa, ok := opts.flattenContext.newRefs[pr[0]]; ok && pa.isOAIGen {
571
572 debugLog("update parent entry: %s", pr[0])
573 pa.schema = r.schema
574 pa.resolved = false
575 replacedWithComplex = true
576 }
577
578
579 if len(pr) > 1 {
580 for _, p := range pr[1:] {
581 replacingRef := spec.MustCreateRef(pr[0])
582
583
584 replacedWithComplex = replacedWithComplex || path.Dir(replacingRef.String()) != definitionsPath
585 debugLog("rewrite parent with ref: %s", replacingRef.String())
586
587
588
589 if err := replace.UpdateRef(opts.Swagger(), p, replacingRef); err != nil {
590 return false, err
591 }
592
593 if pa, ok := opts.flattenContext.newRefs[p]; ok && pa.isOAIGen {
594
595 debugLog("update parent entry: %s", p)
596 pa.schema = r.schema
597 pa.resolved = false
598 replacedWithComplex = true
599 }
600 }
601 }
602
603
604 debugLog("removing definition %s", path.Base(r.path))
605 delete(opts.Swagger().Definitions, path.Base(r.path))
606
607
608 for kk, value := range opts.flattenContext.newRefs {
609 if kk == k || !value.isOAIGen || value.resolved {
610 continue
611 }
612
613 found := false
614 newParents := make([]string, 0, len(value.parents))
615 for _, parent := range value.parents {
616 switch {
617 case parent == r.path:
618 found = true
619 parent = pr[0]
620 case strings.HasPrefix(parent, r.path+"/"):
621 found = true
622 parent = path.Join(pr[0], strings.TrimPrefix(parent, r.path))
623 }
624
625 newParents = append(newParents, parent)
626 }
627
628 if found {
629 value.parents = newParents
630 }
631 }
632
633
634 debugLog("marking naming conflict resolved for key: %s", r.key)
635 opts.flattenContext.newRefs[r.key].isOAIGen = false
636 opts.flattenContext.newRefs[r.key].resolved = true
637
638
639 if r.schema != nil && r.schema.Ref.String() == "" {
640 asch, err := Schema(SchemaOpts{Schema: r.schema, Root: opts.Swagger(), BasePath: opts.BasePath})
641 if err != nil {
642 return false, err
643 }
644
645 debugLog("re-inlined schema: parent: %s, %t", pr[0], asch.isAnalyzedAsComplex())
646 replacedWithComplex = replacedWithComplex || !(path.Dir(pr[0]) == definitionsPath) && asch.isAnalyzedAsComplex()
647 }
648
649 return replacedWithComplex, nil
650 }
651
652
653
654
655
656 func namePointers(opts *FlattenOpts) error {
657 debugLog("name pointers")
658
659 refsToReplace := make(map[string]SchemaRef, len(opts.Spec.references.schemas))
660 for k, ref := range opts.Spec.references.allRefs {
661 debugLog("name pointers: %q => %#v", k, ref)
662 if path.Dir(ref.String()) == definitionsPath {
663
664 continue
665 }
666
667 result, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), ref)
668 if err != nil {
669 return fmt.Errorf("at %s, %w", k, err)
670 }
671
672 replacingRef := result.Ref
673 sch := result.Schema
674 if opts.flattenContext != nil {
675 opts.flattenContext.warnings = append(opts.flattenContext.warnings, result.Warnings...)
676 }
677
678 debugLog("planning pointer to replace at %s: %s, resolved to: %s", k, ref.String(), replacingRef.String())
679 refsToReplace[k] = SchemaRef{
680 Name: k,
681 Ref: replacingRef,
682 Schema: sch,
683 TopLevel: path.Dir(replacingRef.String()) == definitionsPath,
684 }
685 }
686
687 depthFirst := sortref.DepthFirst(refsToReplace)
688 namer := &InlineSchemaNamer{
689 Spec: opts.Swagger(),
690 Operations: operations.AllOpRefsByRef(opts.Spec, nil),
691 flattenContext: opts.flattenContext,
692 opts: opts,
693 }
694
695 for _, key := range depthFirst {
696 v := refsToReplace[key]
697
698 result, erd := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), v.Ref)
699 if erd != nil {
700 return fmt.Errorf("at %s, %w", key, erd)
701 }
702
703 if opts.flattenContext != nil {
704 opts.flattenContext.warnings = append(opts.flattenContext.warnings, result.Warnings...)
705 }
706
707 v.Ref = result.Ref
708 v.Schema = result.Schema
709 v.TopLevel = path.Dir(result.Ref.String()) == definitionsPath
710 debugLog("replacing pointer at %s: resolved to: %s", key, v.Ref.String())
711
712 if v.TopLevel {
713 debugLog("replace pointer %s by canonical definition: %s", key, v.Ref.String())
714
715
716 if err := replace.UpdateRef(opts.Swagger(), key, v.Ref); err != nil {
717 return err
718 }
719
720 continue
721 }
722
723 if err := flattenAnonPointer(key, v, refsToReplace, namer, opts); err != nil {
724 return err
725 }
726 }
727
728 opts.Spec.reload()
729
730 return nil
731 }
732
733 func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]SchemaRef, namer *InlineSchemaNamer, opts *FlattenOpts) error {
734
735
736
737
738
739
740
741 debugLog("namePointers at %s for %s", key, v.Ref.String())
742
743
744 asch, ers := Schema(SchemaOpts{Schema: v.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
745 if ers != nil {
746 return fmt.Errorf("schema analysis [%s]: %w", key, ers)
747 }
748 callers := make([]string, 0, 64)
749
750 debugLog("looking for callers")
751
752 an := New(opts.Swagger())
753 for k, w := range an.references.allRefs {
754 r, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), w)
755 if err != nil {
756 return fmt.Errorf("at %s, %w", key, err)
757 }
758
759 if opts.flattenContext != nil {
760 opts.flattenContext.warnings = append(opts.flattenContext.warnings, r.Warnings...)
761 }
762
763 if r.Ref.String() == v.Ref.String() {
764 callers = append(callers, k)
765 }
766 }
767
768 debugLog("callers for %s: %d", v.Ref.String(), len(callers))
769 if len(callers) == 0 {
770
771 return nil
772 }
773
774 parts := sortref.KeyParts(v.Ref.String())
775 debugLog("number of callers for %s: %d", v.Ref.String(), len(callers))
776
777
778
779 debugLog("decide what to do with the schema pointed to: asch.IsSimpleSchema=%t, len(callers)=%d, parts.IsSharedParam=%t, parts.IsSharedResponse=%t",
780 asch.IsSimpleSchema, len(callers), parts.IsSharedParam(), parts.IsSharedResponse(),
781 )
782
783 if (!asch.IsSimpleSchema || len(callers) > 1) && !parts.IsSharedParam() && !parts.IsSharedResponse() {
784 debugLog("replace JSON pointer at [%s] by definition: %s", key, v.Ref.String())
785 if err := namer.Name(v.Ref.String(), v.Schema, asch); err != nil {
786 return err
787 }
788
789
790 for _, caller := range callers {
791 if caller == key {
792 continue
793 }
794
795
796 debugLog("identified caller of %s at [%s]", v.Ref.String(), caller)
797 c := refsToReplace[caller]
798 c.Ref = v.Ref
799 refsToReplace[caller] = c
800 }
801
802 return nil
803 }
804
805
806 debugLog("expand JSON pointer for key=%s", key)
807
808 if err := replace.UpdateRefWithSchema(opts.Swagger(), key, v.Schema); err != nil {
809 return err
810 }
811
812
813 return nil
814 }
815
View as plain text