1 package analysis
2
3 import (
4 "fmt"
5 "path"
6 "sort"
7 "strings"
8
9 "github.com/go-openapi/analysis/internal/flatten/operations"
10 "github.com/go-openapi/analysis/internal/flatten/replace"
11 "github.com/go-openapi/analysis/internal/flatten/schutils"
12 "github.com/go-openapi/analysis/internal/flatten/sortref"
13 "github.com/go-openapi/spec"
14 "github.com/go-openapi/swag"
15 )
16
17
18 type InlineSchemaNamer struct {
19 Spec *spec.Swagger
20 Operations map[string]operations.OpRef
21 flattenContext *context
22 opts *FlattenOpts
23 }
24
25
26 func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *AnalyzedSchema) error {
27 debugLog("naming inlined schema at %s", key)
28
29 parts := sortref.KeyParts(key)
30 for _, name := range namesFromKey(parts, aschema, isn.Operations) {
31 if name == "" {
32 continue
33 }
34
35
36 mangle := mangler(isn.opts)
37 newName, isOAIGen := uniqifyName(isn.Spec.Definitions, mangle(name))
38
39
40 sch := schutils.Clone(schema)
41
42
43 debugLog("rewriting schema to ref: key=%s with new name: %s", key, newName)
44 if err := replace.RewriteSchemaToRef(isn.Spec, key,
45 spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
46 return fmt.Errorf("error while creating definition %q from inline schema: %w", newName, err)
47 }
48
49
50
51
52
53 an := New(isn.Spec)
54 for k, v := range an.references.allRefs {
55 r, erd := replace.DeepestRef(isn.opts.Swagger(), isn.opts.ExpandOpts(false), v)
56 if erd != nil {
57 return fmt.Errorf("at %s, %w", k, erd)
58 }
59
60 if isn.opts.flattenContext != nil {
61 isn.opts.flattenContext.warnings = append(isn.opts.flattenContext.warnings, r.Warnings...)
62 }
63
64 if r.Ref.String() != key && (r.Ref.String() != path.Join(definitionsPath, newName) || path.Dir(v.String()) == definitionsPath) {
65 continue
66 }
67
68 debugLog("found a $ref to a rewritten schema: %s points to %s", k, v.String())
69
70
71 if err := replace.UpdateRef(isn.Spec, k,
72 spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
73 return err
74 }
75 }
76
77
78 sch.AddExtension("x-go-gen-location", GenLocation(parts))
79
80
81 schutils.Save(isn.Spec, newName, sch)
82
83
84 if isn.flattenContext == nil {
85 continue
86 }
87
88 debugLog("track created ref: key=%s, newName=%s, isOAIGen=%t", key, newName, isOAIGen)
89 resolved := false
90
91 if _, ok := isn.flattenContext.newRefs[key]; ok {
92 resolved = isn.flattenContext.newRefs[key].resolved
93 }
94
95 isn.flattenContext.newRefs[key] = &newRef{
96 key: key,
97 newName: newName,
98 path: path.Join(definitionsPath, newName),
99 isOAIGen: isOAIGen,
100 resolved: resolved,
101 schema: sch,
102 }
103 }
104
105 return nil
106 }
107
108
109 func uniqifyName(definitions spec.Definitions, name string) (string, bool) {
110 isOAIGen := false
111 if name == "" {
112 name = "oaiGen"
113 isOAIGen = true
114 }
115
116 if len(definitions) == 0 {
117 return name, isOAIGen
118 }
119
120 unq := true
121 for k := range definitions {
122 if strings.EqualFold(k, name) {
123 unq = false
124
125 break
126 }
127 }
128
129 if unq {
130 return name, isOAIGen
131 }
132
133 name += "OAIGen"
134 isOAIGen = true
135 var idx int
136 unique := name
137 _, known := definitions[unique]
138
139 for known {
140 idx++
141 unique = fmt.Sprintf("%s%d", name, idx)
142 _, known = definitions[unique]
143 }
144
145 return unique, isOAIGen
146 }
147
148 func namesFromKey(parts sortref.SplitKey, aschema *AnalyzedSchema, operations map[string]operations.OpRef) []string {
149 var (
150 baseNames [][]string
151 startIndex int
152 )
153
154 switch {
155 case parts.IsOperation():
156 baseNames, startIndex = namesForOperation(parts, operations)
157 case parts.IsDefinition():
158 baseNames, startIndex = namesForDefinition(parts)
159 default:
160
161 baseNames = [][]string{parts}
162 startIndex = len(baseNames) + 1
163 }
164
165 result := make([]string, 0, len(baseNames))
166 for _, segments := range baseNames {
167 nm := parts.BuildName(segments, startIndex, partAdder(aschema))
168 if nm == "" {
169 continue
170 }
171
172 result = append(result, nm)
173 }
174 sort.Strings(result)
175
176 debugLog("names from parts: %v => %v", parts, result)
177 return result
178 }
179
180 func namesForParam(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
181 var (
182 baseNames [][]string
183 startIndex int
184 )
185
186 piref := parts.PathItemRef()
187 if piref.String() != "" && parts.IsOperationParam() {
188 if op, ok := operations[piref.String()]; ok {
189 startIndex = 5
190 baseNames = append(baseNames, []string{op.ID, "params", "body"})
191 }
192 } else if parts.IsSharedOperationParam() {
193 pref := parts.PathRef()
194 for k, v := range operations {
195 if strings.HasPrefix(k, pref.String()) {
196 startIndex = 4
197 baseNames = append(baseNames, []string{v.ID, "params", "body"})
198 }
199 }
200 }
201
202 return baseNames, startIndex
203 }
204
205 func namesForOperation(parts sortref.SplitKey, operations map[string]operations.OpRef) ([][]string, int) {
206 var (
207 baseNames [][]string
208 startIndex int
209 )
210
211
212 if parts.IsOperationParam() || parts.IsSharedOperationParam() {
213 baseNames, startIndex = namesForParam(parts, operations)
214 }
215
216
217 if parts.IsOperationResponse() {
218 piref := parts.PathItemRef()
219 if piref.String() != "" {
220 if op, ok := operations[piref.String()]; ok {
221 startIndex = 6
222 baseNames = append(baseNames, []string{op.ID, parts.ResponseName(), "body"})
223 }
224 }
225 }
226
227 return baseNames, startIndex
228 }
229
230 func namesForDefinition(parts sortref.SplitKey) ([][]string, int) {
231 nm := parts.DefinitionName()
232 if nm != "" {
233 return [][]string{{parts.DefinitionName()}}, 2
234 }
235
236 return [][]string{}, 0
237 }
238
239
240 func partAdder(aschema *AnalyzedSchema) sortref.PartAdder {
241 return func(part string) []string {
242 segments := make([]string, 0, 2)
243
244 if part == "items" || part == "additionalItems" {
245 if aschema.IsTuple || aschema.IsTupleWithExtra {
246 segments = append(segments, "tuple")
247 } else {
248 segments = append(segments, "items")
249 }
250
251 if part == "additionalItems" {
252 segments = append(segments, part)
253 }
254
255 return segments
256 }
257
258 segments = append(segments, part)
259
260 return segments
261 }
262 }
263
264 func mangler(o *FlattenOpts) func(string) string {
265 if o.KeepNames {
266 return func(in string) string { return in }
267 }
268
269 return swag.ToJSONName
270 }
271
272 func nameFromRef(ref spec.Ref, o *FlattenOpts) string {
273 mangle := mangler(o)
274
275 u := ref.GetURL()
276 if u.Fragment != "" {
277 return mangle(path.Base(u.Fragment))
278 }
279
280 if u.Path != "" {
281 bn := path.Base(u.Path)
282 if bn != "" && bn != "/" {
283 ext := path.Ext(bn)
284 if ext != "" {
285 return mangle(bn[:len(bn)-len(ext)])
286 }
287
288 return mangle(bn)
289 }
290 }
291
292 return mangle(strings.ReplaceAll(u.Host, ".", " "))
293 }
294
295
296
297
298
299 func GenLocation(parts sortref.SplitKey) string {
300 switch {
301 case parts.IsOperation():
302 return "operations"
303 case parts.IsDefinition():
304 return "models"
305 default:
306 return ""
307 }
308 }
309
View as plain text