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