-
-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy pathindex_model.go
400 lines (358 loc) · 24.1 KB
/
index_model.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package index
import (
"encoding/json"
"github.com/pb33f/libopenapi/datamodel"
"gopkg.in/yaml.v3"
"io/fs"
"log/slog"
"net/http"
"net/url"
"path/filepath"
"sync"
)
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
type Reference struct {
FullDefinition string `json:"fullDefinition,omitempty"`
Definition string `json:"definition,omitempty"`
Name string `json:"name,omitempty"`
Node *yaml.Node `json:"-"`
KeyNode *yaml.Node `json:"-"`
ParentNode *yaml.Node `json:"-"`
ParentNodeSchemaType string `json:"-"` // used to determine if the parent node is an array or not.
ParentNodeTypes []string `json:"-"` // used to capture deep journeys, if any item is an array, we need to know.
Resolved bool `json:"-"`
Circular bool `json:"-"`
Seen bool `json:"-"`
IsRemote bool `json:"isRemote,omitempty"`
Index *SpecIndex `json:"-"` // index that contains this reference.
RemoteLocation string `json:"remoteLocation,omitempty"`
Path string `json:"path,omitempty"` // this won't always be available.
RequiredRefProperties map[string][]string `json:"requiredProperties,omitempty"` // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
}
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
type ReferenceMapped struct {
OriginalReference *Reference `json:"originalReference,omitempty"`
Reference *Reference `json:"reference,omitempty"`
Definition string `json:"definition,omitempty"`
FullDefinition string `json:"fullDefinition,omitempty"`
IsPolymorphic bool `json:"isPolymorphic,omitempty"`
}
// MarshalJSON is a custom JSON marshaller for the ReferenceMapped struct.
func (rm *ReferenceMapped) MarshalJSON() ([]byte, error) {
d := map[string]interface{}{
"definition": rm.Definition,
"fullDefinition": rm.FullDefinition,
"jsonPath": rm.OriginalReference.Path,
"line": rm.OriginalReference.Node.Line,
"startColumn": rm.OriginalReference.Node.Column,
"endColumn": rm.OriginalReference.Node.Content[1].Column +
(len(rm.OriginalReference.Node.Content[1].Value) + 2),
}
if rm.IsPolymorphic {
d["isPolymorphic"] = true
}
if rm.Reference != nil && rm.Reference.KeyNode != nil {
d["targetLine"] = rm.Reference.KeyNode.Line
d["targetColumn"] = rm.Reference.KeyNode.Column
}
return json.Marshal(d)
}
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
// set of granular options. The first being the ability to set the Base URL for resolving relative references, and
// allowing or disallowing remote or local file lookups.
// - https://github.com/pb33f/libopenapi/issues/73
type SpecIndexConfig struct {
// The BaseURL will be the root from which relative references will be resolved from if they can't be found locally.
//
// For example:
// - $ref: somefile.yaml#/components/schemas/SomeSchema
//
// Might not be found locally, if the file was pulled in from a remote server (a good example is the DigitalOcean API).
// so by setting a BaseURL, the reference will try to be resolved from the remote server.
//
// If our baseURL is set to https://pb33f.io/libopenapi then our reference will try to be resolved from:
// - $ref: https://pb33f.io/libopenapi/somefile.yaml#/components/schemas/SomeSchema
//
// More details on relative references can be found in issue #73: https://github.com/pb33f/libopenapi/issues/73
BaseURL *url.URL // set the Base URL for resolving relative references if the spec is exploded.
// If resolving remotely, the RemoteURLHandler will be used to fetch the remote document.
// If not set, the default http client will be used.
// Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132
// deprecated: Use the Rolodex instead
RemoteURLHandler func(url string) (*http.Response, error)
// FSHandler is an entity that implements the `fs.FS` interface that will be used to fetch local or remote documents.
// This is useful if you want to use a custom file system handler, or if you want to use a custom http client or
// custom network implementation for a lookup.
//
// libopenapi will pass the path to the FSHandler, and it will be up to the handler to determine how to fetch
// the document. This is really useful if your application has a custom file system or uses a database for storing
// documents.
//
// Is the FSHandler is set, it will be used for all lookups, regardless of whether they are local or remote.
// it also overrides the RemoteURLHandler if set.
//
// Resolves[#85] https://github.com/pb33f/libopenapi/issues/85
// deprecated: Use the Rolodex instead
FSHandler fs.FS
// If resolving locally, the BasePath will be the root from which relative references will be resolved from
BasePath string // set the Base Path for resolving relative references if the spec is exploded.
// SpecFilePath is the name of the root specification file (usually named "openapi.yaml").
SpecFilePath string
// In an earlier version of libopenapi (pre 0.6.0) the index would automatically resolve all references
// They could have been local, or they could have been remote. This was a problem because it meant
// There was a potential for a remote exploit if a remote reference was malicious. There aren't any known
// exploits, but it's better to be safe than sorry.
//
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
AllowFileLookup bool // Allow file lookups for references. Defaults to false
// If set to true, the index will not be built out, which means only the foundational elements will be
// parsed and added to the index. This is useful to avoid building out an index if the specification is
// broken up into references and want it fully resolved.
//
// Use the `BuildIndex()` method on the index to build it out once resolved/ready.
AvoidBuildIndex bool
// If set to true, the index will not check for circular references automatically, this should be triggered
// manually, otherwise resolving may explode.
AvoidCircularReferenceCheck bool
// Logger is a logger that will be used for logging errors and warnings. If not set, the default logger
// will be used, set to the Error level.
Logger *slog.Logger
// SpecInfo is a pointer to the SpecInfo struct that contains the root node and the spec version. It's the
// struct that was used to create this index.
SpecInfo *datamodel.SpecInfo
// Rolodex is what provides all file and remote based lookups. Without the rolodex, no remote or file lookups
// can be used. Normally you won't need to worry about setting this as each root document gets a rolodex
// of its own automatically.
Rolodex *Rolodex
// The absolute path to the spec file for the index. Will be absolute, either as a http link or a file.
// If the index is for a single file spec, then the root will be empty.
SpecAbsolutePath string
// IgnorePolymorphicCircularReferences will skip over checking for circular references in polymorphic schemas.
// A polymorphic schema is any schema that is composed other schemas using references via `oneOf`, `anyOf` of `allOf`.
// This is disabled by default, which means polymorphic circular references will be checked.
IgnorePolymorphicCircularReferences bool
// IgnoreArrayCircularReferences will skip over checking for circular references in arrays. Sometimes a circular
// reference is required to describe a data-shape correctly. Often those shapes are valid circles if the
// type of the schema implementing the loop is an array. An empty array would technically break the loop.
// So if libopenapi is returning circular references for this use case, then this option should be enabled.
// this is disabled by default, which means array circular references will be checked.
IgnoreArrayCircularReferences bool
// SkipDocumentCheck will skip the document check when building the index. A document check will look for an 'openapi'
// or 'swagger' node in the root of the document. If it's not found, then the document is not a valid OpenAPI or
// the file is a JSON Schema. To allow JSON Schema files to be included set this to true.
SkipDocumentCheck bool
// ExtractRefsSequentially will extract all references sequentially, which means the index will look up references
// as it finds them, vs looking up everything asynchronously.
// This is a more thorough way of building the index, but it's slower. It's required building a document
// to be bundled.
ExtractRefsSequentially bool
// ExcludeExtensionReferences will prevent the indexing of any $ref pointers buried under extensions.
// defaults to false (which means extensions will be included)
ExcludeExtensionRefs bool
// private fields
uri []string
}
// SetTheoreticalRoot sets the spec file paths to point to a theoretical spec file, which does not exist but is required
// in order to formulate the absolute path to root references correctly.
func (s *SpecIndexConfig) SetTheoreticalRoot() {
s.SpecFilePath = filepath.Join(s.BasePath, theoreticalRoot)
basePath := s.BasePath
if !filepath.IsAbs(basePath) {
basePath, _ = filepath.Abs(basePath)
}
s.SpecAbsolutePath = filepath.Join(basePath, theoreticalRoot)
}
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
// AllowFileLookup set to true. This is the default behaviour of the index in previous versions of libopenapi. (pre 0.6.0)
//
// The default BasePath is the current working directory.
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
return &SpecIndexConfig{
AllowRemoteLookup: true,
AllowFileLookup: true,
}
}
// CreateClosedAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
// AllowFileLookup set to false. This is the default behaviour of the index in versions 0.6.0+
//
// The default BasePath is the current working directory.
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
return &SpecIndexConfig{}
}
// SpecIndex is a complete pre-computed index of the entire specification. Numbers are pre-calculated and
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
// everything is pre-walked if you need it.
type SpecIndex struct {
specAbsolutePath string
rolodex *Rolodex // the rolodex is used to fetch remote and file based documents.
allRefs map[string]*Reference // all (deduplicated) refs
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
linesWithRefs map[int]bool // lines that link to references.
allMappedRefs map[string]*Reference // these are the located mapped refs
allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs
refsByLine map[string]map[int]bool // every reference and the lines it's referenced from
pathRefs map[string]map[string]*Reference // all path references
paramOpRefs map[string]map[string]map[string][]*Reference // params in operations.
paramCompRefs map[string]*Reference // params in components
paramAllRefs map[string]*Reference // combined components and ops
paramInlineDuplicateNames map[string][]*Reference // inline params all with the same name
globalTagRefs map[string]*Reference // top level global tags
securitySchemeRefs map[string]*Reference // top level security schemes
requestBodiesRefs map[string]*Reference // top level request bodies
responsesRefs map[string]*Reference // top level responses
headersRefs map[string]*Reference // top level responses
examplesRefs map[string]*Reference // top level examples
securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements
callbacksRefs map[string]map[string][]*Reference // all links
linksRefs map[string]map[string][]*Reference // all callbacks
operationTagsRefs map[string]map[string][]*Reference // tags found in operations
operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations.
operationSummaryRefs map[string]map[string]*Reference // summaries in operations
callbackRefs map[string]*Reference // top level callback refs
serversRefs []*Reference // all top level server refs
rootServersNode *yaml.Node // servers root node
opServersRefs map[string]map[string][]*Reference // all operation level server overrides.
polymorphicRefs map[string]*Reference // every reference to a polymorphic ref
polymorphicAllOfRefs []*Reference // every reference to 'allOf' references
polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references
polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references
externalDocumentsRef []*Reference // all external documents in spec
rootSecurity []*Reference // root security definitions.
rootSecurityNode *yaml.Node // root security node.
refsWithSiblings map[string]Reference // references with sibling elements next to them
pathRefsLock sync.RWMutex // create lock for all refs maps, we want to build data as fast as we can
externalDocumentsCount int // number of externalDocument nodes found
operationTagsCount int // number of unique tags in operations
globalTagsCount int // number of global tags defined
totalTagsCount int // number unique tags in spec
globalLinksCount int // component links
globalCallbacksCount int // component callbacks
pathCount int // number of paths
operationCount int // number of operations
operationParamCount int // number of params defined in operations
componentParamCount int // number of params defined in components
componentsInlineParamUniqueCount int // number of inline params with unique names
componentsInlineParamDuplicateCount int // number of inline params with duplicate names
schemaCount int // number of schemas
refCount int // total ref count
root *yaml.Node // the root document
pathsNode *yaml.Node // paths node
tagsNode *yaml.Node // tags node
parametersNode *yaml.Node // components/parameters node
allParameters map[string]*Reference // all parameters (components/defs)
schemasNode *yaml.Node // components/schemas node
allRefSchemaDefinitions []*Reference // all schemas found that are references.
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
allComponentSchemaDefinitions *sync.Map // all schemas found in components (openapi) or definitions (swagger).
securitySchemesNode *yaml.Node // components/securitySchemes node
allSecuritySchemes *sync.Map // all security schemes / definitions.
allComponentSchemas map[string]*Reference // all component schema definitions
allComponentSchemasLock sync.RWMutex // prevent concurrent read writes to the schema file which causes a race condition
requestBodiesNode *yaml.Node // components/requestBodies node
allRequestBodies map[string]*Reference // all request bodies
responsesNode *yaml.Node // components/responses node
allResponses map[string]*Reference // all responses
headersNode *yaml.Node // components/headers node
allHeaders map[string]*Reference // all headers
examplesNode *yaml.Node // components/examples node
allExamples map[string]*Reference // all components examples
linksNode *yaml.Node // components/links node
allLinks map[string]*Reference // all links
callbacksNode *yaml.Node // components/callbacks node
allCallbacks map[string]*Reference // all components examples
allExternalDocuments map[string]*Reference // all external documents
externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds
refErrors []error // errors when indexing references
operationParamErrors []error // errors when indexing parameters
allDescriptions []*DescriptionReference // every single description found in the spec.
allSummaries []*DescriptionReference // every single summary found in the spec.
allEnums []*EnumReference // every single enum found in the spec.
allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec.
enumCount int
descriptionCount int
summaryCount int
refLock sync.Mutex
nodeMapLock sync.RWMutex
componentLock sync.RWMutex
errorLock sync.RWMutex
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
polyCircularReferences []*CircularReferenceResult // only available when the resolver has been used.
arrayCircularReferences []*CircularReferenceResult // only available when the resolver has been used.
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
config *SpecIndexConfig // configuration for the index
componentIndexChan chan struct{}
polyComponentIndexChan chan struct{}
resolver *Resolver
cache *sync.Map
built bool
uri []string
logger *slog.Logger
nodeMap map[int]map[int]*yaml.Node
nodeMapCompleted chan struct{}
pendingResolve []refMap
highModelCache Cache
}
// GetResolver returns the resolver for this index.
func (index *SpecIndex) GetResolver() *Resolver {
return index.resolver
}
// GetConfig returns the SpecIndexConfig for this index.
func (index *SpecIndex) GetConfig() *SpecIndexConfig {
return index.config
}
func (index *SpecIndex) GetNodeMap() map[int]map[int]*yaml.Node {
return index.nodeMap
}
func (index *SpecIndex) GetCache() *sync.Map {
return index.cache
}
// SetAbsolutePath sets the absolute path to the spec file for the index. Will be absolute, either as a http link or a file.
func (index *SpecIndex) SetAbsolutePath(absolutePath string) {
index.specAbsolutePath = absolutePath
}
// GetSpecAbsolutePath returns the absolute path to the spec file for the index. Will be absolute, either as a http link or a file.
func (index *SpecIndex) GetSpecAbsolutePath() string {
return index.specAbsolutePath
}
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
// URI based document. Decides if the reference is local, remote or in a file.
type ExternalLookupFunction func(id string) (foundNode *yaml.Node, rootNode *yaml.Node, lookupError error)
// IndexingError holds data about something that went wrong during indexing.
type IndexingError struct {
Err error
Node *yaml.Node
KeyNode *yaml.Node
Path string
}
func (i *IndexingError) Error() string {
return i.Err.Error()
}
// DescriptionReference holds data about a description that was found and where it was found.
type DescriptionReference struct {
Content string
Path string
KeyNode *yaml.Node
Node *yaml.Node
ParentNode *yaml.Node
IsSummary bool
}
type EnumReference struct {
Node *yaml.Node
KeyNode *yaml.Node
Type *yaml.Node
Path string
SchemaNode *yaml.Node
ParentNode *yaml.Node
}
type ObjectReference struct {
Node *yaml.Node
KeyNode *yaml.Node
Path string
ParentNode *yaml.Node
}
var methodTypes = []string{"get", "post", "put", "patch", "options", "head", "delete"}